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.
¿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.
Comentario más útil
Eso también podría funcionar. @nunit/framework-team, ¿alguien más encuentra este tema interesante?