Dlaczego JUnit nie zapewnia metod assertNotEquals?


429

Czy ktoś wie, dlaczego zapewnia JUnit 4 assertEquals(foo,bar) ale nie assertNotEqual(foo,bar)metody?

Zapewnia assertNotSame(odpowiada assertSame) i assertFalse(odpowiadaassertTrue ), więc wydaje się dziwne, że nie zawracali sobie tym głowy assertNotEqual.

Nawiasem mówiąc, wiem, że JUnit-addons zapewnia metody, których szukam. Po prostu pytam z ciekawości.


Przynajmniej od JUnit 4.12 jest zapewniony assertNotEquals. junit.org/junit4/javadoc/4.12/org/junit/…
WebF0x

Odpowiedzi:


403

Sugeruję użycie nowszych assertThat()stylów, które mogą łatwo opisywać wszelkiego rodzaju negacje i automatycznie budować opis tego, czego się spodziewałeś i co otrzymałeś, jeśli asercja się nie powiedzie:

assertThat(objectUnderTest, is(not(someOtherObject)));
assertThat(objectUnderTest, not(someOtherObject));
assertThat(objectUnderTest, not(equalTo(someOtherObject)));

Wszystkie trzy opcje są równoważne, wybierz tę, którą uważasz za najbardziej czytelną.

Aby użyć prostych nazw metod (i pozwolić na działanie tej napiętej składni), potrzebujesz następujących importów:

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

134
Doceniam wskaźnik do alternatywnej składni asercji, ale wskazanie gdzie indziej nie odpowiada, dlaczego JUnit nigdy tego nie zapewnił assertNotEquals().
seh

14
@seh: W moim odczuciu pytanie nie dotyczyło zainteresowania historycznego, ale sposobu sformułowania twierdzenia „te dwa obiekty nie są równe” w teście JUnit. Odpowiedziałem na to. Biorąc pod uwagę „dlaczego nie było / nie było assertNotEqual” powiedziałbym, że to dlatego, że jest to wyspecjalizowane twierdzenie, które nie jest potrzebne tak często, assertEqualsa zatem byłoby wyrażane za pomocą ogólnego assertFalse.
Joachim Sauer

21
„wybierz ten, który uważasz za najbardziej czytelny”. Ludzie czytający i piszący testy jednostkowe są programistami. Czy naprawdę uważają to za bardziej czytelne niż assertNotEqual (objectUnderTest, someOtherObject) lub assertFalse (objectUnderTest.equals (someOtherObject))? Fantazyjne interfejsy API nie przekonują mnie - wydaje się, że programistom znacznie trudniej jest odkryć / odkryć, jak z nich korzystać ...
Bacar

@ Bacar: dla niektórych twierdzi to w zasadzie kwestia stylu. Ale assertThatjest o wiele bardziej wyrazisty niż ograniczony zestaw assert*dostępnych metod. Dlatego możesz wyrazić dokładne ograniczenia w jednym wierszu, odczytać je (prawie) jak zdanie w języku angielskim i otrzymać sensowną wiadomość, gdy potwierdzenie nie powiedzie się. To prawda, że ​​nie zawsze jest to funkcja zabójcza, ale gdy zobaczysz ją kilka razy w akcji, zobaczysz, ile to dodaje.
Joachim Sauer

5
@Jachachim Zgadzam się, że assertThatjest bardziej ekspresyjny niż assert*, ale nie sądzę, aby był bardziej ekspresyjny niż wyrażenie java, które można umieścić wewnątrz i na zewnątrz tego assert*wyrażenia (w końcu mogę wyrazić wszystko w kodzie Java). To ogólny problem, z którym zacząłem korzystać z płynnych interfejsów API - każdy jest w zasadzie nowym DSL, którego musisz się nauczyć (kiedy wszyscy już znamy Javę!). Podejrzewam, że Hamcrest jest teraz wystarczająco wszechobecny, że można się spodziewać, że ludzie to poznają. Będę się bawić ...
Bacar

154

