Теория JUnit для хэш-кода / равного контракта - PullRequest
21 голосов
/ 08 мая 2009

Следующий класс служит универсальным тестером для контракта 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 = "";
}

Ответы [ 5 ]

8 голосов
/ 08 мая 2009

Следует учитывать одну вещь: проверка соответствия объекта контракту equals должна включать в себя экземпляры других типов. В частности, проблемы могут возникать с экземплярами подкласса или суперкласса. Джошуа Блох дает превосходное объяснение связанных с этим ловушек в Effective Java (я повторно использую ссылку duffymo, поэтому он должен получить за нее должное) - см. Раздел «Транзитивность», включающий классы Point и ColorPoint.

Да, ваша реализация не препятствует тому, чтобы кто-то писал тест, включающий в себя экземпляры подкласса, но поскольку ObjectTest является универсальным классом, создается впечатление, что все точки данных должны исходить из одного класса (класс является испытано). Может быть, лучше вообще удалить параметр типа. Просто пища для размышлений.

5 голосов
/ 08 мая 2009

Джошуа Блох излагает контракт на хэш-код и равен в главе 3 «Эффективной Java» . Похоже, ты многое рассказал. Проверьте документ, чтобы увидеть, пропустил ли я что-нибудь.

0 голосов
/ 04 июня 2011

Метод equalsWorks(Object x, Object y) выполняет тот же тест, что и equalsIsReflexive(Object x). Это должно быть удалено.

Я также считаю, что notEqualsWorks(Object x, Object y) следует удалить, так как это мешает одной теории выполнять другие с точками данных, которые равны, даже при том, что все тестирование касается наличия таких объектов.

Без таких точек данных рефлексивность - единственное, что проверяется.

0 голосов
/ 22 августа 2010

Теория notEqualsWorks (Object x, Object y) неверна: два разных экземпляра все еще могут быть логически равны в соответствии с их методом equals; вы предполагаете, что экземпляры логически отличаются, если они имеют разные ссылки.

Используя ваш собственный пример, приведенный ниже, два различных точки данных (a! = A2), тем не менее, равны, но не проходят тест notEqualsWorks:

@DataPoint
public static String a = "a";
@DataPoint
public static String a2 = new String("a");
0 голосов
/ 01 июля 2010

Может быть, я что-то упускаю, но тест equalsIsSymmetric на самом деле только правильно проверяется, если у вас есть DataPoints с одинаковыми значениями (например, String a = "a"; String a2 = "a";) В противном случае этот тест выполняется только тогда, когда 2 параметра являются одним экземпляром (т.е. equalsIsSymmetric (a, a);). Фактически, вы снова проверяете, соответствуют ли равные «отражающему» требованию, а не симметричному.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...