Krótko mówiąc, kontrakt hashCode, zgodnie z object.hashCode () Javy:
- Kod skrótu nie powinien się zmieniać, chyba że zmieni się coś, co ma wpływ na equals ()
- equals () oznacza, że kody skrótu to ==
Załóżmy, że interesują nas przede wszystkim niezmienne obiekty danych - ich informacje nigdy się nie zmieniają po ich skonstruowaniu, więc zakłada się, że # 1 jest aktualne. To pozostawia # 2: problem polega po prostu na potwierdzeniu, że równa się implikuje kod skrótu ==.
Oczywiście nie możemy przetestować każdego wyobrażalnego obiektu danych, chyba że ten zestaw jest banalnie mały. Jaki jest więc najlepszy sposób napisania testu jednostkowego, który prawdopodobnie wyłapie typowe przypadki?
Ponieważ instancje tej klasy są niezmienne, istnieją ograniczone sposoby tworzenia takiego obiektu; ten test jednostkowy powinien obejmować je wszystkie, jeśli to możliwe. Na początek punktami wejścia są konstruktory, deserializacja i konstruktory podklas (które powinny być zredukowane do problemu wywołania konstruktora).
[Spróbuję odpowiedzieć na swoje pytanie poprzez badania. Dane wejściowe z innych StackOverflowers to pożądany mechanizm bezpieczeństwa w tym procesie.]
[Może to dotyczyć innych języków OO, więc dodaję ten tag.]
źródło
Odpowiedzi:
EqualsVerifier to stosunkowo nowy projekt o otwartym kodzie źródłowym, który bardzo dobrze sprawdza się przy testowaniu umowy równości. Nie ma problemów, które ma EqualsTester z GSBase. Zdecydowanie poleciłbym to.
źródło
Radziłbym zastanowić się, dlaczego / jak może to nigdy nie być prawdą, a następnie napisać kilka testów jednostkowych, które dotyczą takich sytuacji.
Na przykład, powiedzmy, że masz
Set
klasę niestandardową . Dwa zestawy są równe, jeśli zawierają te same elementy, ale możliwe jest, że podstawowe struktury danych dwóch równych zestawów będą się różnić, jeśli te elementy są przechowywane w innej kolejności. Na przykład:MySet s1 = new MySet( new String[]{"Hello", "World"} ); MySet s2 = new MySet( new String[]{"World", "Hello"} ); assertEquals(s1, s2); assertTrue( s1.hashCode()==s2.hashCode() );
W takim przypadku kolejność elementów w zestawach może wpływać na ich hash, w zależności od zaimplementowanego algorytmu haszowania. To jest rodzaj testu, który bym napisał, ponieważ testuje przypadek, w którym wiem, że jakiś algorytm haszujący mógłby wygenerować różne wyniki dla dwóch obiektów, które zdefiniowałem jako równe.
Powinieneś użyć podobnego standardu z własną klasą niestandardową, cokolwiek to jest.
źródło
Warto do tego wykorzystać dodatki junit. Sprawdź klasę EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ możesz ją rozszerzyć i zaimplementować createInstance oraz createNotEqualInstance, to sprawdzi, czy metody equals i hashCode są poprawne.
źródło
Polecam EqualsTester z GSBase. Robi w zasadzie to, co chcesz. Mam z tym jednak dwa (drobne) problemy:
źródło
[W czasie pisania tego tekstu opublikowano trzy inne odpowiedzi.]
Powtarzam, celem mojego pytania jest znalezienie standardowych przypadków testów, które to potwierdzą
hashCode
iequals
zgadzają się ze sobą. Moje podejście do tego pytania polega na wyobrażeniu sobie wspólnych ścieżek, którymi kierują się programiści pisząc omawiane klasy, a mianowicie niezmiennych danych. Na przykład:equals()
bez pisaniahashCode()
. To często oznacza, że równość była definiowana jako równość pól dwóch instancji.hashCode()
bez pisaniaequals()
. Może to oznaczać, że programista szukał wydajniejszego algorytmu mieszającego.W przypadku # 2 wydaje mi się, że problem nie istnieje. Nie utworzono
equals()
żadnych dodatkowych instancji, więc żadne dodatkowe instancje nie muszą mieć równych kodów skrótów. W najgorszym przypadku algorytm wyznaczania wartości skrótu może dawać gorszą wydajność w przypadku map skrótów, co jest poza zakresem tego pytania.W przypadku # 1 standardowy test jednostkowy polega na utworzeniu dwóch wystąpień tego samego obiektu z tymi samymi danymi przekazanymi do konstruktora i sprawdzeniu równych kodów skrótów. A co z fałszywymi alarmami? Możliwe jest wybranie parametrów konstruktora, które po prostu dają równe kody skrótu w mimo to niesprawnym algorytmie. Test jednostkowy, który ma tendencję do unikania takich parametrów, byłby zgodny z duchem tego pytania. Skrótem tutaj jest
equals()
zbadanie kodu źródłowego , przemyślenie i napisanie testu opartego na tym, ale chociaż może to być konieczne w niektórych przypadkach, mogą również istnieć popularne testy, które wychwytują typowe problemy - i takie testy również spełniają ducha tego pytania.Na przykład, jeśli testowana klasa (nazwij ją Data) ma konstruktor, który pobiera String i instancje zbudowane z ciągów, które są
equals()
zwracanymi instancjami, które byłyequals()
, wtedy dobry test prawdopodobnie przetestowałby:new Data("foo")
new Data("foo")
Moglibyśmy nawet sprawdzić kod skrótu
new Data(new String("foo"))
, aby wymusić na String, aby nie był internowany, chociażData.equals()
moim zdaniem jest to bardziej prawdopodobne, że dostarczy poprawny kod skrótu, niż daje poprawny wynik.Odpowiedź Eli Courtwright jest przykładem intensywnego zastanawiania się nad sposobem złamania algorytmu wyznaczania wartości skrótu w oparciu o znajomość
equals
specyfikacji. Przykład specjalnej kolekcji jest dobry, ponieważCollection
czasami pojawiają się pliki utworzone przez użytkownika i są one dość podatne na muckupy w algorytmie haszującym.źródło
To jeden z niewielu przypadków, w których w teście miałbym wiele potwierdzeń. Ponieważ musisz przetestować metodę equals, powinieneś również sprawdzić metodę hashCode w tym samym czasie. Więc w każdym przypadku testowym metody równości sprawdź również kontrakt hashCode.
A one = new A(...); A two = new A(...); assertEquals("These should be equal", one, two); int oneCode = one.hashCode(); assertEquals("HashCodes should be equal", oneCode, two.hashCode()); assertEquals("HashCode should not change", oneCode, one.hashCode());
I oczywiście sprawdzenie dobrego hashCode to kolejne ćwiczenie. Szczerze mówiąc, nie zawracałbym sobie głowy podwójnym sprawdzaniem, aby upewnić się, że hashCode nie zmienia się w tym samym przebiegu, tego rodzaju problem można lepiej rozwiązać, przechwytując go w przeglądzie kodu i pomagając deweloperowi zrozumieć, dlaczego to nie jest dobry sposób do pisania metod hashCode.
źródło
Możesz również użyć czegoś podobnego do http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java, aby przetestować równości i hashCode.
źródło
Jeśli mam klasę
Thing
, tak jak większość innych, piszę klasęThingTest
, która zawiera wszystkie testy jednostkowe dla tej klasy. KażdyThingTest
ma swoją metodępublic static void checkInvariants(final Thing thing) { ... }
a jeśli
Thing
klasa przesłania hashCode i jest równa, to ma metodępublic static void checkInvariants(final Thing thing1, Thing thing2) { ObjectTest.checkInvariants(thing1, thing2); ... invariants that are specific to Thing }
Ta metoda jest odpowiedzialna za sprawdzanie wszystkich niezmienników, które są przeznaczone do przechowywania między dowolnymi parami
Thing
obiektów.ObjectTest
Metodzie delegatów jest odpowiedzialny za sprawdzenie wszystkich niezmienników, że musi posiadać między dowolnymi dwoma obiektami. Ponieważequals
ihashCode
są metodami wszystkich obiektów, metoda ta sprawdza tohashCode
iequals
jest spójna.Następnie mam kilka metod testowych, które tworzą pary
Thing
obiektów i przekazują je docheckInvariants
metody parami . Używam podziału równoważności, aby zdecydować, które pary warto przetestować. Zwykle tworzę każdą parę tak, aby różniła się tylko jednym atrybutem oraz testem, który testuje dwa równoważne obiekty.Czasami mam też
checkInvariants
metodę 3-argumentową , chociaż uważam, że jest to mniej przydatne w przypadku defektów findinf, więc nie robię tego częstoźródło