如何对返回Func <something>?</something>的C#函数进行单元测试

时间:2010-03-22 12:01:54

标签: c# unit-testing lambda

我有一个包含一个方法的类,该方法返回一个Result对象,该对象包含一个类型为Func的属性。

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

如何编写有关此Func内容的单元测试断言?以下显然不起作用,因为编译器为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));

我猜我可以通过使用表达式树来实现这一点,但是......有没有办法避免这样做?我正在使用NUnit 2.5。

编辑: Result对象中没有其他标识字段。它旨在成为根据当前对象/方法中的决策调用下一个对象/方法的方法。

5 个答案:

答案 0 :(得分:2)

为什么不调用Func并比较返回的值?

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

编辑:我看到Result类没有任何标识。我猜你在Result类中有一些其他字段定义了Result的标识,可以用来确定两个结果是否相等。

答案 1 :(得分:2)

我不知道一个简单的方法来查看lambda(除了你所说的使用表达式树),但如果代表被分配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));

如果使用lambdas,上面的示例不起作用,因为它们被编译为不同的方法。

答案 2 :(得分:0)

如果Func<Result>总是返回相同的结果,则可以测试函数返回的对象。

答案 3 :(得分:0)

看来单位测试Func的内容超出了正常的单元测试范围。 Func表示已编译的代码,因此无需求助于解析MSIL就无法进一步检查。因此,在这种情况下,有必要依靠代理和实例化类型(如Nathan Baulch所建议的那样),或者改为使用表达式树。

我的表达式树等价如下:

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

单元测试如下:

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

请注意,此测试存在一些固有的脆弱性,因为它暗示了表达式的结构及其行为。

答案 4 :(得分:0)

如果我理解正确的问题,NextAction可能会或可能没有不同的lambda实现,这是需要测试的。

在下面的例子中,我比较了IL字节的方法。使用反射,从数组中的主体获取方法信息和IL字节。如果字节数组匹配,则lambda是相同的。

有很多情况不会处理,但如果只是比较两个lambda应该完全相同的问题,这将有效。对不起,这是在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; }
        }
    }