Parametrização de teste em xUnit.net semelhante a NUnit

106

Existe algum meio no framework xUnit.net semelhante aos seguintes recursos do NUnit?

[Test, TestCaseSource("CurrencySamples")]
public void Format_Currency(decimal value, string expected){}

static object[][] CurrencySamples = new object[][]
{
    new object[]{ 0m, "0,00"},
    new object[]{ 0.0004m, "0,00"},
    new object[]{ 5m, "5,00"},
    new object[]{ 5.1m, "5,10"},
    new object[]{ 5.12m, "5,12"},
    new object[]{ 5.1234m, "5,12"},
    new object[]{ 5.1250m, "5,13"}, // round
    new object[]{ 5.1299m, "5,13"}, // round
}

Isso irá gerar 8 testes separados no NUnit GUI

[TestCase((string)null, Result = "1")]
[TestCase("", Result = "1")]
[TestCase(" ", Result = "1")]
[TestCase("1", Result = "2")]
[TestCase(" 1 ", Result = "2")]
public string IncrementDocNumber(string lastNum) { return "some"; }

Isso irá gerar 5 testes separados e comparar automaticamente os resultados ( Assert.Equal()).

[Test]
public void StateTest(
    [Values(1, 10)]
    int input,
    [Values(State.Initial, State.Rejected, State.Stopped)]
    DocumentType docType
){}

Isso irá gerar 6 testes combinatórios. Impagável.

Há alguns anos, experimentei o xUnit e adorei, mas faltava-lhe esses recursos. Não posso viver sem eles. Algo mudou?

UserControl
fonte
Um guia completo que envia objetos complexos como um parâmetro para métodos de teste tipos complexos em teste de unidade
Iman Bahrampour

Respostas:

138

O xUnit oferece uma maneira de executar testes parametrizados por meio de algo chamado teorias de dados . O conceito é equivalente ao encontrado no NUnit, mas a funcionalidade que você tira da caixa não é tão completa.

Aqui está um exemplo:

[Theory]
[InlineData("Foo")]
[InlineData(9)]
[InlineData(true)]
public void Should_be_assigned_different_values(object value)
{
    Assert.NotNull(value);
}

Neste exemplo, o xUnit executará o Should_format_the_currency_value_correctlyteste uma vez a InlineDataAttributecada vez, passando o valor especificado como argumento.

Teorias de dados são um ponto de extensibilidade que você pode usar para criar novas maneiras de executar seus testes parametrizados. A maneira como isso é feito é criando novos atributos que inspecionam e, opcionalmente, atuam sobre os argumentos e o valor de retorno dos métodos de teste.

Você pode encontrar um bom exemplo prático de como teorias de dados do xUnit pode ser estendido em AutoFixture 's Autodata e InlineAutoData teorias.

Enrico Campidoglio
fonte
3
Aparentemente, não é permitido usar literais decimais como parâmetros de atributo.
Sergii Volchkov
1
@RubenBartelink seu link não foi encontrado. Em vez disso, acesse
Ronnie Overby
9
Você precisará do xUnit.net: Extensions (pacote NuGet) ou o [Theory]atributo não estará disponível.
Daniel AA Pelsmaeker
4
Seria ótimo se o framework de teste de unidade .NET mais recomendado tivesse alguma documentação.
Isaac Kleinman
6
O Google diz que suas respostas SÃO a documentação do xUnit.
nathanchere
55

Deixe-me jogar mais uma amostra aqui, apenas no caso de economizar algum tempo para alguém.

[Theory]
[InlineData("goodnight moon", "moon", true)]
[InlineData("hello world", "hi", false)]
public void Contains(string input, string sub, bool expected)
{
    var actual = input.Contains(sub);
    Assert.Equal(expected, actual);
}
Sevenate
fonte
Você esqueceu um colchete de fechamento na 2ª linha?
cs0815
Útil, obrigado :)
Zeek2
21

Em sua primeira solicitação, você pode seguir os exemplos encontrados aqui .

