Nunit: Admite parámetros de tipo genérico para TestCaseSourceAttribute

Creado en 15 nov. 2017  ·  4Comentarios  ·  Fuente: nunit/nunit

Estoy trabajando en algunos casos de prueba que pueden diseñarse mejor usando genéricos. Estoy tratando de usar TestCaseSource con muchas combinaciones diferentes de tipos.

El problema que encuentro es el uso de la reflexión en mis pruebas para invocar un método de prueba genérico que uso para la validación 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);
    }
}

Sin embargo, creo que el siguiente diseño puede agregar valor en este 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);
}

He hecho extensiones a TestCaseSourceAttribute localmente para admitir esto.

¿Es esto algo abierto para una contribución? Primero quiero aclarar el diseño antes de crear un PR.

design enhancement

Comentario más útil

Eso también podría funcionar. @nunit/framework-team, ¿alguien más encuentra este tema interesante?

Todos 4 comentarios

¿Es esto algo abierto para una contribución? Primero quiero aclarar el diseño antes de crear un PR.

Muchas gracias por iniciar la conversación y ofrecer la ayuda. También queremos aclarar el diseño antes de crear un PR. =D ¡Probablemente terminará abierto para una contribución pronto!

La misma pregunta pero por TestCaseAttribute , no TestCaseSourceAttribute : https://github.com/nunit/nunit/issues/1215
Solo inferencia: https://github.com/nunit/nunit/issues/150

Ya tengo que escribir un código subóptimo hoy, así que absolutamente quiero esto.

En el constructor TestCaseData , ¿cree que hay una manera de diferenciar más visualmente los valores Type que completan los parámetros del método genérico de los valores Type que completan los parámetros del método ordinario?

Para la visibilidad: ¿qué pasa con new TestCaseData<T1, T2>(ordinaryArg1, ordinaryArg2)?

O esto, que nos permitiría evitar declarar N nuevas clases TestCaseData :
TestCaseData.Create<T1, T2>(ordinaryArg1, ordinaryArg2, ordinaryArg3)

¿Qué tal si usamos un método como TestCaseData.GenericsArgs(params Type[] generics) similar al método Returns?

Eso también podría funcionar. @nunit/framework-team, ¿alguien más encuentra este tema interesante?

Este problema está marcado como una idea/diseño/discusión pero no ha tenido contribuciones en años, así que lo cierro. Si alguien está interesado en trabajar en esta idea, publique su interés y el equipo considerará reabrir.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Thaina picture Thaina  ·  5Comentarios

jnm2 picture jnm2  ·  4Comentarios

DavidKlempfner picture DavidKlempfner  ·  3Comentarios

TobiasSekan picture TobiasSekan  ·  4Comentarios

xplicit picture xplicit  ·  5Comentarios