Jest assertNotEqualsw JUnit 4.11: https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.11.md#improvements-to-assert-and-assume

import static org.junit.Assert.assertNotEquals;

1
Pamiętaj, że jeden z artefaktów maven jmock (2.6.0) przecieka starą wersję junit-dep, która z kolei ma klasę Assert bez assertNotEquals. Lepiej wyklucz to podczas korzystania z bluszczu.
gkephorus

7
Korzystam z 4.12, ale nadal nie mogę znaleźć assertNotEqual. : s
Mubashar

49

Zastanawiam się tak samo. Interfejs API Assert nie jest bardzo symetryczny; do testowania, czy obiekty są takie same, zapewnia assertSamei assertNotSame.

Oczywiście nie jest za długo pisać:

assertFalse(foo.equals(bar));

Przy takim stwierdzeniu jedyną informacyjną częścią wyniku jest niestety nazwa metody testowej, dlatego komunikat opisowy należy utworzyć osobno:

String msg = "Expected <" + foo + "> to be unequal to <" + bar +">";
assertFalse(msg, foo.equals(bar));

Jest to oczywiście tak nużące, że lepiej rzucić własne assertNotEqual . Na szczęście w przyszłości może być częścią wydania JUnit: JUnit 22


19
Jest to jednak mniej przydatne, ponieważ JUnit nie może wygenerować pomocnego komunikatu o błędzie informującego na przykład o nierównych wartościach foo i bar. Prawdziwa przyczyna niepowodzenia jest ukryta i zamieniona w prosty boolean.
Ben James,

3
W pełni się zgadzam. Zwłaszcza assertFalse potrzebuje odpowiedniego argumentu komunikatu, aby wygenerować dane wyjściowe, aby powiedzieć, co naprawdę poszło źle.
Mikko Maunu

Myślę, że jest to przydatne w testach obecnych tekstów. Thnx
Marouane Gazanayi

Problem z tekstem polega na tym, że będzie on nieaktualny w miarę ewolucji kodu.
Mark Levison,

13

Twierdziłbym, że brak assertNotEqual jest rzeczywiście asymetrią i sprawia, że ​​JUnit jest nieco mniej przyswajalny. Pamiętaj, że jest to dobry przypadek, gdy dodanie metody zmniejszyłoby złożoność interfejsu API, przynajmniej dla mnie: Symetria pomaga rządzić większą przestrzenią. Domyślam się, że przyczyną pominięcia może być zbyt mała liczba osób wzywających tę metodę. Pamiętam jednak czasy, w których nawet twierdzenie False nie istniało; stąd mam pozytywne oczekiwania, że ​​metoda może zostać ostatecznie dodana, biorąc pod uwagę, że nie jest to trudna; chociaż przyznam, że istnieje wiele obejść, nawet eleganckich.


7

Przychodzę na tę imprezę dość późno, ale okazało się, że forma:

static void assertTrue(java.lang.String message, boolean condition) 

może sprawić, że będzie działać w większości przypadków „nie równych”.

int status = doSomething() ; // expected to return 123
assertTrue("doSomething() returned unexpected status", status != 123 ) ;

4
Chociaż to działa, problem polega na tym, że jeśli twierdzenie się nie powiedzie, po prostu powie „Exepcted true, ale will false” lub jakieś inne niejasne stwierdzenie. Byłoby wspaniale, gdyby oczekiwano Nie 123, ale 123.
Stealth Rabbi

6

Pracuję nad JUnit w środowisku java 8, używając jUnit4.12

dla mnie: kompilator nie mógł znaleźć metody assertNotEquals, nawet gdy jej użyłem
import org.junit.Assert;

Więc zmieniłem
assertNotEquals("addb", string);
na
Assert.assertNotEquals("addb", string);

Więc jeśli napotykasz problem związany z assertNotEqualnierozpoznaniem, zmień go na Assert.assertNotEquals(,);ten, aby rozwiązać problem


1
Jest tak, ponieważ metody są statyczne i musisz je importować statycznie. Skorzystaj z tego, import static org.junit.Assert.*;a będziesz mógł używać wszystkich twierdzeń bez nazwy klasy.
Tom Stone,

