9 votes

Comment tester en unité une fonction C# qui renvoie un Func<quelque chose> ?

J'ai une classe contenant une méthode qui renvoie un objet Result qui contient une propriété de type Func.

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

Comment puis-je écrire une assertion de test unitaire concernant le contenu de ce Func ? Ce qui suit ne fonctionne évidemment pas, car le compilateur génère deux méthodes différentes pour le 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));

Je suppose que je pourrais le faire en utilisant des arbres d'expression à la place, mais... y a-t-il un moyen d'éviter de le faire ? J'utilise NUnit 2.5.

EDIT : Il n'y a pas d'autres champs d'identification dans l'objet Résultat. Il s'agit d'un moyen d'invoquer l'objet/méthode suivant(e) sur la base d'une décision prise dans l'objet/méthode en cours.

2voto

Marek Points 5077

Pourquoi ne pas invoquer le Func et comparer les valeurs renvoyées ?

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

EDIT : Je vois que la classe Result n'a pas d'identité. Je suppose que vous avez d'autres champs dans la classe Resultat qui définissent l'identité du Resultat et peuvent être utilisés pour déterminer si deux résultats sont égaux.

2voto

Nathan Baulch Points 7994

Je ne connais pas de moyen facile de regarder à l'intérieur d'un lambda (autre que l'utilisation d'arbres d'expression comme vous l'avez dit) mais il est possible de comparer des délégués si on leur assigne un groupe de méthodes à la place.

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));

L'exemple ci-dessus ne fonctionne pas si vous utilisez des lambdas car elles sont compilées dans des méthodes différentes.

0voto

Andrew Bezzub Points 8794

Si usted Func<Result> renvoient toujours le même résultat, vous pouvez tester quel objet est renvoyé par la fonction.

0voto

goofballLogic Points 508

Eh bien, il semble que le test unitaire du contenu d'une Func va au-delà de la portée normale des tests unitaires. A Func représente du code compilé, et ne peut donc pas être inspecté plus avant sans recourir à l'analyse syntaxique de MSIL. Dans cette situation, il est donc nécessaire de se rabattre sur les délégués et les types instanciés (comme le suggère Nathan Baulch), ou d'utiliser des arbres d'expression à la place.

Mon équivalent d'arbre d'expression ci-dessous :

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

avec les tests unitaires comme suit :

// 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"));

Notez qu'il y a une certaine fragilité inhérente à ce test car il implique la structure de l'expression, ainsi que son comportement.

0voto

rossmcm Points 1536

Si je comprends bien le problème, la NextAction peut ou non avoir une implémentation lambda différente, et c'est ce qu'il faut tester.

Dans l'exemple ci-dessous, je compare les méthodes IL bytes. En utilisant la réflexion, obtenez les informations sur la méthode et les octets IL du corps dans un tableau. Si les tableaux d'octets correspondent, les lambdas sont les mêmes.

Il y a beaucoup de situations que cela ne peut pas gérer, mais s'il s'agit simplement de comparer deux lambdas qui devraient être exactement les mêmes, cela fonctionnera. Désolé, c'est dans 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; }
        }
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X