2013-10-15 34 views
2

这是一个简单的域类Grails应用程序: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的原则用户类,那么我应该写执行约束关闭之前失败的测试。

  • 另一方面,在域中设置唯一约束是一种数据模型配置,而不是真正的逻辑。而且,保存和验证方法在框架中实现。

回答

1

我会将您的测试工作集中在可能出错的领域,而不是试图获得100%的覆盖率。有鉴于此,我避免测试任何简单的,声明为。你没有理由打破任何测试只是重复声明。很难看出这将如何避免意外破坏这个功能。

如果您正在编写处理声明的底层库,那么您应该进行writnig测试。如果没有,依靠图书馆。当然,如果你不相信图书馆作者能够做到这一点,那么你可以编写测试。这里有一个测试工作与奖励的交易。

+0

这是关于SO的这类问题的问题。 OP不想测试约束条件。所以他选择了让他对此放心的答案。除了说更好的测试越多,没有真正的错误答案。 – Gregg

+0

@Gregg如果你测试了绝对的一切,我想你会有更少的错误。但费用是多少?所以最终它总是会降低到只有OP可以决定的权衡,除非我们能够准确量化诸如上市时间,声誉,bug成本等一系列业务级别的结果。 –

+0

@ Gregg的问题是维护测试代码(我几乎所有的项目都测试了几乎所有的约束条件)。问题是关于哲学/想法和最佳实践。 – promanski

6

在我看来,单元测试CRUD方法是不值得的,因为Grails开发人员已经完全测试了这些方法。另一方面,单元测试约束非常重要,因为约束可能在应用程序的生命周期中发生变化,并且您希望确保捕获这些更改。您永远不知道可能需要修改哪些业务逻辑来支持所做的更改。我喜欢用斯波克,这和典型的约束测试会是这个样子:

@TestFor(User) 
class UserSpec extends ConstraintUnitSpec { 

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

    @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 protected]' 
    '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 ? "[email protected]" : "[email protected]" 
    } 

    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] 
    } 
    } 
} 

这是一个技术,我从一个博客帖子教训。但我现在不记得它。我会寻找它,如果我找到它,我会确定并链接到它。

+0

感谢您的有趣答复。我没有询问CRUD测试。我问了验证器设置测试。这样的测试不是重复执行吗?我的意思是,如果您忘记更改代码中的某些内容(让我们说空白:false),您也可以忘记更新测试。有什么区别? – promanski

+1

如果使用我描述的方法,并且忘记添加约束,则测试将失败。如果验证失败,上述测试模式实际上会通过。这是关键。您正在测试以确保您的域将触发约束(验证)错误。 – Gregg

+0

@Gregg这是你从中得到的博客吗? http://www.christianoestreich.com/2012/11/domain-constraints-grails-spock-updated/ –

0

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

@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的动态方法,并将配置作为参数传递。所以为什么不直接在测试中调用这个闭包呢?我为什么要调用保存方法?为什么我会称之为gorm的验证方法?从这个角度来看,在单元测试中直接调用约束闭包似乎并不是那么糟糕的主意。

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

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

总结:在我看来设置简单约束如“空白”,“可空”或唯一与应用程序配置非常相似。 我们不应该测试代码的这一部分,因为如果这样的测试不仅仅是我们的约束定义的复制副本,它还可以仅仅检查框架的逻辑。

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

相关问题