2015-06-01 25 views
13
angezeigt.

Wenn Sie [Theory] zusammen mit [InlineData] verwenden, wird ein Test für jedes bereitgestellte Inline-Datenelement erstellt. Wenn Sie jedoch [MemberData] verwenden, wird es nur als ein Test angezeigt.MemberData-Tests werden als ein Test anstelle von vielen

Gibt es eine Möglichkeit, [MemberData] Tests als mehrere Tests erscheinen zu lassen?

+0

IIRC finden gibt es ein Github Problem abdeckt diese und/oder dort behandelt wurde –

+0

Du dich daran erinnern, die Ausgabe von war? Ich konnte kein Problem finden, das dies beschreibt. – NPadrutt

+0

Sorry konnte nichts finden, was eindeutig passt. Vielleicht hilft Ihnen das Lesen von [this one] (https://github.com/xunit/xunit/issues/7), zu verbalisieren, wonach Sie auf GitHub suchen (z. B. Entdeckung, Vorauflistung von Theorien, Test) Methode vs Testfall). Ich würde auch in die Dokumente schauen, um zu bestimmen, was das beabsichtigte Verhalten für v2 und für v1 backcompat ist (dh das Vorauflisten von Theorien ist ein zweischneidiges Schwert, wenn man Ressourcen pro Testfall hoch/runter drehen muss) –

Antwort

17

Ich habe viel Zeit damit verbracht, dieses Projekt in meinem Projekt herauszufinden. This related Github discussion von @NPadrutt selbst hat sehr geholfen, aber es war immer noch verwirrend.

Die tl; dr, ist dies: [MemberInfo] wird einen einzigen Gruppentest berichten, es sei denn die zur Verfügung gestellten Objekte für jeden Test vollständig serialisiert und deserialisiert von IXunitSerializable Umsetzung sein können.


Hintergrund

Mein eigener Testaufbau war so etwas wie:

public static IEnumerable<object[]> GetClients() 
{ 
    yield return new object[] { new Impl.Client("clientType1") }; 
    yield return new object[] { new Impl.Client("clientType2") }; 
} 

[Theory] 
[MemberData(nameof(GetClients))] 
public void ClientTheory(Impl.Client testClient) 
{ 
    // ... test here 
} 

Der Test zweimal lief, einmal für jedes Objekt aus [MemberData], wie erwartet. Wie @NPadrutt erfahren hat, wurde nur ein Element im Test-Explorer statt zwei angezeigt. Dies liegt daran, dass das angegebene Objekt Impl.Client nicht von beiden Schnittstellen serialisiert werden konnte (mehr dazu später).

In meinem Fall wollte ich Testbelange nicht in meinen Hauptcode bluten. Ich dachte, ich könnte einen dünnen Proxy um meine wahre Klasse schreiben, der den xUnit-Runner glauben lässt, er könnte ihn serialisieren, aber nachdem ich länger damit gekämpft hatte, als ich zugeben wollte, wurde mir klar, dass ich den Teil nicht verstand :

Die Objekte werden nicht nur während der Erkennung serialisiert, um Permutationen zu zählen; Jedes Objekt ist auch deserialized zur Testlaufzeit, während der Test beginnt.

Daher muss jedes Objekt, das Sie mit [MemberData] bereitstellen, eine vollständige Round-Trip (De-) Serialisierung unterstützen. Das scheint mir jetzt offensichtlich, aber ich konnte keine Dokumentation darüber finden, während ich versuchte, es herauszufinden.


Lösung

  • Stellen Sie sicher, jedes Objekt (und jede nicht-primitive es enthalten kann) kann vollständig serialisiert und deserialisiert werden. Die Implementierung von xUnit IXunitSerializable teilt xUnit mit, dass es ein serialisierbares Objekt ist. Wenn Sie, wie in meinem Fall, dem Hauptcode keine Attribute hinzufügen möchten, besteht eine Lösung darin, eine dünne serialisierbare Builder-Klasse zum Testen zu erstellen, die alles darstellen kann, was zum Erstellen der tatsächlichen Klasse benötigt wird.Hier ist der obige Code, nachdem ich es an die Arbeit:

TestClientBuilder

public class TestClientBuilder : IXunitSerializable 
{ 
    private string type; 

    // required for deserializer 
    public TestClientBuilder() 
    { 
    } 

    public TestClientBuilder(string type) 
    { 
     this.type = type; 
    } 

    public Impl.Client Build() 
    { 
     return new Impl.Client(type); 
    } 

    public void Deserialize(IXunitSerializationInfo info) 
    { 
     type = info.GetValue<string>("type"); 
    } 

    public void Serialize(IXunitSerializationInfo info) 
    { 
     info.AddValue("type", type, typeof(string)); 
    } 

    public override string ToString() 
    { 
     return $"Type = {type}"; 
    } 
} 

-Test

public static IEnumerable<object[]> GetClients() 
{ 
    yield return new object[] { new TestClientBuilder("clientType1") }; 
    yield return new object[] { new TestClientBuilder("clientType2") }; 
} 

[Theory] 
[MemberData(nameof(GetClients))] 
private void ClientTheory(TestClientBuilder clientBuilder) 
{ 
    var client = clientBuilder.Build(); 
    // ... test here 
} 

Es ist leicht ärgerlich, dass ich nicht bekommen, das Ziel Objekt wird nicht mehr injiziert, aber es ist nur eine zusätzliche Codezeile, um meinen Builder aufzurufen. Und meine Tests gehen vorbei (und erscheinen zweimal!), Also beschwere ich mich nicht.

+0

In der Tat leicht nervig. Irgendwelche Updates zu besseren Möglichkeiten, dies zu tun? – Strake

+0

@Strake Nicht von meiner Seite. Dies ist die Lösung, die ich verwendet habe und es funktioniert, außer hässlich zu sein. –

6

MemberData kann mit Eigenschaften oder Methoden arbeiten, die IEnumerable von object [] zurückgeben. Sie erhalten ein separates Testergebnis für jede Ausbeute in diesem Szenario sehen:

public class Tests 
{ 
    [Theory] 
    [MemberData("TestCases", MemberType = typeof(TestDataProvider))] 
    public void IsLargerTest(string testName, int a, int b) 
    { 
     Assert.True(b>a); 
    } 
} 

public class TestDataProvider 
{ 
    public static IEnumerable<object[]> TestCases() 
    { 
     yield return new object[] {"case1", 1, 2}; 
     yield return new object[] {"case2", 2, 3}; 
     yield return new object[] {"case3", 3, 4}; 
    } 
} 

aber sobald man Objekte komplexe benutzerdefinierte passieren müssen, egal wie viele Testfälle werden Sie die Testausgabefenster haben werden zeigen, nur ein Test. Dies ist kein ideales Verhalten und in der Tat sehr unpraktisch beim Debuggen, welcher Testfall fehlschlägt. Die Problemumgehung besteht darin, einen eigenen Wrapper zu erstellen, der von IXunitSerializable abgeleitet wird.

public class MemberDataSerializer<T> : IXunitSerializable 
    { 
     public T Object { get; private set; } 

     public MemberDataSerializer() 
     { 
     } 

     public MemberDataSerializer(T objectToSerialize) 
     { 
      Object = objectToSerialize; 
     } 

     public void Deserialize(IXunitSerializationInfo info) 
     { 
      Object = JsonConvert.DeserializeObject<T>(info.GetValue<string>("objValue")); 
     } 

     public void Serialize(IXunitSerializationInfo info) 
     { 
      var json = JsonConvert.SerializeObject(Object); 
      info.AddValue("objValue", json); 
     } 
    } 

Jetzt können Sie Ihre benutzerdefinierten Objekte als Parameter Theorien xUnit und noch sehen/debug sie als unabhängige Ergebnisse im Testläufer Fenster:

public class UnitTest1 
{ 
    [Theory] 
    [MemberData("TestData", MemberType = typeof(TestDataProvider))] 
    public void Test1(string testName, MemberDataSerializer<TestData> testCase) 
    { 
     Assert.Equal(1, testCase.Object.IntProp); 
    } 
} 

public class TestDataProvider 
{ 
    public static IEnumerable<object[]> TestData() 
    { 
     yield return new object[] { "test1", new MemberDataSerializer<TestData>(new TestData { IntProp = 1, StringProp = "hello" }) }; 
     yield return new object[] { "test2", new MemberDataSerializer<TestData>(new TestData { IntProp = 2, StringProp = "Myro" }) };  
    } 
} 

public class TestData 
{ 
    public int IntProp { get; set; } 
    public string StringProp { get; set; } 
} 

Hoffnung, das hilft.

+0

Der Serializer funktioniert großartig. Ich habe es leicht modifiziert, um auch eine 'Description' Eigenschaft zu haben und sie in' ToString() 'zu verwenden. Auf diese Weise können Sie zB "test1" und "test2" im Test Explorer anzeigen lassen. – Stijn

0

In meinem letzten Projekt erleben ich das gleiche Problem und nach einigen Recherchen die Lösung, die ich mit aufkommen ist wie folgt:

Implementieren Sie Ihre individuellen MyTheoryAttribute FactAttribute sich entlang mit MyTheoryDiscoverer IXunitTestCaseDiscoverer und mehr benutzerdefinierten MyTestCases Implementierung TestMethodTestCase Verlängerung und die Implementierung von IXunitTestCase nach Ihren Wünschen. Ihre benutzerdefinierten Testfälle sollten von MyTheoryDiscoverer erkannt und verwendet werden, um Ihre Testfälle mit Aufzählungstheorie in einer für das Xunit-Framework sichtbaren Form zu kapseln, selbst wenn übergebene Werte nicht nativ von Xunit serialisiert werden und IXunitSerializable nicht implementiert wird.

Was ist am wichtigsten es besteht keine Notwendigkeit, Ihren wertvollen Code im Test zu ändern!

Es ist ein bisschen Arbeit zu tun, aber da es bereits von mir gemacht wurde und unter MIT-Lizenz verfügbar ist, fühlen Sie sich frei, es zu benutzen. Es ist Teil von DjvuNet Projekt, das auf GitHub gehostet wird.

Direkter Link auf die entsprechenden Ordner mit xUnit Support-Code ist unten:

DjvuNet test support code

es zu verwenden entweder separate Baugruppe mit diesen Dateien erstellen oder sie direkt in Ihr Testprojekt.

Verwendung ist genau das gleiche wie mit Xunit TheoryAttribute und sowohl ClassDataAttribute und MemberDataAttribute werden unterstützt, d. H.:

[DjvuTheory] 
[ClassData(typeof(DjvuJsonDataSource))] 
public void InfoChunk_Theory(DjvuJsonDocument doc, int index) 
{ 
    // Test code goes here 
} 


[DjvuTheory] 
[MemberData(nameof(BG44TestData))] 
public void ProgressiveDecodeBackground_Theory(BG44DataJson data, long length) 
{ 
    // Test code goes here 
} 

Kredit geht an einen anderen Entwickler als gut, aber leider kann ich auf Github seine Repo nicht