Grails 域标准验证器:我应该测试还是不测试?
Posted
技术标签:
【中文标题】Grails 域标准验证器:我应该测试还是不测试?【英文标题】:Grails domain standard validators: should I test or not? 【发布时间】:2013-10-23 19:44:58 【问题描述】:这是一个 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 原则编写 User 类,那么我应该在实现约束关闭之前编写失败测试。
另一方面,在域中设置唯一约束与其说是真正的逻辑,不如说是一种数据模型配置。更重要的是,save 和 validate 方法都是在框架中实现的。
【问题讨论】:
【参考方案1】:在我看来,单元测试 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]
这是我从博客文章中学到的一种技术。但我现在想不起来了。我会寻找它,如果我找到它,我会确定并链接到它。
【讨论】:
感谢您的有趣回复。我没有询问 CRUD 测试。我询问了验证器设置测试。这样的测试不是重复实现吗?我的意思是,如果您忘记更改代码中的某些内容(让我们说空白:false),您也可能忘记更新您的测试。有什么区别? 如果您使用我描述的方法,并且您忘记添加约束,测试将失败。如果验证失败,上面的测试模式实际上会通过。那是关键。您正在测试以确保您的域将触发约束(验证)错误。 @Gregg 你是从这个博客上得到它的吗? christianoestreich.com/2012/11/… @corky_bantam 很有可能。【参考方案2】:我会将您的测试工作集中在可能出错的区域,而不是试图获得 100% 的覆盖率。
考虑到这一点,我避免测试任何简单的声明。你没有逻辑可以打破,任何测试都只是重复声明。很难看出这将如何使您免于意外破坏此功能。
如果您正在编写处理声明的底层库,那么您应该编写测试。如果没有,请依赖库。当然,如果您不相信库的作者能做到这一点,那么您可以编写测试。这里需要权衡测试工作与奖励。
【讨论】:
这是关于 SO 这类问题的问题。 OP 不想测试约束。所以他选择了能让他安心的答案。没有真正正确的错误答案,只是说测试越多越好。 @Gregg 如果您完全测试了所有内容,我想您的错误会更少。但代价是什么?因此,最终它总是会归结为只有 OP 才能决定的权衡,除非我们能够准确地量化一大堆业务级别的结果,例如上市时间、声誉、错误成本等。跨度> @Gregg 问题在于测试代码的维护(我在几个项目中测试了几乎所有的约束)。问题是关于哲学/理念和最佳实践。【参考方案3】:经过更多研究,我想分享下一个针对同一用户类的测试样本,并最终回答我自己的问题。
@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 中的约束闭包和配置闭包有什么区别?我们不测试配置,是吗? 我认为我们不测试配置,因为配置测试就像此配置的副本(重复我们自己)。 更重要的是,如果今天有人仍然关心这个指标,这种测试甚至不会增加代码覆盖率,因为第一次运行集成或功能测试应该运行所有域的所有约束。
最后一件事:这个测试在现实生活中能够捕捉到什么样的错误?
总结一下:在我看来设置“空白”、“可空”或唯一性等简单约束与应用程序配置非常相似。 我们不应该测试这部分代码,因为如果这样的测试不仅仅是我们的约束定义的抄本,它可能只检查框架的逻辑。
我为约束编写了许多单元测试。现在我在重构期间将它们删除。我将只留下我自己的验证器逻辑的单元测试。
【讨论】:
以上是关于Grails 域标准验证器:我应该测试还是不测试?的主要内容,如果未能解决你的问题,请参考以下文章
在 Intellij Idea 中运行 grails 2.1.3 测试:Spock 测试中出现奇怪错误:无法添加域类 [class x.y.Z]。它不是域
覆盖 dateCreated 以在 Grails 中进行测试