3

Oczywistym powodem, dla którego ludzie chcieli assertNotEquals (), było porównanie wbudowanych programów bez konieczności wcześniejszej ich konwersji na obiekty o pełnym przepływie:

Pełen przykład:

....
assertThat(1, not(equalTo(Integer.valueOf(winningBidderId))));
....

vs.

assertNotEqual(1, winningBidderId);

Niestety, ponieważ Eclipse domyślnie nie zawiera JUnit 4.11, musisz być wyczerpujący.

Zastrzeżenie Nie sądzę, że „1” musi być zawinięte w Integer.valueOf (), ale ponieważ jestem nowo wrócony z .NET, nie liczę na moją poprawność.


1

Lepiej jest używać Hamcrest do negatywnych asercji niż assertFalse, ponieważ w poprzednim przypadku raport z testu pokaże różnicę w przypadku niepowodzenia asercji.

Jeśli używasz assertFalse, po prostu pojawia się błąd potwierdzenia w raporcie. tzn. utracono informacje o przyczynie awarii.


1

Zwykle robię to, gdy oczekuję, że dwa obiekty będą równe:

assertTrue(obj1.equals(obj2));

i:

assertFalse(obj1.equals(obj2));

kiedy oczekuje się, że będą nierówne. Zdaję sobie sprawę, że nie jest to odpowiedź na twoje pytanie, ale jest najbliższa, jaką mogę uzyskać. Może pomóc innym w poszukiwaniu tego, co mogą zrobić w wersjach JUnit przed JUnit 4.11.


0

Całkowicie zgadzam się z punktem widzenia PO. Assert.assertFalse(expected.equals(actual))nie jest naturalnym sposobem wyrażania nierówności.
Ale chciałbym twierdzić, że dalej niż Assert.assertEquals(), Assert.assertNotEquals()działa, ale nie jest przyjazny dla dokumentu co test faktycznie dochodzi do zrozumienia i / debug jak nie twierdzenie użytkownika.
Tak więc JUnit 4.11 i JUnit 5 zapewniają Assert.assertNotEquals()(Assertions.assertNotEquals() w JUnit 5), ale naprawdę unikam ich używania.

Alternatywnie, aby potwierdzić stan obiektu, ogólnie używam interfejsu API dopasowywania, który łatwo wnika w stan obiektu, który wyraźnie dokumentuje intencję twierdzeń i jest bardzo przyjazny dla użytkownika, aby zrozumieć przyczynę niepowodzenia potwierdzenia.

Oto przykład.
Załóżmy, że mam klasę Animal, którą chcę przetestować, createWithNewNameAndAge()metodę, która tworzy nowy obiekt Animal, zmieniając jego nazwę i wiek, ale zachowując ulubione jedzenie.
Załóżmy, że używam, Assert.assertNotEquals()aby stwierdzić, że oryginał i nowe obiekty są różne.
Oto klasa Animal z wadliwą implementacją createWithNewNameAndAge():

public class Animal {

    private String name;
    private int age;
    private String favoriteFood;

    public Animal(String name, int age, String favoriteFood) {
        this.name = name;
        this.age = age;
        this.favoriteFood = favoriteFood;
    }

    // Flawed implementation : use this.name and this.age to create the 
    // new Animal instead of using the name and age parameters
    public Animal createWithNewNameAndAge(String name, int age) {
        return new Animal(this.name, this.age, this.favoriteFood);
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getFavoriteFood() {
        return favoriteFood;
    }

    @Override
    public String toString() {
        return "Animal [name=" + name + ", age=" + age + ", favoriteFood=" + favoriteFood + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((favoriteFood == null) ? 0 : favoriteFood.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Animal)) return false;

        Animal other = (Animal) obj;
        return age == other.age && favoriteFood.equals(other.favoriteFood) &&
                name.equals(other.name);
    }

}

JUnit 4.11+ (lub JUnit 5) zarówno jako narzędzie do testowania, jak i asercji

