Nunit: Suporta parâmetros de tipo genérico para TestCaseSourceAttribute

Criado em 15 nov. 2017  ·  4Comentários  ·  Fonte: nunit/nunit

Estou trabalhando em alguns casos de teste que podem ser melhor projetados usando genéricos. Estou tentando usar TestCaseSource com muitas combinações diferentes de tipos.

O problema que estou encontrando é o uso de reflexão em meus testes para invocar um método de teste genérico que uso para validação de objetos.

[TestFixture]
public class SomeTests
{
    public static IEnumerable<TestCaseData> TestCases()
    {
        yield return
            new TestCaseData(
                typeof(Foo),
                typeof(Bar),
                Generator.Foo,
                Assertions.Foo);
        yield return
            new TestCaseData(
                typeof(Bar),
                typeof(Foo),
                Generator.Bar,
                Assertions.Bar);
    }

    [TestCaseSource(nameof(TestCases))]
    public void CheckSomeTypes(
        Type sourceType,
        Type destinationType,
        TSource source,
        Action<object, object> assert)
    {
        GetType()
            .GetMethod(nameof(CheckSomeTypesImpl), BindingFlags.Public | BindingFlags.Instance)
            .MakeGenericType(new[] { sourceType, destinationType })
            .Invoke(this, new object[] { source, assert });
    }

    public void CheckSomeTypesImpl<TSource, TDestination>(
        TSource source,
        Action<TSource, TDestination> assert)
    {
        var destination = Mapper.Map<TSource, TDestination>(source);

        // Assert are equiviliant
        assert(source, destination);
    }
}

No entanto, acredito que o design a seguir pode agregar valor neste caso:

[TestFixture]
public class SomeTests
{
    public static IEnumerable<TestCaseData> TestCases()
    {
        yield return
            new TestCaseData(
                typeof(Foo),
                typeof(Bar),
                Generator.Foo,
                Assertions.Foo);
        yield return
            new TestCaseData(
                typeof(Bar),
                typeof(Foo),
                Generator.Bar,
                Assertions.Bar);
    }

    [TestCaseSource(nameof(TestCases))]
    public void CheckSomeTypes<TSource, TDestination>(
        TSource source,
        Action<TSource, TDestination> assert)
    {
        var destination = Mapper.Map<TSource, TDestination>(source);

        // Assert are equiviliant
        assert(source, destination);
    }
}

public class Foo
{
    public string Qux { get; set; }
}

public class Bar
{
    public string Qux { get; set; }
}

public static class Generator
{
    public static Foo Foo =>
        new Foo { Qux = "TestTestTest" };
    public static Bar Bar =>
        new Foo { Bar = "AnotherAnotherAnother" };
}

public static class Mapper
{
    // Generic mapper function
    public static TDestination Map<TSource, TDestination>(TSource source)
    {
        if (typeof(TSource) == typeof(Foo))
        {
            return Map((Foo) source);
        }

        if (typeof(TSource) == typeof(Bar))
        {
            return Map((Bar) source);
        }

        throw new NotImplementedException($"No mapping for {typeof(TSource).FullName}.");
    }

    public static Foo Map(Bar bar)
    {
        return new Foo { Qux = bar.Qux };
    }

    public static Bar Map(Foo foo)
    {
        return new Bar { Qux = foo.Qux };
    }
}

public static class Assertions
{
    public static Action<Foo, Bar> Foo =>
        (foo, bar) => Assert.AreEqual(foo.Qux, bar.Qux);
    public static Action<Bar, Foo> Bar =>
        (bar, foo) => Assert.AreEqual(bar.Qux, foo.Qux);
}

Fiz extensões para TestCaseSourceAttribute localmente para dar suporte a isso.

Isso é algo aberto para uma contribuição? Quero esclarecer o design antes de criar um PR.

design enhancement

Comentários muito úteis

Isso poderia funcionar também. @nunit/framework-team, mais alguém achou esse tópico interessante?

Todos 4 comentários

Isso é algo aberto para uma contribuição? Quero esclarecer o design antes de criar um PR.

Muito obrigado por iniciar a conversa e oferecer a ajuda. Também queremos esclarecer o design antes de você criar um PR. =D Provavelmente acabará aberto para uma contribuição em breve!

Mesma pergunta, mas por TestCaseAttribute , não TestCaseSourceAttribute : https://github.com/nunit/nunit/issues/1215
Apenas inferência: https://github.com/nunit/nunit/issues/150

Eu já estou tendo que escrever código abaixo do ideal hoje, então eu absolutamente quero isso.

No construtor TestCaseData , você acha que há uma maneira de diferenciar mais visualmente valores Type que preenchem parâmetros de métodos genéricos de valores Type que preenchem parâmetros de métodos comuns?

Para descoberta: que tal new TestCaseData<T1, T2>(ordinaryArg1, ordinaryArg2)?

Ou isso, o que nos permitiria evitar declarar N novas classes TestCaseData :
TestCaseData.Create<T1, T2>(ordinaryArg1, ordinaryArg2, ordinaryArg3)

Que tal usar um método como TestCaseData.GenericsArgs(params Type[] generics) semelhante ao método Returns.

Isso poderia funcionar também. @nunit/framework-team, mais alguém achou esse tópico interessante?

Esta edição está marcada como uma ideia/design/discussão, mas não tem contribuições há anos, então estou encerrando. Se alguém estiver interessado em trabalhar nessa ideia, poste seu interesse e a equipe considerará a reabertura.

Esta página foi útil?
0 / 5 - 0 avaliações