Grails 单元测试空奇怪
Grails Unit Test Null Strangeness
我在 grails 中有一组基本的单元测试,但我遇到了一些奇怪的、不一致的行为。所以基本上单元测试是针对与我的用户域交互的服务。
问题很严重 - 如果我 运行 完整的测试规范 - 每次都有 4 个测试失败。如果我 运行 他们单独 - 他们通过。这里显而易见的是,在测试之间存在状态 - 但鉴于这是一个单元测试 - 不应该存在(并且我已经通过日志记录验证了这一点)。
这里使用的组件是:
- 用户服务
- 用户域
- 地址域
- UserCommand(可验证)
所以查看规范,我在设置中有以下内容:
User user
def setup() {
println "================ Setting Up User (${User.count()}) ================"
// Set the roles
new Role(authority: "ROLE_ADMIN").save()
new Role(authority: "ROLE_OPERATOR").save()
def role = new Role(authority: "ROLE_USER").save()
def userAddress = new Address()
userAddress.with {
name = "Name"
address1 = "Address 1"
address2 = "Address 2"
townCity = "Town"
postcode = "BT19"
county = "Down"
}
// Create an admin user
user = new User()
user.with {
christianName = "Phil"
surname = "Preston"
email = "p@p.com"
username = "admin"
password = "password123"
phone = ""
skype = ""
address = userAddress
}
println(user.properties)
// Save the test user
if (user.validate()) {
println "Saving"
user.save(flush: true, failOnErrors: true)
UserRole.create(user, role)
}
else {
user.errors.allErrors.eachWithIndex { i, x ->
println "${i} - ${x}"
}
}
assert user
}
而两个简单的测试如下:
void "Test updateUser - changing a user password"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = user.email
skype = user.skype
phone = user.phone
password = "newPassword"
passwordCheck = "newPassword"
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the password should be update - but nothing else"
updated.password == cmd.password
}
void "Test updateUser - changing a user detail"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = "update@update.com"
skype = user.skype
phone = user.phone
password = user.password
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the email should be update - but nothing else"
updated.email == cmd.email
}
(还有其他的-但这都是为了证明问题)
所以奇怪的是:
- 第一次测试通过,第二次失败。
- 如果我交换顺序 - 第一个仍然通过,第二个失败
- 如果我 运行 两个单独 - 他们都会通过
setup()
中的代码每次打印User对象的个数(0)
- 它验证每次创建对象(
assert
和记录)
单元测试失败,因为我正在更新的用户域对象验证失败时抛出异常。所以如下:
def updateUser(UserCommand userCommand) {
assert userCommand
// Get the current
User foundUser = User.findById(userCommand.id)
def wasAdmin = foundUser.admin
def wasOperator = foundUser.operator
// Bind Data
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
foundUser?.address?.with {
name = userCommand.name
address1 = userCommand.address1
address2 = userCommand.address2
townCity = userCommand.townCity
county = userCommand.county
postcode = userCommand.postcode
}
// THIS VALIDATION FAILS ON SECOND TEST
if (foundUser.validate()) {
foundUser.save()
// ... removed
return foundUser.refresh()
}
else {
Helper.copyErrorToCmd(foundUser, userCommand)
log.error("Errors: ${foundUser.errors.allErrors}")
throw new UserServiceException(cmd: userCommand, message: "There were errors updating user")
}
User域对象上的错误基本上是(缩短):
- 'skype': 拒绝值 [null]
- 'phone': 拒绝值 [null]
因此您可以看到我为这些字段设置了空字符串 - 并且转换为 null(Grails 会这样做),这就是原因。然而,问题是:
- 两者都应该失败,并且当 运行 单独
时肯定会失败
- 两个字段在约束
中都被标记为nullable: true
- 在 UserService 中,我使用调试器检查在 运行 两个测试时保存的对象 - phone 和 skype 对于两个测试都是空的,但一个失败了,一个没有't
所以User域对象中的约束如下:
static constraints = {
christianName blank: false
surname blank: false
username blank: false, unique: true, size: 5..20
password blank: false, password: true, minSize: 8
email email: true, blank: false
phone nullable: true
skype nullable: true
address nullable: true
}
我正在使用具有以下约束的 Command 对象:
static constraints = {
importFrom User
importFrom Address
password blank: false, password: true, minSize: 8
passwordCheck(blank: false, password: true, validator: { pwd, uco -> return pwd == uco.password })
}
请注意,我什至还尝试在 UserCommand 约束闭包中添加 Skype 和 phone 字段。
如果我在 setup
中的字段中添加文本,这个问题就会消失,但在实际应用程序中这是不可能的,因为这些字段可以留空。
对于这种不一致是如何发生的任何帮助,我们将不胜感激。
休闲
我添加了一个最小的 GitHub 项目来重现问题:Github Project Recreating Issue。看问题:
./grailsw test-app
请问运行两次考试,第一次通过第二次不通过。到 运行 单独失败的测试 运行:
./gradlew test --tests "org.arkdev.bwmc.accountmission.UserServiceSpec.*Test updateUser - changing a user detail"
你可以看到测试通过。
问题似乎是 skype
和 phone
字段在第一次测试中是 nullable:true,在第二次测试中是 nullable:false(我进入 user.validate()
,然后进入 GrailsDomainClassValidator.java
,在 validate(Object,Errors,boolean)
调用中我可以看到 constrainedProperties
映射(基本上是字段 -> 约束)。这表明 skype 是 NullableConstraint.nullable == true
,在第一次调用设置时,但在第二次测试的设置调用中显示 NullableConstraint.nullable == false
。)。这没有意义。
您不必从技术上测试框架中的约束,但我之前遇到过类似的问题,所以我明白您为什么要这样做。
可以post你的完整测试吗?你在使用@TestFor/@Mock吗?就像检查一样,但我假设你必须有 @Mock 否则你会在创建域 类 时遇到不同的错误? @Mock 是使用约束所必需的(或@GrailsUnitTestMixin afaik)。
你在评论里说"The values in your app can be blank";应该注意的是,这也与可为空的不同。如果它真的可以是空白的,你应该 change/add 它然后 "" 就可以了
您也没有使用 'failOnError: true',因为您打错了。
抱歉,我没有足够的代表来写评论。
第一次通话后
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
在 UserService.updateUser(UserCommand)
中(由特征方法 Test updateUser - changing a user password
调用)静态成员 User.constraints
变为 null
。去搞清楚。我不知道 Grails 是如何工作的,所以也许其他人可以理解它。但对我来说,这似乎不应该发生。也许你在那个调用中不知何故犯了一个错误。
我在 grails 中有一组基本的单元测试,但我遇到了一些奇怪的、不一致的行为。所以基本上单元测试是针对与我的用户域交互的服务。
问题很严重 - 如果我 运行 完整的测试规范 - 每次都有 4 个测试失败。如果我 运行 他们单独 - 他们通过。这里显而易见的是,在测试之间存在状态 - 但鉴于这是一个单元测试 - 不应该存在(并且我已经通过日志记录验证了这一点)。
这里使用的组件是:
- 用户服务
- 用户域
- 地址域
- UserCommand(可验证)
所以查看规范,我在设置中有以下内容:
User user
def setup() {
println "================ Setting Up User (${User.count()}) ================"
// Set the roles
new Role(authority: "ROLE_ADMIN").save()
new Role(authority: "ROLE_OPERATOR").save()
def role = new Role(authority: "ROLE_USER").save()
def userAddress = new Address()
userAddress.with {
name = "Name"
address1 = "Address 1"
address2 = "Address 2"
townCity = "Town"
postcode = "BT19"
county = "Down"
}
// Create an admin user
user = new User()
user.with {
christianName = "Phil"
surname = "Preston"
email = "p@p.com"
username = "admin"
password = "password123"
phone = ""
skype = ""
address = userAddress
}
println(user.properties)
// Save the test user
if (user.validate()) {
println "Saving"
user.save(flush: true, failOnErrors: true)
UserRole.create(user, role)
}
else {
user.errors.allErrors.eachWithIndex { i, x ->
println "${i} - ${x}"
}
}
assert user
}
而两个简单的测试如下:
void "Test updateUser - changing a user password"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = user.email
skype = user.skype
phone = user.phone
password = "newPassword"
passwordCheck = "newPassword"
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the password should be update - but nothing else"
updated.password == cmd.password
}
void "Test updateUser - changing a user detail"() {
given: "An update to the user"
UserCommand cmd = new UserCommand()
cmd.with {
id = user.id
christianName = user.christianName
surname = user.surname
username = user.username
email = "update@update.com"
skype = user.skype
phone = user.phone
password = user.password
isAdmin = user.isAdmin()
isOperator = user.isOperator()
}
when: "attempt to update"
User updated = service.updateUser(cmd)
then: "the email should be update - but nothing else"
updated.email == cmd.email
}
(还有其他的-但这都是为了证明问题)
所以奇怪的是:
- 第一次测试通过,第二次失败。
- 如果我交换顺序 - 第一个仍然通过,第二个失败
- 如果我 运行 两个单独 - 他们都会通过
setup()
中的代码每次打印User对象的个数(0)- 它验证每次创建对象(
assert
和记录)
单元测试失败,因为我正在更新的用户域对象验证失败时抛出异常。所以如下:
def updateUser(UserCommand userCommand) {
assert userCommand
// Get the current
User foundUser = User.findById(userCommand.id)
def wasAdmin = foundUser.admin
def wasOperator = foundUser.operator
// Bind Data
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
foundUser?.address?.with {
name = userCommand.name
address1 = userCommand.address1
address2 = userCommand.address2
townCity = userCommand.townCity
county = userCommand.county
postcode = userCommand.postcode
}
// THIS VALIDATION FAILS ON SECOND TEST
if (foundUser.validate()) {
foundUser.save()
// ... removed
return foundUser.refresh()
}
else {
Helper.copyErrorToCmd(foundUser, userCommand)
log.error("Errors: ${foundUser.errors.allErrors}")
throw new UserServiceException(cmd: userCommand, message: "There were errors updating user")
}
User域对象上的错误基本上是(缩短):
- 'skype': 拒绝值 [null]
- 'phone': 拒绝值 [null]
因此您可以看到我为这些字段设置了空字符串 - 并且转换为 null(Grails 会这样做),这就是原因。然而,问题是:
- 两者都应该失败,并且当 运行 单独 时肯定会失败
- 两个字段在约束 中都被标记为
- 在 UserService 中,我使用调试器检查在 运行 两个测试时保存的对象 - phone 和 skype 对于两个测试都是空的,但一个失败了,一个没有't
nullable: true
所以User域对象中的约束如下:
static constraints = {
christianName blank: false
surname blank: false
username blank: false, unique: true, size: 5..20
password blank: false, password: true, minSize: 8
email email: true, blank: false
phone nullable: true
skype nullable: true
address nullable: true
}
我正在使用具有以下约束的 Command 对象:
static constraints = {
importFrom User
importFrom Address
password blank: false, password: true, minSize: 8
passwordCheck(blank: false, password: true, validator: { pwd, uco -> return pwd == uco.password })
}
请注意,我什至还尝试在 UserCommand 约束闭包中添加 Skype 和 phone 字段。
如果我在 setup
中的字段中添加文本,这个问题就会消失,但在实际应用程序中这是不可能的,因为这些字段可以留空。
对于这种不一致是如何发生的任何帮助,我们将不胜感激。
休闲
我添加了一个最小的 GitHub 项目来重现问题:Github Project Recreating Issue。看问题:
./grailsw test-app
请问运行两次考试,第一次通过第二次不通过。到 运行 单独失败的测试 运行:
./gradlew test --tests "org.arkdev.bwmc.accountmission.UserServiceSpec.*Test updateUser - changing a user detail"
你可以看到测试通过。
问题似乎是 skype
和 phone
字段在第一次测试中是 nullable:true,在第二次测试中是 nullable:false(我进入 user.validate()
,然后进入 GrailsDomainClassValidator.java
,在 validate(Object,Errors,boolean)
调用中我可以看到 constrainedProperties
映射(基本上是字段 -> 约束)。这表明 skype 是 NullableConstraint.nullable == true
,在第一次调用设置时,但在第二次测试的设置调用中显示 NullableConstraint.nullable == false
。)。这没有意义。
您不必从技术上测试框架中的约束,但我之前遇到过类似的问题,所以我明白您为什么要这样做。
可以post你的完整测试吗?你在使用@TestFor/@Mock吗?就像检查一样,但我假设你必须有 @Mock 否则你会在创建域 类 时遇到不同的错误? @Mock 是使用约束所必需的(或@GrailsUnitTestMixin afaik)。
你在评论里说"The values in your app can be blank";应该注意的是,这也与可为空的不同。如果它真的可以是空白的,你应该 change/add 它然后 "" 就可以了
您也没有使用 'failOnError: true',因为您打错了。
抱歉,我没有足够的代表来写评论。
第一次通话后
bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])
在 UserService.updateUser(UserCommand)
中(由特征方法 Test updateUser - changing a user password
调用)静态成员 User.constraints
变为 null
。去搞清楚。我不知道 Grails 是如何工作的,所以也许其他人可以理解它。但对我来说,这似乎不应该发生。也许你在那个调用中不知何故犯了一个错误。