2010-03-22 17 views
8

Tengo una clase que contiene un método que devuelve un objeto Result que contiene una propiedad de tipo Func.¿Cómo pruebo la unidad de una función C# que devuelve un Func <something>?

class Result { 
    public Func<Result> NextAction { get; set; } 
} 

¿Cómo escribo una afirmación de prueba de unidad con respecto al contenido de esta función? A continuación, obviamente, no funciona, ya que el compilador genera dos métodos diferentes para el lambda:

// Arrange 
ListController controller = new ListController(domain); 
// Act 
Result actual = controller.DefaultAction(); 
// Assert 
Func<Result> expected =() => new ProductsController(domain).ListAction(); 
Assert.That(actual.NextAction, Is.EqualTo(expected)); 

que supongo que podría hacer esto mediante el uso de los árboles de expresión en su lugar, pero ... ¿hay una manera de evitar ¿haciéndolo? Estoy usando NUnit 2.5.

EDITAR: No hay otros campos de identificación en el objeto Result. Se pretende que sea una forma de invocar el siguiente objeto/método en función de una decisión tomada en el objeto/método actual.

Respuesta

0

Bueno, parece que la unidad que prueba los contenidos de un Func va más allá del rango normal de pruebas unitarias. Un Func representa el código compilado, y por lo tanto no puede ser inspeccionado más a fondo sin recurrir al análisis de MSIL. En esta situación, es necesario recurrir a delegados y tipos instanciados (como lo sugiere Nathan Baulch) o utilizar árboles de expresiones en su lugar.

Mi expresión equivalente árbol a continuación:

class Result { 
    public Expression<Func<Result>> NextAction { get; set; } 
} 

con la prueba de la unidad de la siguiente manera:

// Arrange 
ListController controller = new ListController(domain); 
// Act 
Result actual = controller.DefaultAction(); 
// Assert 
MethodCallExpression methodExpr = (MethodCallExpression)actual.NextAction.Body; 
NewExpression newExpr = (NewExpression)methodExpr.Object; 
Assert.That(newExpr.Type, Is.EqualTo(typeof(ProductsController))); 
Assert.That(methodExpr.Method.Name, Is.EqualTo("ListAction")); 

Tenga en cuenta que hay una cierta fragilidad inherente a esta prueba, ya que implica la estructura de la expresión, así como su comportamiento.

2

¿Por qué no invocar el Func y comparar los valores devueltos?

var actualValue = actual.NextAction(); 
var expectedValue = expected(); 
Assert.That(actualValue, Is.EqualTo(expectedValue)); 

EDIT: Veo que la clase de resultado no tiene ninguna identidad. Supongo que tiene otros campos en la clase Result que definen la identidad del resultado y se pueden usar para determinar si dos resultados son iguales.

+0

Porque el Func devuelve otro objeto Result, ¡con los mismos problemas! – goofballLogic

+0

Luego necesita implementar algo que identifique el resultado y determine la igualdad en función de eso – Marek

+0

¿Como una cadena arbitraria que describe el intento del Func? – goofballLogic

0

Si Func<Result> siempre devuelven el mismo resultado, puede probar qué objeto devuelve la función.

+0

Desafortunadamente, el Func devuelve un Resultado que solo contiene otro Func goofballLogic

2

No conozco una manera fácil de mirar dentro de un lambda (que no sea el uso de árboles de expresión como dijiste) pero es posible comparar delegados si tienen asignado un method group.

var result1 = new Result { 
    NextAction = new ProductsController(domain).ListAction }; 
var result2 = new Result { 
    NextAction = new ProductsController(domain).ListAction }; 

//objects are different 
Assert.That(result1, Is.Not.EqualTo(result2)); 

//delegates are different 
Assert.That(result1.NextAction, Is.Not.EqualTo(result2.NextAction)); 

//methods are the same 
Assert.That(result1.NextAction.Method, Is.EqualTo(result2.NextAction.Method)); 

El ejemplo anterior no funciona si utiliza lambdas ya que se compilan con métodos diferentes.

+0

