2010-03-02 9 views
28

¿Hay alguna forma de pasar los tipos genéricos usando un TestCase a una prueba en NUnit?NUnit TestCase con Genericos

Esto es lo que me gustaría hacer, pero la sintaxis no es correcta ...

[Test] 
[TestCase<IMyInterface, MyConcreteClass>] 
public void MyMethod_GenericCall_MakesGenericCall<TInterface, TConcreteClass>() 
{ 
    // Arrange 

    // Act 
    var response = MyClassUnderTest.MyMethod<TInterface>(); 

    // Assert 
    Assert.IsInstanceOf<TConcreteClass>(response); 
} 

O si no, ¿cuál es la mejor manera de conseguir la misma funcionalidad (obviamente voy a tener múltiples TestCases en el código real)?

de actualización con otro ejemplo ...

Este es otro ejemplo de un solo tipo genérico pasado ...

[Test] 
[TestCase<MyClass>("Some response")] 
public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse) 
{ 
    // Arrange 

    // Act 
    var response = MyClassUnderTest.MyMethod<T>(); 

    // Assert 
    Assert.AreEqual(expectedResponse, response); 
} 
+0

¿Podría ser más específico sobre lo que quiere probar? En el ejemplo anterior, parece que está escribiendo pruebas de unidades para la infraestructura .NET en lugar de su código. –

+0

Disculpa, estaba intentando que el ejemplo fuera lo más simple posible y es posible que haya ido demasiado lejos. Estoy escribiendo pruebas para probar que ciertas clases se han registrado contra ciertas interfaces en un contenedor IoC. Entiendo que esto está superando los límites de lo que debería probarse desde un punto de vista lógico. Sin embargo, hay muchas otras instancias en las que me gustaría probar el paso de diferentes tipos a un método genérico. –

+2

NUnit TestCase puede, por supuesto, abreviarse como NUTCase. –

Respuesta

17

que tuvo la oportunidad de hacer algo similar en la actualidad, y no estaba contento con el uso de la reflexión.

Decidí aprovechar [TestCaseSource] en lugar de delegar la lógica de prueba como un contexto de prueba a una clase de prueba genérica, anclada en una interfaz no genérica, y llamar a la interfaz de pruebas individuales (mis pruebas reales tienen muchos más métodos en la interfaz, y el uso de AutoFixture para establecer el contexto):

class Sut<T> 
{ 
    public string ReverseName() 
    { 
     return new string(typeof(T).Name.Reverse().ToArray()); 
    } 
} 

[TestFixture] 
class TestingGenerics 
{ 
    public IEnumerable<ITester> TestCases() 
    { 
     yield return new Tester<string> { Expectation = "gnirtS"}; 
     yield return new Tester<int> { Expectation = "23tnI" }; 
     yield return new Tester<List<string>> { Expectation = "1`tsiL" }; 
    } 

    [TestCaseSource("TestCases")] 
    public void TestReverse(ITester tester) 
    { 
     tester.TestReverse(); 
    } 

    public interface ITester 
    { 
     void TestReverse(); 
    } 

    public class Tester<T> : ITester 
    { 
     private Sut<T> _sut; 

     public string Expectation { get; set; } 

     public Tester() 
     { 
      _sut=new Sut<T>(); 
     } 

     public void TestReverse() 
     { 
      Assert.AreEqual(Expectation,_sut.ReverseName()); 
     } 

    } 
} 
4

de inicio con la prueba primero - incluso cuando se analiza. ¿Qué quieres hacer? Probablemente algo como esto:

[Test] 
public void Test_GenericCalls() 
{ 
    MyMethod_GenericCall_MakesGenericCall<int>("an int response"); 
    MyMethod_GenericCall_MakesGenericCall<string>("a string response"); 
     : 
} 

a continuación, puedes realizar la prueba de una prueba de funcionamiento simple y llano. Sin marcador [Test]

public void MyMethod_GenericCall_MakesGenericCall<T>(string expectedResponse) 
{ 
    // Arrange 

    // Act 
    var response = MyClassUnderTest.MyMethod<T>(); 

    // Assert 
    Assert.AreEqual(expectedResponse, response); 
} 
+0

Aunque no utiliza TestCases, esta parece ser la forma más adecuada de estructurar este código desde una perspectiva de legibilidad. Entonces, la desventaja es que una instancia que falla causará que la prueba falle y, por lo tanto, no detectará un error. –

+0

Para ayudar a identificar el error, siempre puede insertar una descripción como otro parámetro en el método MyMethod_GenericCall_MakesGenericCall y usarlo en el método Assert.AreEqual. –

6

Los atributos en C# no pueden ser genéricos, por lo que no podrá hacer las cosas exactamente como lo desee. Quizás lo más fácil sería poner los atributos TestCase en un método auxiliar que utiliza la reflexión para llamar al método real. Algo como esto podría funcionar (nota, no probado):

[TestCase(typeof(MyClass), "SomeResponse")] 
    public void TestWrapper(Type t, string s) 
    { 
     typeof(MyClassUnderTest).GetMethod("MyMethod_GenericCall_MakesGenericCall").MakeGenericMethod(t).Invoke(null, new [] { s }); 
    } 
+0

¿Por qué no pensé en esto? Muy inteligente @kvb. ¡Gracias! –

4

