webentwicklung-frage-antwort-db.com.de

Komplexe Parameter an [Theorie] übergeben

Xunit hat eine nette Funktion : Sie können einen Test mit einem Theory-Attribut erstellen und Daten in InlineData-Attribute einfügen. XUnit generiert viele Tests und testet sie alle.

Ich möchte etwas davon haben, aber die Parameter für meine Methode sind keine 'einfachen Daten' (wie string, int, double), sondern eine Liste meiner Klasse:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
68
zchpit

In XUnit gibt es viele xxxxData-Attribute. Überprüfen Sie beispielsweise das Attribut PropertyData

Sie können eine Eigenschaft implementieren, die IEnumerable<object[]> zurückgibt. Jeder object[], den diese Methode generiert, wird dann als Parameter für einen einzelnen Aufruf der [Theory]-Methode "entpackt".

Eine andere Option ist ClassData, die auf dieselbe Weise funktioniert, die Generatoren jedoch problemlos zwischen den Tests in verschiedenen Klassen/Namespaces gemeinsam nutzen kann. Außerdem werden die Datengeneratoren von den tatsächlichen Testmethoden getrennt.

D.h. diese Beispiele von hier :

PropertyData-Beispiel

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}

ClassData-Beispiel

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}
110
quetzalcoatl

So aktualisieren Sie die Antwort von @ Quetzalcoatl: Das Attribut [PropertyData] wurde durch [MemberData] ersetzt, das den String-Namen einer statischen Methode, eines Felds oder einer Eigenschaft als Argument verwendet, die einen IEnumerable<object[]> zurückgibt. (Ich finde es besonders schön, eine Iterator-Methode zu haben, die tatsächlich berechne Testfälle nacheinander kann und sie bei der Berechnung aufgibt.)

Jedes Element in der vom Enumerator zurückgegebenen Sequenz ist ein object[] und jedes Array muss dieselbe Länge haben. Diese Länge muss die Anzahl der Argumente für Ihren Testfall sein (mit dem Attribut [MemberData] versehen und jedes Element muss denselben Typ wie das entsprechende Element haben Methodenparameter (oder vielleicht können sie konvertierbare Typen sein, ich weiß es nicht.)

(Siehe Versionshinweise für xUnit.net März 2014 und den aktuellen Patch mit Beispielcode .)

29
davidbak

Das Erstellen anonymer Objekt-Arrays ist nicht der einfachste Weg, die Daten zu erstellen. Daher habe ich dieses Muster in meinem Projekt verwendet

Definieren Sie zunächst einige wiederverwendbare, gemeinsam genutzte Klassen

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}

Jetzt sind Ihre individuellen Test- und Member-Daten einfacher zu schreiben und sauberer ...

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, string testDescription, bool expectedResult)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}

Die Eigenschaft Description der Zeichenfolge besteht darin, sich einen Knochen zu werfen, wenn einer Ihrer vielen Testfälle fehlschlägt

7
fiat

Angenommen, wir haben eine komplexe Fahrzeugklasse mit einer Herstellerklasse:

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

Wir werden die Autoklasse füllen und einem Theorie-Test unterziehen.

Erstellen Sie daher eine 'CarClassData'-Klasse, die eine Instanz der Car-Klasse wie folgt zurückgibt:

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

Es ist Zeit, eine Testmethode (CarTest) zu erstellen und das Auto als Parameter zu definieren:

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}

complex type in theory

Viel Glück

3
Iman Bahrampour

Sie können diesen Weg versuchen:

public class TestClass {

    bool isSaturday(DateTime dt)
    {
       string day = dt.DayOfWeek.ToString();
       return (day == "Saturday");
    }

    [Theory]
    [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
    public void test(int i)
    {
       // parse test case
       var input = TestCase.IsSaturdayTestCase[i];
       DateTime dt = (DateTime)input[0];
       bool expected = (bool)input[1];

       // test
       bool result = isSaturday(dt);
       result.Should().Be(expected);
    }   
}

Erstellen Sie eine weitere Klasse für die Testdaten:

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}
2
Sandy_Vu

Für meine Bedürfnisse wollte ich nur eine Reihe von Testbenutzern durchlaufen, aber [ClassData] usw. schienen übertrieben für das, was ich brauchte (da die Liste der Elemente für jeden Test lokalisiert war).

Also habe ich folgendes gemacht, mit einem Array innerhalb des Tests - von außen indiziert:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}

Damit wurde mein Ziel erreicht und gleichzeitig die Absicht des Tests klar. Sie müssen nur die Indizes synchron halten, aber das ist alles.

Sieht in den Ergebnissen gut aus, sie ist zusammenlegbar und Sie können eine bestimmte Instanz erneut ausführen, wenn Sie einen Fehler erhalten:

 enter image description here

0
Simon_Weaver