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

发布于 2024-08-25 16:09:12 字数 639 浏览 4 评论 0 原文

我有一个类,其中包含一个返回 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 对象中没有其他标识字段。它旨在成为一种基于当前对象/方法中做出的决策来调用下一个对象/方法的方法。

I have a class containing a method which returns a Result object which contains a property of type Func.

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

How do I write a unit test assertion regarding the content of this Func? The following obviously does not work, because the compiler generates two different methods for the 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));

I'm guessing that I could do this by using expression trees instead, but... is there a way to avoid doing so? I'm using NUnit 2.5.

EDIT:
There are no other identifying fields in the Result object. It is intended to be a way of invoking the next object/method based on a decision made in the current object/method.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

爱你不解释 2024-09-01 16:09:13

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

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

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

Why not invoke the Func and compare the returned values?

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

EDIT: I see that the Result class does not have any identity. I guess you have some other fields in the Result class that define the identity of the Result and can be used to determine if two results are equal.

输什么也不输骨气 2024-09-01 16:09:13

我不知道有一种简单的方法来查看 lambda 内部(除了如您所说的使用表达式树之外),但如果为委托分配了 方法组 代替。

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

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

I'm not aware of an easy way to look inside a lambda (other than using expression trees as you said) but it is possible to compare delegates if they're assigned a method group instead.

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

The above example does not work if you use lambdas since they are compiled to different methods.

梦幻的味道 2024-09-01 16:09:13

如果您 Func 始终返回相同的结果,您可以测试函数返回哪个对象。

If you Func<Result> always return the same result you can test which object is returned by the function.

当梦初醒 2024-09-01 16:09:13

看来对 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"));

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

Well it appears that unit testing the contents of a Func goes beyond the normal range of unit testing. A Func represents compiled code, and therefore can not be further inspected without resorting to parsing MSIL. In this situation, it is therefore necessary to fall back on delegates and instantiated types (as suggested by Nathan Baulch), or to use expression trees instead.

My expression tree equivalent below:

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

with the unit testing as follows:

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

Note that there is some inherent fragility to this test as it implies the structure of the expression, as well as its behaviour.

千纸鹤 2024-09-01 16:09:13

如果我正确理解了这个问题,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; }
        }
    }

If I understand the issue properly the NextAction may or may not have a different lambda implementation, which is what needs testing.

In the example below I compare the methods IL bytes. Using reflection, get the method info and IL bytes from the body in an array. If the byte arrays match, the lambda's are the same.

There are lots of situations that this wont handle, but if it's just question of comparing two lambda's that should be exactly the same, this will work. Sorry it's in 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; }
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文