@Test
void assertListNotEquals_JUnit_way() {
    Animal scoubi = new Animal("scoubi", 10, "hay");
    Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
    Assert.assertNotEquals(scoubi, littleScoubi);
}

Test kończy się niepowodzeniem zgodnie z oczekiwaniami, ale przyczyna podana programistowi naprawdę nie jest pomocna. Po prostu mówi, że wartości powinny być różne i wyświetlać toString()wynik wywołany na rzeczywistym Animalparametrze:

java.lang.AssertionError: Wartości powinny być inne. Rzeczywiste: zwierzę

[name = scoubi, age = 10, favoriteFood = hay]

at org.junit.Assert.fail (Assert.java:88)

Ok, obiekty nie są równe. Ale gdzie jest problem?
Które pole nie jest poprawnie wycenione w testowanej metodzie? Jeden? Dwa? Wszyscy ?
Aby to odkryć, musisz wykopać createWithNewNameAndAge() implementację / użyć debuggera, podczas gdy testujący interfejs API byłby o wiele bardziej przyjazny, gdyby uczynił dla nas różnicę między tym, czego się oczekuje, a tym, co zostanie uzyskane.


JUnit 4.11 jako test runner i testowy interfejs API Matchera jako narzędzie do asercji

Tutaj ten sam scenariusz testowy, ale wykorzystujący AssertJ (doskonały interfejs API dopasowywania testów) do potwierdzenia Animalstanu:

import org.assertj.core.api.Assertions;

@Test
void assertListNotEquals_AssertJ() {
    Animal scoubi = new Animal("scoubi", 10, "hay");
    Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
    Assertions.assertThat(littleScoubi)
              .extracting(Animal::getName, Animal::getAge, Animal::getFavoriteFood)
              .containsExactly("little scoubi", 1, "hay");
}

Oczywiście test nadal się nie udaje, ale tym razem powód jest jasno określony:

java.lang.AssertionError:

Przy nadziei:

<[„scoubi”, 10, „hay”]>

zawierać dokładnie (i w tej samej kolejności):

<[„little scoubi”, 1, „hay”]>

ale nie znaleziono niektórych elementów:

<[„little scoubi”, 1]>

i innych nie oczekiwano:

<[„scoubi”, 10]>

at junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)

Możemy odczytać, że w przypadku Animal::getName, Animal::getAge, Animal::getFavoriteFoodwartości zwracanego zwierzęcia oczekujemy, że będą miały tę wartość:

"little scoubi", 1, "hay" 

ale mieliśmy te wartości:

"scoubi", 10, "hay"

Wiemy więc, gdzie badać: namei agenie są odpowiednio wyceniani. Dodatkowo fakt podania haywartości w potwierdzeniu Animal::getFavoriteFood()pozwala również na dokładniejsze potwierdzenie zwróconego Animal. Chcemy, aby obiekty nie były takie same dla niektórych właściwości, ale niekoniecznie dla wszystkich właściwości.
Zdecydowanie użycie interfejsu API dopasowywania jest o wiele bardziej przejrzyste i elastyczne.


-1

Spójność Modulo API, dlaczego JUnit nie podał, assertNotEquals()jest tym samym powodem, dla którego JUnit nigdy nie podał metod takich jak

  • assertStringMatchesTheRegex(regex, str) vs. assertStringDoesntMatchTheRegex(regex, str)
  • assertStringBeginsWith(prefix, str) vs. assertStringDoesntBeginWith(prefix, str)

tzn. nie ma końca oferowanie określonych metod asercji dla rzeczy, których możesz chcieć w swojej logice asercji!

Znacznie lepiej, aby zapewnić jak się składać prymitywów testowych equalTo(...), is(...), not(...), regex(...)i niech ci kawałek programista razem zamiast więcej czytelności i rozsądku.


3
z jakiegoś powodu istnieje assertEquals (). Nie musiał, ale tak jest. Pytanie dotyczyło braku symetrii - dlaczego istnieje assertEquals, a nie jej odpowiednik?
foo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.