Você pode construir uma classe estática contendo os dados necessários para uma coleção de testes

using System.Collections.Generic;

namespace PropertyDataDrivenTests
{
    public static class DemoPropertyDataSource
    {
        private static readonly List<object[]> _data = new List<object[]>
            {
                new object[] {1, true},
                new object[] {2, false},
                new object[] {-1, false},
                new object[] {0, false}
            };

        public static IEnumerable<object[]> TestData
        {
            get { return _data; }
        }
    }
}

Em seguida, usando o atributo MemberData, defina o teste como tal

public class TestFile1
{
    [Theory]
    [MemberData("TestData", MemberType = typeof(DemoPropertyDataSource))]
    public void SampleTest1(int number, bool expectedResult)
    {
        var sut = new CheckThisNumber(1);
        var result = sut.CheckIfEqual(number);
        Assert.Equal(result, expectedResult);
    }
}

ou se você estiver usando C # 6.0,

[Theory]
[MemberData(nameof(PropertyDataDrivenTests.TestData), MemberType = typeof(DemoPropertyDataSource))]

O primeiro argumento de MemberDataAttribute permite definir o membro a ser usado como fonte de dados, para que você tenha bastante flexibilidade na reutilização.

LewisM
fonte
13

De acordo com este artigo no xUnit, você tem três opções de "parametrização":

  1. InlineData
  2. ClassData
  3. MemberData

Exemplo de InlineData

[Theory]
[InlineData(1, 2)]
[InlineData(-4, -6)]
[InlineData(2, 4)]
public void FooTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

Exemplo de ClassData

public class BarTestData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { 1, 2 };
        yield return new object[] { -4, -6 };
        yield return new object[] { 2, 4 };
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


[Theory]
[ClassData(typeof(BarTestData))]
public void BarTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

Exemplo MemberData

[Theory]
[MemberData(nameof(BazTestData))]
public void BazTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

public static IEnumerable<object[]> BazTestData => new List<object[]>
    {
        new object[] { 1, 2 },
        new object[] { -4, -6 },
        new object[] { 2, 4 },
    };
itim
fonte
12

Encontrei uma biblioteca que produz funcionalidade equivalente ao [Values]atributo do NUnit, chamada Xunit.Combinatorial :

Ele permite que você especifique valores em nível de parâmetro:

[Theory, CombinatorialData]
public void CheckValidAge([CombinatorialValues(5, 18, 21, 25)] int age, 
    bool friendlyOfficer)
{
    // This will run with all combinations:
    // 5  true
    // 18 true
    // 21 true
    // 25 true
    // 5  false
    // 18 false
    // 21 false
    // 25 false
}

Ou você pode implicitamente fazer com que ele descubra o número mínimo de invocações para cobrir todas as combinações possíveis:

[Theory, PairwiseData]
public void CheckValidAge(bool p1, bool p2, bool p3)
{
    // Pairwise generates these 4 test cases:
    // false false false
    // false true  true
    // true  false true
    // true  true  false
}
Adão
fonte
6

Aceitei todas as respostas aqui e, adicionalmente, usei os TheoryData<,>tipos genéricos de XUnit para me fornecer definições de dados simples, fáceis de ler e digitar seguras para o atributo 'MemberData' em meu teste, conforme este exemplo:

/// must be public & static for MemberDataAttr to use
public static TheoryData<int, bool, string> DataForTest1 = new TheoryData<int, bool, string> {
    { 1, true, "First" },
    { 2, false, "Second" },
    { 3, true, "Third" }
};

[Theory(DisplayName = "My First Test"), MemberData(nameof(DataForTest1))]
public void Test1(int valA, bool valB, string valC)
{
    Debug.WriteLine($"Running {nameof(Test1)} with values: {valA}, {valB} & {valC} ");
}

Três execuções de teste observadas no explorador de teste para 'Meu primeiro teste'


NB usando VS2017 (15.3.3), C # 7 e XUnit 2.2.0 para .NET Core

Peter
fonte
Isso é adorável.
Brett Rowberry