【问题标题】:JUnit theory for hashCode/equals contracthashCode/equals 合约的 JUnit 理论
【发布时间】:2009-05-07 23:03:05
【问题描述】:

以下类用作 equals/hashCode 合约的通用测试器。它是本地测试框架的一部分。

  • 你怎么看?
  • 我如何(强)测试这门课?
  • Junit 理论很好用吗?

班级:

@Ignore
@RunWith(Theories.class)
public abstract class ObjectTest {

    // For any non-null reference value x, x.equals(x) should return true
    @Theory
    public void equalsIsReflexive(Object x) {
        assumeThat(x, is(not(equalTo(null))));
        assertThat(x.equals(x), is(true));
    }

    // For any non-null reference values x and y, x.equals(y) 
    // should return true if and only if y.equals(x) returns true.
    @Theory
    public void equalsIsSymmetric(Object x, Object y) {
        assumeThat(x, is(not(equalTo(null))));
        assumeThat(y, is(not(equalTo(null))));
        assumeThat(y.equals(x), is(true));
        assertThat(x.equals(y), is(true));
    }

    // For any non-null reference values x, y, and z, if x.equals(y)
    // returns true and y.equals(z) returns true, then x.equals(z) 
    // should return true.
    @Theory
    public void equalsIsTransitive(Object x, Object y, Object z) {
        assumeThat(x, is(not(equalTo(null))));
        assumeThat(y, is(not(equalTo(null))));
        assumeThat(z, is(not(equalTo(null))));
        assumeThat(x.equals(y) && y.equals(z), is(true));
        assertThat(z.equals(x), is(true));
    }

    // For any non-null reference values x and y, multiple invocations
    // of x.equals(y) consistently return true  or consistently return
    // false, provided no information used in equals comparisons on
    // the objects is modified.
    @Theory
    public void equalsIsConsistent(Object x, Object y) {
        assumeThat(x, is(not(equalTo(null))));
        boolean alwaysTheSame = x.equals(y);

        for (int i = 0; i < 30; i++) {
            assertThat(x.equals(y), is(alwaysTheSame));
        }
    }

    // For any non-null reference value x, x.equals(null) should
    // return false.
    @Theory
    public void equalsReturnFalseOnNull(Object x) {
        assumeThat(x, is(not(equalTo(null))));
        assertThat(x.equals(null), is(false));
    }

    // Whenever it is invoked on the same object more than once 
    // the hashCode() method must consistently return the same 
    // integer.
    @Theory
    public void hashCodeIsSelfConsistent(Object x) {
        assumeThat(x, is(not(equalTo(null))));
        int alwaysTheSame = x.hashCode();

        for (int i = 0; i < 30; i++) {
            assertThat(x.hashCode(), is(alwaysTheSame));
        }
    }

    // If two objects are equal according to the equals(Object) method,
    // then calling the hashCode method on each of the two objects
    // must produce the same integer result.
    @Theory
    public void hashCodeIsConsistentWithEquals(Object x, Object y) {
        assumeThat(x, is(not(equalTo(null))));
        assumeThat(x.equals(y), is(true));
        assertThat(x.hashCode(), is(equalTo(y.hashCode())));
    }

    // Test that x.equals(y) where x and y are the same datapoint 
    // instance works. User must provide datapoints that are not equal.
    @Theory
    public void equalsWorks(Object x, Object y) {
        assumeThat(x, is(not(equalTo(null))));
        assumeThat(x == y, is(true));
        assertThat(x.equals(y), is(true));
    }

    // Test that x.equals(y) where x and y are the same datapoint instance
    // works. User must provide datapoints that are not equal.
    @Theory
    public void notEqualsWorks(Object x, Object y) {
        assumeThat(x, is(not(equalTo(null))));
        assumeThat(x != y, is(true));
        assertThat(x.equals(y), is(false));
    }
}

用法:

