简体   繁体   English

Grails单元测试无效性

[英]Grails Unit Test Null Strangeness

I have a set of basic unit tests in grails and I am getting some strange, inconsistent behaviour. 我在grails中有一组基本的单元测试,并且得到了一些奇怪的,不一致的行为。 So basically the unit tests are for a Service for interacting with my User Domain. 因此,基本上,单元测试用于与我的用户域进行交互的服务。

The issue is at a high level - If I run the full Spec of tests - 4 tests fail each time. 问题出在高水平-如果我运行完整的测试规范-每次4个测试都会失败。 If I run them individually - they pass. 如果我单独运行它们-它们会通过。 The obvious thing here is that there is state being held between tests - but given this is a unit test - there shouldn't be (and I have verified that with logging). 这里很明显的事情是,测试之间保持着状态-但是鉴于这是一个单元测试-不应存在(并且我已经通过记录验证了这一点)。

The components in play here are: 这里发挥的作用是:

  • UserService UserService
  • User Domain 用户域
  • Address Domain 地址域
  • UserCommand (Verifiable) UserCommand(可验证)

So looking at the Spec I have the following in setup: 因此,在查看规格时,我在设置中具有以下内容:

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
}

And the two simple tests are as follows: 两个简单的测试如下:

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
}

(There are others - but this is all to demonstrate the problem) (还有其他人-但这全都可以证明问题所在)

So the strangeness is as follows: 因此,奇怪之处如下:

  • The first test passes, the second one fails. 第一次测试通过,第二次失败。
  • If I swap the order - the first one still passes, second fails 如果我调换订单-第一个仍然通过,第二个失败
  • If I run both individually - they both will pass 如果我两个人都跑-他们都会通过
  • The code in setup() prints the number of User objects (0) each time setup()的代码每次都会打印用户对象(0)的数量
  • It verifies the object is created each time ( assert and logging) 它验证每次创建对象( assert和记录)

The unit test fails because I throw an exception when validation fails forthe User domain object I am udating. 单元测试失败,因为当我要验证的用户域对象的验证失败时,我抛出异常。 So the following: 因此,以下内容:

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")
    }

The errors on the User domain object are basically (shortened): 基本上(缩短)了用户域对象上的错误:

  • 'skype': rejected value [null] 'skype':拒绝值[null]
  • 'phone': rejected value [null] “电话”:拒绝值[null]

So you can see that I have empty strings for these fields - and with conversion to null (as Grails will do), this is the reason. 因此,您可以看到我在这些字段中使用了空字符串-并转换为null(如Grails所做的那样),这就是原因。 However the issue with that is: 但是,问题在于:

  • It should fail for both, and certainly fail when run individually 两者都应该失败,并且当单独运行时肯定会失败
  • Both fields are marked as nullable: true in the constraints 两个字段都标记为nullable: true约束中为nullable: true
  • In UserService I have used the debugger to check the objects that are being saved when running both tests - the phone and skype are null for both tests, yet one fails and one doesn't 在UserService中,我使用调试器检查了运行两个测试时正在保存的对象-电话和skype在两个测试中均为null,但一个失败而一个没有

So the constraints in the User domain object are as follows: 因此,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
}

And I am using a Command object which has the following constraints: 我正在使用具有以下约束的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 })
}

Note I have even tried adding the Skype and phone fields in the UserCommand constraints closure as well. 注意,我什至尝试在UserCommand约束闭包中添加Skype和phone字段。

This issue goes away if I add text to the fields in setup , but thats not going to possible in the actual app as these fields can be left blank. 如果我在setup的字段中添加文本,此问题就消失了,但是在实际应用中这不可能,因为这些字段可以留空。

Any help on how this inconsistency happens would be greatly appreciated. 任何有关这种不一致情况如何发生的帮助将不胜感激。

RECREATION 娱乐

I have added a minimal GitHub project which recreates the issue: Github Project Recreating Issue . 我添加了一个最小的GitHub项目,它重新创建了该问题: Github Project Recreating Issue To see the problem: 要查看问题:

./grailsw test-app

Will run the two test, first passes second fails. 将运行两次测试,第一次通过第二次失败。 To run the failed test individually run: 要单独运行失败的测试,请运行:

./gradlew test --tests "org.arkdev.bwmc.accountmission.UserServiceSpec.*Test updateUser - changing a user detail"

And you can see the test pass. 您可以看到测试通过。

The issue seems to be that the skype and phone fields are nullable:true for the first test, and nullable:false on the second test (I step into user.validate() , and step into the GrailsDomainClassValidator.java , in the validate(Object,Errors,boolean) call I can see the constrainedProperties Map (which is basically field -> constraints). This shows that skype is NullableConstraint.nullable == true , on the first call of setup, but is showing that NullableConstraint.nullable == false on the setup call for the second test.). 这个问题似乎是在skypephone领域可以为空:第一次测试真实的,可为空:在第二次测试(我踏入假user.validate()并步入GrailsDomainClassValidator.java ,在validate(Object,Errors,boolean)调用我可以看到constrainedProperties Map(基本上是字段->约束),这表明在第一次调用setup时,Skype为NullableConstraint.nullable == true ,但显示NullableConstraint.nullable == false在第二次测试的设置调用中为NullableConstraint.nullable == false 。)。 This makes no sense. 这是没有道理的。

You shouldn't have to test the constraints in the framework technically but I've had similar issues before so I can see why you would want to. 您不必从技术上测试框架中的约束,但之前我遇到过类似的问题,因此我可以了解您为什么要这样做。

Is it possible to post your full test? 是否可以发布您的完整测试? Are you using @TestFor/@Mock at all? 您是否正在使用@ TestFor / @ Mock? Just as a check, but I assume you must have @Mock otherwise you would get a different error creating the domain classes? 就像检查一样,但是我认为您必须具有@Mock,否则创建域类会遇到其他错误? @Mock is required for the constraints to be used (or @GrailsUnitTestMixin afaik). 使用约束需要@Mock(或@GrailsUnitTestMixin afaik)。

You say in your comment "The values in your app can be blank"; 您在评论中说“应用程序中的值可以为空”; it should be noted that this is not the same as nullable as well. 应该注意的是,这与可空值也不相同。 If it really can be blank you should change/add it and then "" will work 如果确实可以空白,则应更改/添加它,然后“”将起作用

You also are not using 'failOnError: true' as you have typoed it. 您也没有使用'failOnError:true',因为已经输入了错误。

Sorry I don't have enough rep to write as a comment. 抱歉,我没有足够的代表来撰写评论。

After the first call 第一次通话后

bindData(foundUser, userCommand, ['class', 'operator', 'admin', 'address'])

in UserService.updateUser(UserCommand) (called by feature method Test updateUser - changing a user password ) the static member User.constraints becomes null . UserService.updateUser(UserCommand) (通过功能方法Test updateUser - changing a user password调用)中,静态成员User.constraints变为null Go figure. 去搞清楚。 I have no idea whatsoever how Grails works, so maybe someone else can make sense of it. 我不知道Grails是如何工作的,所以也许其他人也可以理解。 But to me it seems that this should not happen. 但是在我看来,这不应该发生。 Maybe you are somehow making a mistake in that call. 也许您以某种方式在通话中犯了一个错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM