2017-12-31 46 views
1

Geltendmachung habe ich eine Arbeits hamcrest Behauptung:Eigenschaften auf Listenelemente mit assertJ

assertThat(mylist, contains(
    containsString("15"), 
    containsString("217"))); 

Das gewünschte Verhalten ist:

  • mylist == asList("Abcd15", "217aB") => Erfolg
  • myList == asList("Abcd15", "218") => Ausfall

Wie kann ich diesen Ausdruck zu assertJ migrieren.

assertThat(mylist.get(0)).contains("15"); 
assertThat(mylist.get(1)).contains("217"); 

Aber das sind Behauptungen über die Listenelemente, nicht auf der Liste

: Natürlich gibt es naive Lösungen, wie, wie dies auf dem ersten und dem zweiten Wert zu behaupten. Das Versuchen von Behauptungen in der Liste beschränkt mich auf sehr generische Funktionen. Vielleicht könnte es nur mit einer benutzerdefinierten Behauptung gelöst werden, so etwas wie die folgenden wäre schön:

assertThat(mylist).elements() 
    .next().contains("15") 
    .next().contains("217") 

Aber bevor ich eine benutzerdefinierte Assertion schreiben, würde mich interessieren, wie andere würde dieses Problem lösen?

Edit: Eine zusätzliche nicht-funktionale Anforderung ist, dass der Test leicht durch zusätzliche Constraints erweiterbar sein sollte. In Hamcrest ist es ziemlich einfach, zusätzliche Beschränkungen auszudrücken, z.

assertThat(mylist, contains(
    emptyString(),          //additional element 
    allOf(containsString("08"), containsString("15")), //extended constraint 
    containsString("217")));       // unchanged 

Tests abhängig von der Listenindex sein wird für dieses Beispiel werden neu nummeriert müssen, Tests einen benutzerdefinierten Zustand verwendet, wird die komplette Bedingung (beachten Sie, dass die Einschränkungen in allOf sind nicht beschränkt auf Teilkette überprüft) neu schreiben müssen.

+0

Ist das Ihr wirklicher Anwendungsfall, oder möchten Sie beispielsweise überprüfen, dass zum Beispiel eine Liste von Benutzern Benutzer mit den Namen "John" und "Jack" und mit dem Alter von 25 und 45 hat? –

+0

Der wirkliche Anwendungsfall ist ein Quellcode-Generator. Jeder Eintrag der Liste ist ein generiertes Quellcode-Snippet. Der Quellcode wird nicht analysiert (nur auf die Festplatte geschrieben), aber es gibt Szenarien, in denen der Quellcode bestimmte Muster enthalten sollte oder nicht. Eine Lösung zum Zerlegen von Objekten würde das Problem nicht lösen. – CoronA

Antwort

1

Der nächstgelegene ich gefunden habe, ist eine „ContainsSubstring“ Zustand zu schreiben, und eine statische Methode zu erstellen, und verwenden Sie

assertThat(list).has(containsSubstring("15", atIndex(0))) 
       .has(containsSubstring("217", atIndex(1))); 

Aber vielleicht sollten Sie einfach eine Schleife schreiben:

List<String> list = ...; 
List<String> expectedSubstrings = Arrays.asList("15", "217"); 
for (int i = 0; i < list.size(); i++) { 
    assertThat(list.get(i)).contains(expectedSubstrings.get(i)); 
} 

Oder um einen parametrisierten Test zu schreiben, so dass jedes Element von JUnit selbst auf jeder Teilkette getestet wird.

+0

Das Optimum wäre, dass das Hinzufügen eines dritten Elements an der ersten Position nicht bedeutet, andere Teile des Programms zu ändern. Neben der naiven Version (auch auf Indizes basierend) ist meine Post flexibler, da sie mehrere Einschränkungen für ein Listenelement erlaubt. – CoronA

1

In der Tat müssen Sie Ihre eigenen Condition in assertj für die Überprüfung der Sammlung, die die Teilzeichenfolgen in Reihenfolge enthält, implementieren. zum Beispiel:

assertThat(items).has(containsExactly(
    stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new) 
)); 

Was ist Ansatz habe ich gewählt, um Ihre Anforderungen zu erfüllen? einen Vertrag Testfall schreiben, und setzt dann das Merkmal, dass die assertj gegeben nicht, hier ist mein Testfall für die hamcrest contains(containsString(...)) assertj containsExactly wie unten anpassen:

import org.assertj.core.api.Assertions; 
import org.assertj.core.api.Condition; 
import org.hamcrest.Matchers; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.junit.runners.Parameterized; 
import org.junit.runners.Parameterized.Parameters; 

import java.util.Collection; 
import java.util.List; 

import static java.util.Arrays.asList; 
import static java.util.Arrays.stream; 
import static java.util.stream.Collectors.toList; 
import static org.hamcrest.Matchers.contains; 
import static org.hamcrest.Matchers.containsString; 
import static org.junit.Assert.assertThat; 

@RunWith(Parameterized.class) 
public class MatchersTest { 
    private final SubstringExpectation expectation; 

    public MatchersTest(SubstringExpectation expectation) { 
     this.expectation = expectation; 
    } 

    @Parameters 
    public static List<SubstringExpectation> parameters() { 
     return asList(MatchersTest::hamcrest, MatchersTest::assertj); 
    } 

    private static void assertj(Collection<? extends String> items, String... subItems) { 
     Assertions.assertThat(items).has(containsExactly(stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new))); 
    } 

    private static Condition<String> containsSubstring(String substring) { 
     return new Condition<>(s -> s.contains(substring), "contains substring: \"%s\"", substring); 
    } 

    @SuppressWarnings("unchecked") 
    private static <C extends Condition<? super T>, T extends Iterable<? extends E>, E> C containsExactly(Condition<E>... conditions) { 
     return (C) new Condition<T>("contains exactly:" + stream(conditions).map(it -> it.toString()).collect(toList())) { 
      @Override 
      public boolean matches(T items) { 
       int size = 0; 
       for (E item : items) { 
        if (!matches(item, size++)) return false; 
       } 
       return size == conditions.length; 
      } 

      private boolean matches(E item, int i) { 
       return i < conditions.length && conditions[i].matches(item); 
      } 
     }; 
    } 

    private static void hamcrest(Collection<? extends String> items, String... subItems) { 
     assertThat(items, contains(stream(subItems).map(Matchers::containsString).collect(toList()))); 
    } 

    @Test 
    public void matchAll() { 
     expectation.checking(asList("foo", "bar"), "foo", "bar"); 
    } 


    @Test 
    public void matchAllContainingSubSequence() { 
     expectation.checking(asList("foo", "bar"), "fo", "ba"); 
    } 

    @Test 
    public void matchPartlyContainingSubSequence() { 
     try { 
      expectation.checking(asList("foo", "bar"), "fo"); 
      fail(); 
     } catch (AssertionError expected) { 
      assertThat(expected.getMessage(), containsString("\"bar\"")); 
     } 
    } 

    @Test 
    public void matchAgainstWithManySubstrings() { 
     try { 
      expectation.checking(asList("foo", "bar"), "fo", "ba", "<many>"); 
      fail(); 
     } catch (AssertionError expected) { 
      assertThat(expected.getMessage(), containsString("<many>")); 
     } 
    } 

    private void fail() { 
     throw new IllegalStateException("should failed"); 
    } 

    interface SubstringExpectation { 
     void checking(Collection<? extends String> items, String... subItems); 
    } 
} 

Sie jedoch nach unten verwenden verkettet Condition s eher als die assertj fließend api, so schlage ich vor, Sie versuchen, stattdessen die hamcrest verwenden. Mit anderen Worten, wenn Sie diesen Stil in assertj verwenden, müssen Sie viele Condition s schreiben oder hamcrest Matcher s an assertj Condition anpassen.

+0

Dieser Ansatz ist in Ordnung für einfache Teilstring-Prüfungen (wie in meinem Beispiel). Aber der Test wird wahrscheinlich unrobust, wenn zusätzliche Prüfungen hinzugefügt werden. Ich werde meine Frage aktualisieren, um auf die Robustheitskriterien hinzuweisen. – CoronA

+0

@CoronA Hallo, bitte sehen Sie meine bearbeitete Antwort. –

+0

Ich werde auf weitere Antworten warten, aber in der Tat scheint hamcrest in diesem Szenario flexibler zu sein. – CoronA

1

Für diese Art von Behauptungen Hamcrest ist AssertJ überlegen, können Sie Hamcrest mit Bedingungen nachahmen, aber Sie müssen sie schreiben, da es keine bereitgestellte out of the box in AssertJ (assertJ Philosophie ist nicht mit Hamcrest zu diesem Aspekt konkurrieren).

Im nächsten AssertJ Version (in Kürze veröffentlicht werden!), Können Sie hamcrest Matcher wiederzuverwenden AssertJ Bedingungen, beispielsweise zu bauen:

Condition<String> containing123 = new HamcrestCondition<>(containsString("123")); 

// assertions succeed 
assertThat("abc123").is(containing123); 
assertThat("def456").isNot(containing123); 

Als abschließende Bemerkung, dieser Vorschlag ...

assertThat(mylist).elements() 
        .next().contains("15") 
        .next().contains("217") 

... leider kann wegen Generika Einschränkung nicht, obwohl Sie wissen, dass Sie eine Liste des String, Java Generics nicht stark genug ist, um eine bestimmte Art (StringAssert) in Abhängigkeit von einem anderen (String) zu wählen Das heißt, Sie ca n führen Sie nur die Assertion Object auf den Elementen durch, aber nicht die Assertion String.

+0

Meine aktuelle Wegbeschreibung versucht 'sufficient' anstelle von' Condition' zu verwenden, da der 'Consumer ' mehr als eine Eigenschaft geltend machen kann, was der obigen Flexibilität dient. – CoronA

+0

Dennoch bestätige ich Ihre Aussage über die Beschränkung von Generika, aber ich würde vorschlagen, diese Einschränkung zu brechen. Gibt es einen Grund, warum 'assertThat (mylist) .first(). As (StringAsssert.class) .contains ("15") 'nicht bereits implementiert wurde? Dies würde für mein Szenario (das Iterieren über eine Liste) nicht helfen, aber es würde in anderen Tests von mir ziemlich hilfreich sein. – CoronA

+0

Was derzeit implementiert ist: 'assertThat (mylist) .first(). AsString(). StartsWith (" prefix ");', wir dachten, andere 'asXxx'-Methoden hinzuzufügen, aber das würde die API zu sehr durcheinander bringen. Wir haben diese Syntax 'assertThat (list, StringAssert.class) .first(). StartsWith ("prefix"); '. Nachdem ich gesagt habe, dass ich über Ihren Vorschlag mit 'as (Assert class) nachdenken könnte, da es leicht zu entdecken ist, besteht ein Nachteil darin, dass' as' bereits für die Beschreibung von Assertions verwendet wird. –

Verwandte Themen