Próbuję napisać testy jednostkowe dla różnych clone()
operacji w dużym projekcie i zastanawiam się, czy istnieje gdzieś istniejąca klasa, która jest w stanie pobrać dwa obiekty tego samego typu, przeprowadzić głębokie porównanie i powiedzieć, czy one są identyczne czy nie?
java
comparison
equals
Uri
źródło
źródło
Odpowiedzi:
Unitils ma tę funkcjonalność:
źródło
unitils
jest wadliwe właśnie dlatego, że porównuje zmienne, nawet jeśli nie mają one zauważalnego wpływu . Inną (niepożądaną) konsekwencją porównywania zmiennych jest to, że czyste domknięcia (bez własnego stanu) nie są obsługiwane. Ponadto wymaga, aby porównywane obiekty były tego samego typu środowiska wykonawczego. Zakasałem rękawy i stworzyłem własną wersję narzędzia do głębokiego porównywania, które rozwiązuje te problemy.Uwielbiam to pytanie! Głównie dlatego, że prawie nigdy nie ma na nie odpowiedzi ani odpowiedzi źle. To tak, jakby nikt jeszcze tego nie rozgryzł. Dziewicze terytorium :)
Po pierwsze, nawet nie myśl o używaniu
equals
. Kontraktequals
, zgodnie z definicją w javadoc, jest relacją równoważności (zwrotną, symetryczną i przechodnią), a nie relacją równości. W tym celu musiałby być również antysymetryczny. Jedyną implementacjąequals
tego jest (lub kiedykolwiek mogłaby być) prawdziwa relacja równości to ta wjava.lang.Object
. Nawet jeśli kiedyśequals
porównywałeś wszystko na wykresie, ryzyko zerwania umowy jest dość wysokie. Jak zauważył Josh Bloch w Effective Java , kontrakt równych jest bardzo łatwy do zerwania:Poza tym, po co tak naprawdę metoda boolowska? Nie sądzisz, że byłoby miło ująć wszystkie różnice między oryginałem a klonem? Zakładam również, że nie chcesz przejmować się pisaniem / utrzymywaniem kodu porównawczego dla każdego obiektu na wykresie, ale raczej szukasz czegoś, co będzie skalowane wraz ze źródłem, gdy zmienia się w czasie.
Soooo, to, czego naprawdę chcesz, to jakieś narzędzie do porównywania stanów. Sposób implementacji tego narzędzia zależy w rzeczywistości od charakteru modelu domeny i ograniczeń wydajności. Z mojego doświadczenia wynika, że nie ma ogólnej magicznej kuli. I to będzie to powolne przez dużą liczbę iteracji. Ale jeśli chodzi o testowanie kompletności operacji klonowania, wykona to zadanie całkiem dobrze. Dwie najlepsze opcje to serializacja i odbicie.
Niektóre problemy, które napotkasz:
XStream jest dość szybki i w połączeniu z XMLUnit wykona zadanie w zaledwie kilku wierszach kodu. XMLUnit jest fajny, ponieważ może zgłosić wszystkie różnice lub po prostu zatrzymać się na pierwszym, który znajdzie. A jego dane wyjściowe obejmują ścieżkę xpath do różnych węzłów, co jest miłe. Domyślnie nie zezwala na nieuporządkowane kolekcje, ale można je tak skonfigurować. Wstrzyknięcie specjalnego modułu obsługi różnic (nazywanego a
DifferenceListener
modułu ) pozwala określić sposób radzenia sobie z różnicami, w tym ignorowanie kolejności. Jednak gdy tylko zechcesz zrobić cokolwiek poza najprostszym dostosowaniem, napisanie staje się trudne, a szczegóły są zwykle powiązane z określonym obiektem domeny.Osobiście wolę używać refleksji, aby przeglądać wszystkie zadeklarowane pola i analizować każde z nich, śledząc różnice. Słowo ostrzeżenia: nie używaj rekursji, chyba że lubisz wyjątki przepełnienia stosu. Trzymaj rzeczy w zakresie ze stosem (użyj pliku
LinkedList
lub coś). Zwykle ignoruję pola przejściowe i statyczne oraz pomijam pary obiektów, które już porównałem, więc nie kończę w nieskończonych pętlach, jeśli ktoś zdecydował się napisać samoodwołujący się kod (jednak zawsze porównuję prymitywne opakowania bez względu na wszystko , ponieważ te same referencje obiektów są często używane ponownie). Możesz skonfigurować rzeczy z góry, aby ignorować porządkowanie kolekcji i ignorować specjalne typy lub pola, ale ja lubię definiować moje zasady porównywania stanów na samych polach za pomocą adnotacji. Właśnie do tego, IMHO, służyły adnotacje, aby metadane o klasie były dostępne w czasie wykonywania. Coś jak:Myślę, że to naprawdę trudny problem, ale całkowicie możliwy do rozwiązania! A kiedy już masz coś, co działa dla Ciebie, jest to naprawdę bardzo przydatne :)
Więc powodzenia. A jeśli wymyślisz coś, co jest po prostu geniuszem, nie zapomnij o tym!
źródło
Zobacz DeepEquals i DeepHashCode () w java-util: https://github.com/jdereg/java-util
Ta klasa robi dokładnie to, o co prosi pierwotny autor.
źródło
public
powinno mieć zastosowanie do wszystkich typów, a nie tylko do kolekcji / map.Zastąp metodę equals ()
Możesz po prostu nadpisać metodę equals () klasy za pomocą EqualsBuilder.reflectionEquals (), jak wyjaśniono tutaj :
źródło
Wystarczyło zaimplementować porównanie dwóch instancji encji poprawionych przez Hibernate Envers. Zacząłem pisać własne różnice, ale potem znalazłem następujące ramy.
https://github.com/SQiShER/java-object-diff
Możesz porównać dwa obiekty tego samego typu i pokaże zmiany, uzupełnienia i usunięcia. Jeśli nie ma zmian, obiekty są równe (w teorii). Dla metod pobierających, które powinny być ignorowane podczas sprawdzania, są dostępne adnotacje. Ramka ma znacznie szersze zastosowania niż sprawdzanie równości, tj. Używam do generowania dziennika zmian.
Jego działanie jest OK, porównując jednostki JPA, pamiętaj, aby najpierw odłączyć je od zarządzającego podmiotami.
źródło
Używam XStream:
źródło
W AssertJ możesz:
Prawdopodobnie nie będzie działać we wszystkich przypadkach, jednak będzie działać w większej liczbie przypadków, niż myślisz.
Oto, co mówi dokumentacja:
źródło
isEqualToComparingFieldByFieldRecursively
jest teraz przestarzała. UżyjassertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
zamiast tego :)http://www.unitils.org/tutorial-reflectionassert.html
źródło
Hamcrest ma Matcher samePropertyValuesAs . Ale opiera się na Konwencji JavaBeans (używa metod pobierających i ustawiających). Jeżeli obiekty, które mają być porównywane, nie mają metod pobierających i ustawiających dla swoich atrybutów, to nie zadziała.
Fasola użytkownika - z metodami pobierającymi i ustawiającymi
źródło
isFoo
metody odczytu dlaBoolean
właściwości. Istnieje PR, który jest otwarty od 2016 roku, aby to naprawić. github.com/hamcrest/JavaHamcrest/pull/136Jeśli Twoje obiekty implementują Serializable, możesz użyć tego:
źródło
Twój przykład listy połączonej nie jest trudny w obsłudze. Gdy kod przechodzi przez dwa wykresy obiektów, umieszcza odwiedzane obiekty w zestawie lub mapie. Przed przejściem do innego odniesienia do obiektu, ten zestaw jest testowany, aby sprawdzić, czy obiekt już przeszedł. Jeśli tak, nie musisz iść dalej.
Zgadzam się z osobą powyżej, która powiedziała, że użyj LinkedList (jak Stack, ale bez zsynchronizowanych metod, więc jest szybszy). Idealnym rozwiązaniem jest przechodzenie przez wykres obiektu za pomocą stosu, podczas gdy odbicia są używane do uzyskania każdego pola. Napisane raz, to „zewnętrzne” equals () i „zewnętrzne” hashCode () są tym, co powinny wywoływać wszystkie metody equals () i hashCode (). Nigdy więcej nie potrzebujesz metody customer equals ().
Napisałem kawałek kodu, który przechodzi przez kompletny graf obiektów, wymieniony w Google Code. Zobacz json-io (http://code.google.com/p/json-io/). Serializuje graf obiektu Java do formatu JSON i deserializuje z niego. Obsługuje wszystkie obiekty Java, z lub bez publicznych konstruktorów, z możliwością serializacji lub bez możliwości serializacji itp. Ten sam kod przechodzenia będzie podstawą dla zewnętrznej implementacji „equals ()” i zewnętrznej implementacji „hashcode ()”. Przy okazji, JsonReader / JsonWriter (json-io) jest zwykle szybszy niż wbudowany ObjectInputStream / ObjectOutputStream.
Tego JsonReader / JsonWriter można użyć do porównania, ale nie pomoże to z hashcode. Jeśli chcesz mieć uniwersalny hashcode () i equals (), potrzebuje własnego kodu. Może uda mi się to zrobić z ogólnym odwiedzającym wykres. Zobaczymy.
Inne kwestie - pola statyczne - to proste - można je pominąć, ponieważ wszystkie wystąpienia equals () miałyby tę samą wartość dla pól statycznych, ponieważ pola statyczne są współużytkowane przez wszystkie wystąpienia.
Jeśli chodzi o pola przejściowe - będzie to opcja do wyboru. Czasami możesz chcieć, aby stany nieustalone liczyły się, innym razem nie. „Czasami czujesz się jak wariat, czasami nie”.
Wróć do projektu json-io (dla moich innych projektów), a znajdziesz zewnętrzny projekt equals () / hashcode (). Nie mam jeszcze na to nazwy, ale będzie to oczywiste.
źródło
Apache daje ci coś, konwertuje oba obiekty na ciągi porównuje ciągi, ale musisz Override toString ()
Zastąp toString ()
Jeśli wszystkie pola są typami pierwotnymi:
Jeśli masz inne niż pierwotne pola i / lub kolekcje i / lub mapy:
źródło
Myślę, że wiesz o tym, ale teoretycznie powinieneś zawsze nadpisywać .equals, aby zapewnić, że dwa obiekty są naprawdę równe. Oznaczałoby to, że sprawdzają nadpisane metody .equals na swoich członkach.
Właśnie z tego powodu .equals jest zdefiniowane w Object.
Gdyby to było robione konsekwentnie, nie miałbyś problemu.
źródło
Gwarancja zatrzymania tak głębokiego porównania może stanowić problem. Co powinny zrobić następujące osoby? (Jeśli zaimplementujesz taki komparator, będzie to dobry test jednostkowy).
Oto kolejny:
źródło
Using an answer instead of a comment to get a longer limit and better formatting.
Jeśli to jest komentarz, to po co używać sekcji odpowiedzi? Dlatego oznaczyłem to. nie z powodu?
. Ta odpowiedź jest już oznaczona przez kogoś innego, kto nie zostawił komentarza. Właśnie dostałem to w kolejce do recenzji. Mogłem być zły, że powinienem był być bardziej ostrożny.Myślę, że najłatwiejsze rozwiązanie inspirowane rozwiązaniem Ray Hulha jest serializacja obiektu, a następnie dokładne porównanie surowego wyniku.
Serializacja może być albo bajtem, jsonem, xml lub prostym toStringiem itp. ToString wydaje się tańsze. Lombok generuje dla nas darmowe, łatwe do dostosowania pierścienie ToST. Zobacz przykład poniżej.
źródło
Ponieważ Java 7
Objects.deepEquals(Object, Object)
.źródło