Spock框架是否支持对象深度比较?

8

如何使用Spock检查深度对象相等性。

假设我们有一个非常简单的测试,比较两个完全相同的人对象。

def "A persons test"() {
    setup:
    def person1 = new Person("Foo", new Address("Bar"))
    def person2 = new Person("Foo", new Address("Bar"))

    expect:
    person1 == person2
}

测试失败了

Condition not satisfied:

person1 == person2
|       |  |
|       |  Person@6bedbc4d
|       false
Person@57af006c

这看起来像一种非常自然的断言相等的方式。

开始使用Spock的主要原因之一是避免编写大量的Hamcrest样板匹配器代码。

4个回答

16

Spock没有内置机制来执行深度对象比较,因为定义对象相等性超出了任何测试框架的范围。您可以做很多事情。

1. 两个类都是 Groovy 类

如果您的类(PersonAddress)都是 Groovy 类,您可以对这两个类使用 @EqualsAndHashCode 注解生成 equalshashCode 方法,例如:

import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor
import spock.lang.Specification

class PersonSpec extends Specification {

    def "a person test"() {
        setup:
        def person1 = new Person("Foo", new Address("Bar"))
        def person2 = new Person("Foo", new Address("Bar"))

        expect:
        person1 == person2
    }

    @TupleConstructor
    @EqualsAndHashCode
    static class Person {
        String name
        Address address
    }

    @TupleConstructor
    @EqualsAndHashCode
    static class Address {
        String city
    }
}

这只是在Groovy中同时实现两种方法的方便替代方法。

2. 这两个类都是Java类

如果你想使用==运算符比较这两个对象,那么你需要在这两个类中定义equalshashCode方法,例如:

public final class Person {

    private final String name;
    private final Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (name != null ? !name.equals(person.name) : person.name != null) return false;
        return address != null ? address.equals(person.address) : person.address == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (address != null ? address.hashCode() : 0);
        return result;
    }

    static class Address {
        private final String city;

        public Address(String city) {
            this.city = city;
        }

        public String getCity() {
            return city;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Address address = (Address) o;

            return city != null ? city.equals(address.city) : address.city == null;
        }

        @Override
        public int hashCode() {
            return city != null ? city.hashCode() : 0;
        }
    }
}

在这个例子中,两种方法都是使用IntelliJ IDEA的“生成equals和hashCode”命令定义的。

3. 我可以使用Lombok!

如果你不想手动定义这两个方法(比如因为你必须记得在修改类字段时更改它们),那么你可以使用Lombok的@EqualsAndHashCode注解来执行类似于Groovy注解的操作,但可应用于任何Java类。

4. 我想保留默认的equalshashCode方法

好吧,在这种情况下,你可以尝试各种方法:

  1. 你可以像这样逐个字段地比较两个对象:

    class PersonSpec extends Specification {
    
        def "a person test"() {
            setup:
            def person1 = new Person("Foo", new Address("Bar"))
            def person2 = new Person("Foo", new Address("Bar"))
    
            expect:
            person1.name == person2.name
    
            and:
            person1.address.city == person2.address.city
        }
    
        @TupleConstructor
        static class Person {
            String name
            Address address
        }
    
        @TupleConstructor
        static class Address {
            String city
        }
    }
    
  2. 您可以尝试使用第三方工具,例如Unitils反射断言

  3. 这可能听起来很奇怪,但您可以比较两个对象的JSON表示形式,类似于:

  4. import groovy.json.JsonOutput
    import groovy.transform.TupleConstructor
    import spock.lang.Specification
    
    class PersonSpec extends Specification {
    
        def "a person test"() {
            setup:
            def person1 = new Person("Foo", new Address("Bar"))
            def person2 = new Person("Foo", new Address("Bar"))
    
            expect:
            new JsonOutput().toJson(person1) == new JsonOutput().toJson(person2)
        }
    
        @TupleConstructor
        static class Person {
            String name
            Address address
        }
    
        @TupleConstructor
        static class Address {
            String city
        }
    }
    
    无论如何,我强烈建议以某种方式定义equalshashCode,并简单地使用==运算符。希望能够帮到你。

无论如何,我强烈建议以某种方式定义equalshashCode,并简单地使用==运算符。希望能够帮到你。


1
在选项1中,您可以只使用@Canonical,而不是@EqualsAndHashCode + @TupleConstructor,以及这些数据类中也应该有的重要的@ToString - Renato
1
对于4.1版本,您可以使用with来使测试更加简洁。http://spockframework.org/spock/docs/1.1/all_in_one.html#specs-as-doc - tim_yates
感谢您提供了一个很好的答案,尽管不是我所希望的;) - Lars KJ

2
您可以利用Groovy简洁的映射比较语法:
person1.properties == person2.properties

这只适用于简单的平面对象,而不是嵌套的对象。您可以像这样进行调整:

person1.properties << ['address': person1.address.properties] == person2.properties << ['address': person2.address.properties]

...但是在那个点上,JSON解决方案更加优雅。


1

我强烈推荐您使用Assertj进行深度断言。以下是一个示例:

def "labels1 should be deeply equal to labels2"() {
        when:
        def labels1 = [new Label("labelA"), new Label("labelB")]
        def labels2 = [new Label("labelB"), new Label("labelA")]

        then:
        assertThat(labels1)
                .usingRecursiveComparison()
                .ignoringCollectionOrder()
                .isEqualTo(labels2)
    }

不要忘记添加Gradle依赖项:

dependencies {
    testImplementation "org.assertj:assertj-core:3.11.1"
}

0

看起来,你需要正确覆盖你的equals和hashcode方法。 在Groovy中,这可以非常容易地完成,你只需要使用@Canonical注释。它不仅提供了equals和hashcode,顺便说一句。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接