Unit testing Grails域标准验证器:我是否应该测试?

Unit testing Grails域标准验证器:我是否应该测试?,unit-testing,testing,grails,tdd,Unit Testing,Testing,Grails,Tdd,这是grails应用程序中的一个简单域类: class User { String username static constraints = { username unique: true } } 我的问题是:我是否应该编写单元测试来检查用户名字段是否唯一 @Test void cannotCreateMoreThanOneUserWithTheSameUsername() { new User(username: 'john').save(

这是grails应用程序中的一个简单域类:

class User {
    String username

    static constraints = {
        username unique: true
    }
}
我的问题是:我是否应该编写单元测试来检查用户名字段是否唯一

@Test
void cannotCreateMoreThanOneUserWithTheSameUsername() {
    new User(username: 'john').save()

    def secondUser = new User(username: 'john')
    assert !secondUser.validate()
}
我怀疑是因为:

  • 如果我按照TDD原则编写用户类,那么我应该在实现约束关闭之前编写失败的测试

  • 另一方面,在域中设置唯一约束更像是数据模型配置,而不是真正的逻辑。此外,save和validate方法也在框架中实现


在我看来,单元测试CRUD方法不值得花时间,因为Grails开发人员已经对这些方法进行了全面测试。另一方面,单元测试约束很重要,因为约束可能在应用程序的生命周期中发生变化,并且您希望确保捕捉到这些变化。您永远不知道可能需要修改哪些业务逻辑来支持上述更改。我喜欢使用Spock进行测试,典型的约束测试如下:

@TestFor(User)
class UserSpec extends ConstraintUnitSpec {

  def setup() {
    mockForConstraintsTests(User, [new User(username: 'username', emailAddress: 'email@email.com')])
  }

  @Unroll("test user all constraints #field is #error")
  def "test user all constraints"() {
    when:
    def obj = new User("$field": val)

    then:
    validateConstraints(obj, field, error)

    where:
    error      | field                 | val
    'blank'    | 'username'            | ' '
    'nullable' | 'username'            | null
    'unique'   | 'username'            | 'username'
    'blank'    | 'password'            | ' '
    'nullable' | 'password'            | null
    'maxSize'  | 'password'            | getLongString(65)
    'email'    | 'emailAddress'        | getEmail(false)
    'unique'   | 'emailAddress'        | 'email@email.com'
    'blank'    | 'firstName'           | ' '
    'nullable' | 'firstName'           | null
    'maxSize'  | 'firstName'           | getLongString(51)
    'blank'    | 'lastName'            | ' '
    'nullable' | 'lastName'            | null
    'maxSize'  | 'lastName'            | getLongString(151)
    'nullable' | 'certificationStatus' | null
  }
}
下面是ConstraintUnitSpec基类:

abstract class ConstraintUnitSpec extends Specification {

  String getLongString(Integer length) {
    'a' * length
  }

  String getEmail(Boolean valid) {
    valid ? "test@wbr.com" : "test@w"
  }

  String getUrl(Boolean valid) {
    valid ? "http://www.google.com" : "http:/ww.helloworld.com"
  }

  String getCreditCard(Boolean valid) {
    valid ? "4111111111111111" : "41014"
  }

  void validateConstraints(obj, field, error) {


    def validated = obj.validate()

    if (error && error != 'valid') {
      assert !validated
      assert obj.errors[field]
      assert error == obj.errors[field]
    } else {
      assert !obj.errors[field]
    }
  }
}

这是我从一篇博客文章中学到的技巧。但我现在想不起来了。我会寻找它,如果我找到了,我会确定并链接到它。

我会将您的测试精力集中在可能出错的领域,而不是试图获得100%的覆盖率

考虑到这一点,我避免测试任何简单声明的内容。您没有打破逻辑,任何测试都只是重复声明。很难看出这将如何避免您意外破坏此功能


如果您正在编写处理声明的底层库,那么您应该编写测试。如果没有,请依赖图书馆。当然,如果您不相信库作者能够正确地实现这一点,那么您可以编写测试。这里有一个测试努力与回报的权衡。

经过更多的研究,我想为同一个用户类共享下一个测试样本,并最终回答我自己的问题

@Test
void usernameIsUnique() {
    def mock = new ConstraintsMock()
    User.constraints.delegate = mock
    User.constraints.call()
    assert mock.recordedUsernameUniqueConstraint == true
}

class ConstraintsMock {
    Boolean recordedUsernameUniqueConstraint = null

    def username = { Map m ->
        recordedUsernameUniqueConstraint = m.unique
        assert m.unique
    }
}
这是一个非常幼稚的测试样本。这与其说是一种行为,不如说是一种执行测试,我认为这是一种不好的行为。 但它真的与问题中的测试样本不同吗

第一件事:我们想要测试什么逻辑?约束闭包的真正逻辑是什么?它只是为我们想要配置的每个字段调用gorm的动态方法,并将配置作为参数传递。 那么为什么不在测试中调用这个闭包呢?为什么我要调用save方法?为什么我要调用gorm的validate方法?从这个角度来看,在单元测试中直接调用约束闭包似乎不是个坏主意

另一方面,Config.groovy中的约束闭包和配置闭包有什么区别?我们不测试配置,是吗? 我认为我们不测试配置,因为测试配置就像复制这个配置(重复我们自己)。 更重要的是,这种测试甚至不会增加代码覆盖率,如果今天还有人关心这个指标的话,因为集成或功能测试的第一次运行应该运行所有域的所有约束

最后一件事:在现实生活中,这个测试能够捕捉到什么样的bug

总而言之:在我看来设置诸如“blank”、“nullable”或unique之类的简单约束与应用程序配置非常相似我们不应该测试这部分代码,因为如果这样的测试不仅仅是我们约束定义的副本,它可能只检查框架的逻辑


我为约束编写了许多单元测试。现在,我将在重构过程中删除它们。我将只留下我自己的验证器逻辑的单元测试。

谢谢您有趣的回复。我没问过积垢测试。我询问了验证器设置测试。这样的测试不是重复实现吗?我的意思是,如果您忘记更改代码中的某些内容(比如blank:false),那么您也可以忘记更新测试。有什么区别?如果使用我描述的方法,而忘记添加约束,测试将失败。如果验证失败,上面的测试模式实际上是通过的。这是关键。您正在测试以确保您的域将触发约束(验证)错误。@Gregg这是您从中获得它的博客吗@很可能是科基·班塔姆,这就是诸如此类问题的问题所在。OP不想测试约束。所以他选择了一个让他安心的答案。除了说测试越多越好之外,没有真正对错的答案。@Gregg如果你完全测试了所有东西,我想你会有更少的bug。但代价是什么?因此,最终它总是会归结为一个只有OP才能决定的权衡,除非我们能够准确地量化一系列业务级别的结果,如上市时间、声誉、缺陷成本等。@Gregg问题在于测试代码的维护(我在我的几个项目中几乎测试了所有约束)。问题是关于哲学/理念和最佳实践。