import org.junit.experimental.theories.DataPoint;

public class ObjectTestTest extends ObjectTest {

    @DataPoint
    public static String a = "a";
    @DataPoint
    public static String b = "b";
    @DataPoint
    public static String nullString = null;
    @DataPoint
    public static String emptyString = "";
}

【问题讨论】:

  • 如果我没看错,你的 equalsIsSymmetric 方法中的最后一条语句不应该是 assertThat,而不是假设吗?
  • 那么,您正在寻求一个本土解决方案,但是您知道一些开源库来进行这些常见的测试吗? (我还建议可比较和可序列化。)我会对使用这样的框架感兴趣。
  • 没有这样的框架(如我所见)。我可以将此代码贡献给一个开源项目(请参阅下面 Frank 的回答)
  • @ivo:我已经用美元集成了这个类:bitbucket.org/dfa/dollar/src/tip/src/test/java/com/humaorie/…

标签: java unit-testing junit


【解决方案1】:

需要考虑的一件事:测试对象是否符合 equals 契约应该涉及其他类型的实例。特别是,子类或超类的实例可能会出现问题。 Joshua Bloch 对 Effective Java 中的相关陷阱给出了很好的解释(我正在重用 duffymo 的链接,所以他应该得到认可)——请参阅涉及 Point 和 ColorPoint 类的传递性部分。

的确,您的实现不会阻止某人编写涉及子类实例的测试,但因为 ObjectTest 是一个泛型类,它给人的印象是所有数据点都应该来自一个类(该类是测试)。完全删除类型参数可能会更好。值得深思。

【讨论】:

  • 确实!谢谢,我正在删除类型参数 T。
【解决方案2】:

Joshua Bloch 在chapter 3 of "Effective Java" 中列出了哈希码和等于的合约。看起来你涵盖了很多。检查文档,看看我是否遗漏了什么。

【讨论】:

  • Object的javadoc也很详细
【解决方案3】:

也许我遗漏了一些东西,但实际上只有在必须使用具有相同值的 DataPoints 时才能正确测试 equalsIsSymmetric 测试(例如 String a = "a"; String a2 = "a";) 否则,仅当 2 个参数是一个实例时才进行此测试(即 equalsIsSymmetric(a, a);)。事实上,你再次测试 equals 是否遵守“反射”要求而不是对称要求。

【讨论】:

  • 因此测试有assumeThat(y.equals(x), is(true))
  • 是的,但在当前设置中,它无法创建一个 'x' 和一个 'y' 来保存 x!= y 和 x.equals(y),因为 notEqualsWorks 测试将在这种情况下失败。所以 equalsIsSymmetric 测试只对 x == y 的 x 和 y 执行。
  • 是的。假设上述设置,JUnit 将执行:equalsIsSymmetric(a, a) 和 equalsIsSymmetric(b, b)。对吗?
【解决方案4】:

notEqualsWorks(Object x, Object y) 理论是错误的:两个不同的实例根据它们的 equals 方法仍然可能在逻辑上相等;如果它们是不同的引用,您假设实例在逻辑上是不同的。

使用上面您自己的示例,下面的两个不同数据点 (a != a2) 仍然相等,但未通过 notEqualsWorks 测试:

@DataPoint
public static String a = "a";
@DataPoint
public static String a2 = new String("a");

【讨论】:

  • true 但您应该注意该理论具有以下要求:“用户必须提供不相等的数据点”。
【解决方案5】:

equalsWorks(Object x, Object y) 方法与equalsIsReflexive(Object x) 进行相同的测试。它应该被删除。

我还认为应该删除notEqualsWorks(Object x, Object y),因为它会阻止一个人用相等的数据点做其他理论,即使整个测试都是关于拥有这样的对象。

如果没有这些数据点,则只能测试自反性。

【讨论】:

    猜你喜欢
    • 2011-07-09
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-31
    相关资源
    最近更新 更多