Sí, esto es muy parecido a las pruebas para el registro de eventos o cualquier otro uso de delegado. Si bien este puede ser el mejor compromiso, la intención original era incluir la creación de instancias de ProductsController como parte de la invocación de NextAction. – goofballLogic

0

Si entiendo el problema correctamente, NextAction puede tener o no una implementación lambda diferente, que es lo que necesita probarse.

En el siguiente ejemplo comparo los métodos IL bytes. Usando la reflexión, obtenga la información del método y los bytes de IL del cuerpo en una matriz. Si las matrices de bytes coinciden, las lambda son iguales.

Hay muchas situaciones que esto no manejará, pero si solo se trata de comparar dos lambda que deberían ser exactamente iguales, esto funcionará.Lo sentimos, está en MSTest :)

using System.Reflection; 
.... 


    [TestClass] 
    public class Testing 
    { 
     [TestMethod] 
     public void Results_lambdas_match() 
     { 
      // Arrange 
      ListController testClass = new ListController(); 
      Func<Result> expected = () => new ProductsController().ListAction(); 
      Result actual; 
      byte[ ] actualMethodBytes; 
      byte[ ] expectedMethodBytes; 

      // Act 
      actual = testClass.DefaultAction(); 

      // Assert 
      actualMethodBytes = actual.NextAction. 
       Method.GetMethodBody().GetILAsByteArray(); 
      expectedMethodBytes = expected. 
       Method.GetMethodBody().GetILAsByteArray(); 

      // Test that the arrays are the same, more rigorous check really should 
      // be done .. but this is an example :) 
      for (int count=0; count < actualMethodBytes.Length; count++) 
      { 
       if (actualMethodBytes[ count ] != expectedMethodBytes[ count ]) 
        throw new AssertFailedException(
         "Method implementations are not the same"); 
      } 
     } 
     [TestMethod] 
     public void Results_lambdas_do_not_match() 
     { 
      // Arrange 
      ListController testClass = new ListController(); 
      Func<Result> expected = () => new OtherController().ListAction(); 
      Result actual; 
      byte[ ] actualMethodBytes; 
      byte[ ] expectedMethodBytes; 
      int count=0; 

      // Act 
      actual = testClass.DefaultAction(); 

      // Assert 
      actualMethodBytes = actual.NextAction. 
       Method.GetMethodBody().GetILAsByteArray(); 
      expectedMethodBytes = expected. 
       Method.GetMethodBody().GetILAsByteArray(); 

      // Test that the arrays aren't the same, more checking really should 
      // be done .. but this is an example :) 
      for (; count < actualMethodBytes.Length; count++) 
      { 
       if (actualMethodBytes[ count ] != expectedMethodBytes[ count ]) 
        break; 
      } 
      if ((count + 1 == actualMethodBytes.Length) 
       && (actualMethodBytes.Length == expectedMethodBytes.Length)) 
       throw new AssertFailedException(
        "Method implementations are the same, they should NOT be."); 
     } 

     public class Result 
     { 
      public Func<Result> NextAction { get; set; } 
     } 
     public class ListController 
     { 
      public Result DefaultAction() 
      { 
       Result result = new Result(); 
       result.NextAction = () => new ProductsController().ListAction(); 

       return result; 
      } 
     } 
     public class ProductsController 
     { 
      public Result ListAction() { return null; } 
     } 
     public class OtherController 
     { 
      public Result ListAction() { return null; } 
     } 
    } 
+0

Sí, esto podría ser una posibilidad. Eso podría funcionar, excepto que el código pasa el parámetro "dominio" al constructor de ProductsController que se resuelve en diferentes IL. Creo que esto se debe a la clase creada para alojar la expresión lambda compilada. – goofballLogic

+0

Una forma de evitar esto sería 'capturar' el código correcto de bytes IL del código en ejecución y simplemente igualarlo. La prueba terminaría siendo muy específica para las condiciones que se están probando, pero funcionaría. Esto no está mal si tienes una prueba para ese estado específico, pero no quisiera muchos de ellos. – MarcLawrence

Cuestiones relacionadas