Hice algo similar la semana pasada. Esto es lo que terminé con:

internal interface ITestRunner 
{ 
    void RunTest(object _param, object _expectedValue); 
} 

internal class TestRunner<T> : ITestRunner 
{ 
    public void RunTest(object _param, T _expectedValue) 
    { 
     T result = MakeGenericCall<T>(); 

     Assert.AreEqual(_expectedValue, result); 
    } 
    public void RunTest(object _param, object _expectedValue) 
    { 
     RunTest(_param, (T)_expectedValue); 
    } 
} 

Y entonces la prueba en sí:

[Test] 
[TestCase(typeof(int), "my param", 20)] 
[TestCase(typeof(double), "my param", 123.456789)] 
public void TestParse(Type _type, object _param, object _expectedValue) 
{ 
    Type runnerType = typeof(TestRunner<>); 
    var runner = Activator.CreateInstance(runnerType.MakeGenericType(_type)); 
    ((ITestRunner)runner).RunTest(_param, _expectedValue); 
} 
+0

Sí, de forma similar a la respuesta de kvb, esto es lo más aproximado que se puede obtener en términos del atributo TestCase y luego usar el reflejo para ejecutar la prueba. Sin embargo, como le dije a kvb, es un poco difícil entender lo que se está probando. –

+0

@Russell: Definitivamente. En mi código actual, mitigé un poco el problema de legibilidad mediante el uso de nombres claros para las clases y los métodos involucrados. –

2

Como era de pruebas con funciones genéricas que devuelven objetos ?. Ejemplo:

public Empleado TestObjetoEmpleado(Empleado objEmpleado) 
{ 
    return objEmpleado; 
} 

Gracias

4

Usted puede hacer a medida GenericTestCaseAttribute

[Test] 
[GenericTestCase(typeof(MyClass) ,"Some response", TestName = "Test1")] 
[GenericTestCase(typeof(MyClass1) ,"Some response", TestName = "Test2")] 
public void MapWithInitTest<T>(string expectedResponse) 
{ 
    // Arrange 

    // Act 
    var response = MyClassUnderTest.MyMethod<T>(); 

    // Assert 
    Assert.AreEqual(expectedResponse, response); 
} 

Aquí está la implementación de GenericTestCaseAttribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 
public class GenericTestCaseAttribute : TestCaseAttribute, ITestBuilder 
{ 
    private readonly Type _type; 
    public GenericTestCaseAttribute(Type type, params object[] arguments) : base(arguments) 
    { 
     _type = type; 
    } 

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite) 
    { 
     if (method.IsGenericMethodDefinition && _type != null) 
     { 
      var gm = method.MakeGenericMethod(_type); 
      return BuildFrom(gm, suite); 
     } 
     return BuildFrom(method, suite); 
    } 
} 
16

métodos de prueba NUnit realidad puede ser genérico, siempre que los argumentos de tipo genérico se pueden deducir de los parámetros:

[TestCase(42)] 
[TestCase("string")] 
[TestCase(double.Epsilon)] 
public void GenericTest<T>(T instance) 
{ 
    Console.WriteLine(instance); 
} 

NUnit Generic Test

Si los argumentos genéricos no se pueden deducir, el corredor de prueba se no tienen ni idea de cómo resolver los argumentos de tipo:

[TestCase(42)] 
[TestCase("string")] 
[TestCase(double.Epsilon)] 
public void GenericTest<T>(object instance) 
{ 
    Console.WriteLine(instance); 
} 

NUnit Generic Test Fail

Pero en este caso se puede aplicar un atributo personalizado:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 
public class TestCaseGenericAttribute : TestCaseAttribute, ITestBuilder 
{ 
    public TestCaseGenericAttribute(params object[] arguments) 
     : base(arguments) 
    { 
    } 

    public Type[] TypeArguments { get; set; } 

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite) 
    { 
     if (!method.IsGenericMethodDefinition) 
      return base.BuildFrom(method, suite); 

     if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length) 
     { 
      var parms = new TestCaseParameters { RunState = RunState.NotRunnable }; 
      parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements"); 
      return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) }; 
     } 

     var genMethod = method.MakeGenericMethod(TypeArguments); 
     return base.BuildFrom(genMethod, suite); 
    } 
} 

Uso:

[TestCaseGeneric("Some response", TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }] 
public void MyMethod_GenericCall_MakesGenericCall<T1, T2>(string expectedResponse) 
{ 
    // whatever 
} 

Y una personalización similar para TestCaseSourceAttribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 
public class TestCaseSourceGenericAttribute : TestCaseSourceAttribute, ITestBuilder 
{ 
    public TestCaseSourceGenericAttribute(string sourceName) 
     : base(sourceName) 
    { 
    } 

    public Type[] TypeArguments { get; set; } 

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite) 
    { 
     if (!method.IsGenericMethodDefinition) 
      return base.BuildFrom(method, suite); 

     if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length) 
     { 
      var parms = new TestCaseParameters { RunState = RunState.NotRunnable }; 
      parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements"); 
      return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) }; 
     } 

     var genMethod = method.MakeGenericMethod(TypeArguments); 
     return base.BuildFrom(genMethod, suite); 
    } 
} 

Uso:

[TestCaseSourceGeneric(nameof(mySource)), TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]