对于单元测试,我将使用 xUnit 测试框架。 在 .NET Core 中,您可以使用 dotnet new xunit 命令添加一个测试项目。一个显而易见的问题是模拟和验证动态参数,例如,假设您想验证一个方法调用是否具有动态属性。
要使用 Moq 模拟库,您可以通过 NuGet 添加此依赖项,例如:
dotnet add package Moq –-version 4.10.0假设您有一个接口,其想法是验证它是否被正确的动态对象调用。
public interface IMessageBus { void Send(dynamic message); }忽略该接口的实现。这些实现细节对于编写单元测试不是必需的。下面是被测试的系统:
public class MessageService { private readonly IMessageBus _messageBus; public MessageService(IMessageBus messageBus) { _messageBus = messageBus; } public void SendRawJson<T>(string json) { var message = JsonConvert.DeserializeObject<T>(json) as dynamic; _messageBus.Send(message); } }您可以使用泛型,这样就可以为序列化程序传入动态类型。然后调用 IMessageBus 并发送动态消息。被测试的方法接受一个 string 参数,并使用 dynamic 类型进行调用。
对于单元测试,请将其封装在 MessageServiceTests 类中。首先初始化 Mock 和被测试的服务:
public class MessageServiceTests { private readonly Mock<IMessageBus> _messageBus; private readonly MessageService _service; public MessageServiceTests() { _messageBus = new Mock<IMessageBus>(); _service = new MessageService(_messageBus.Object); } }使用 Moq 库中的 C# 泛型来模拟 IMessageBus,然后使用 Object 属性创建一个模拟实例。在所有的单元测试中私有实例变量都很有用,高可重用性的私有实例增加了类的内聚性。
使用 Moq 验证调用,一种直观的方式是尝试这么做:
_messageBus.Verify(m => m.Send(It.Is<ExpandoObject>(o => o != null && (o as dynamic).a == 1)));但是,遗憾的是,您将看到这样的错误消息:“表达式树不能包含动态操作。” 这是因为 C# lambda 表达式无法访问 DLR,它期望一个来自 CLR 的类型,这使得此动态参数难以验证。记得您的训练,利用您的“代码感”来解决这个问题。
要处理诸如类型之间不一致的问题,请使用 Callback 方法:
dynamic message = null; _messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>())).Callback<object>(o => message = o);请注意,Callback 方法将类型转换为 System.Object。因为所有类型都继承自 object 类型,所以可以将其赋值为 dynamic 类型。C# 可以把此 lambda 表达式中的 object 拆箱成 dynamic message。
是时候为 ExpandoObject 类型编写一个漂亮的单元测试了。使用 xUnit 作为测试框架,您将看到带有 Fact 属性的方法。
[Fact] public void SendsWithExpandoObject() { // arrange const string json = "{\"a\":1}"; dynamic message = null; _messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>())).Callback<object>(o => message = o); // act _service.SendRawJson<ExpandoObject>(json); // assert Assert.NotNull(message); Assert.Equal(1, message.a); }使用 DynamicObject 类型进行测试,重用您之前看到的 TypedDynamicJson:
[Fact] public void SendsWithDynamicObject() { // arrange const string json = "{\"a\":1,\"b\":\"1\"}"; dynamic message = null; _messageBus.Setup(m => m.Send(It.IsAny<TypedDynamicJson<long>>())).Callback<object>(o => message = o); // act _service.SendRawJson<TypedDynamicJson<long>>(json); // assert Assert.NotNull(message); Assert.Equal(1, message.a); Assert.Equal("a", string.Join(",", message.GetDynamicMemberNames())); }使用 C# 泛型,您可以在重用代码的同时转换序列化程序的动态类型。Moq 中的 Callback 方法允许您在两种类型系统之间进行必要的跳转。拥有一个优雅的类型层次结构和一个共同的父类成为了一个救星。
Using 语句下面的 using 语句是代码示例的一部分:
System: CLR 的基础类型,例如 Object 和 Console
System.Collections.Generic: 可枚举类型,例如 IDictionary
System.Dynamic: DLR 的动态类型,例如 ExpandoObject 和 DynamicObject
Newtonsonft.Json: JSON 序列化程序
Moq: 模拟库
Xunit: 测试框架
总结