为了账号安全,请及时绑定邮箱和手机立即绑定

使用 Microsoft Fakes 进行单元测试

标签:
C#

在编写单元测试时,我们会遇到不同的外部依赖项,大体上可以分为两类:

  • 依赖于接口或抽象类

  • 依赖于具体类

我们将使用 Microsoft Fakes 分别对两种条件下的依赖项进行隔离。

依赖于接口或抽象类

首先,我们来定义被测试代码。

复制代码

 1   public interface IEmailSender 2   { 3     bool SendEmail(string content); 4   } 5  6   public class Customer 7   { 8     public string Name { get; set; } 9     public override string ToString()10     {11       return Name;12     }13   }14 15   public interface ICustomerRepository16   {17     Customer Add(Customer customer);18   }19 20   public class CustomerRepository : ICustomerRepository21   {22     private IEmailSender _emailSender;23 24     public CustomerRepository(IEmailSender emailSender)25     {26       _emailSender = emailSender;27     }28 29     public Customer Add(Customer customer)30     {31       _emailSender.SendEmail(customer.ToString());32       return customer;33     }34   }

复制代码

在上面的代码中,CustomerRepostory 依赖于 IEmailSender 接口。

当在 CustomerRepostory 中调用 Add 方法添加 Customer 时,将调用 IEmailSender 的 SendEmail 方法来发送一个邮件。

我们将如何为 Add 方法添加单元测试呢?

复制代码

 1     [TestMethod] 2     public void TestCustomerRepositoryWhenAddCustomerThenShouldSendEmail() 3     { 4       // Arrange 5       IEmailSender stubEmailSender = new EmailSender(); 6  7       // Act 8       CustomerRepository repository = new CustomerRepository(emailSender); 9       Customer customer = new Customer() { Name = "Dennis Gao" };10       repository.Add(customer);11 12       // Assert13       Assert.IsTrue(isEmailSent);14     }

复制代码

在这里,我们肯定不会使用这种直接实例化 EmailSender 的方法,因为这样就依赖了具体的类了。

1 IEmailSender stubEmailSender = new EmailSender();

现在,我们使用 Microsoft Fakes 中的 Stub 功能来帮助测试。

在测试工程的引用列表中,在被测试程序集上点击右键,选择 "Add Fakes Assembly"。

然后会新增一个 Fakes 目录,并生成一个带 .Fakes 的文件。

下一步,在测试类中添加 {被测试工程名称}.Fakes 名空间。

1 using ConsoleApplication17_TestFakes;2 using ConsoleApplication17_TestFakes.Fakes;

当在代码中输入 Stub 时,智能提示会显示出已经自动生成的 Stub 类了。

现在,我们就可以使用 Stub 功能来模拟 IEmailSender 接口了。

复制代码

 1     [TestMethod] 2     public void TestCustomerRepositoryWhenAddCustomerThenShouldSendEmail() 3     { 4       // Arrange 5       bool isEmailSent = false; 6       IEmailSender stubEmailSender = new StubIEmailSender() 7       { 8         SendEmailString = (content) => 9         {10           isEmailSent = true;11           return true;12         },13       };14 15       // Act16       CustomerRepository repository = new CustomerRepository(stubEmailSender);17       Customer customer = new Customer() { Name = "Dennis Gao" };18       repository.Add(customer);19 20       // Assert21       Assert.IsTrue(isEmailSent);22     }

复制代码

依赖于具体类

生活不总是那么美好,当然不是所有代码都会遵循控制反转的原则。很多时候,我们仍然需要使用具体类。

比如,在如下的代码中,OrderRepository 中的 Add 方法直接构建一个 EmailSender ,然后调用其 SendEmail 方法来发送邮件。

复制代码

 1   public class Order 2   { 3     public long Id { get; set; } 4     public override string ToString() 5     { 6       return Id.ToString(); 7     } 8   } 9 10   public interface IOrderRepository11   {12     Order Add(Order order);13   }14 15   public class EmailSender : IEmailSender16   {17     public bool SendEmail(string content)18     {19       return true;20     }21   }22 23   public class OrderRepository : IOrderRepository24   {25     public OrderRepository()26     {27     }28 29     public Order Add(Order order)30     {31       IEmailSender emailSender = new EmailSender();32       emailSender.SendEmail(order.ToString());33       return order;34     }35   }

复制代码

现在,我们已经没有接口或者抽象类可用于模拟了,所以 Stub 在此种条件下也失去了作用。此时,Shim 上场了。Shim 是运行时方法拦截器,功能更加强大。通过 Shim 我们可以为任意类的方法或属性提供我们自己的实现。

复制代码

 1     [TestMethod] 2     public void TestOrderRepositoryWhenAddOrderThenShouldSendEmail() 3     { 4       // Arrange 5       bool isEmailSent = false; 6  7       using (ShimsContext.Create()) 8       { 9         ShimEmailSender.AllInstances.SendEmailString = (@this, content) =>10         {11           isEmailSent = true;12           return true;13         };14 15         // Act16         OrderRepository repository = new OrderRepository();17         Order order = new Order() { Id = 123 };18         repository.Add(order);19       }20 21       // Assert22       Assert.IsTrue(isEmailSent);23     }

复制代码

使用 Shim 时,需要先为其指定上下文范围,通过 ShimsContext.Create() 来创建。

通常,如果遇到使用 Shim 的情况,则说明代码或许写的有些问题,没有遵循控制反转原则等。

使用 Shim 来控制系统类

假设我们需要一个判断当天是否是全年最后一天的方法,我们把它定义在 DateTimeHelper 静态类中。

复制代码

 1   public static class DateTimeHelper 2   { 3     public static bool IsTodayLastDateOfYear() 4     { 5       DateTime today = DateTime.Now; 6       if (today.Month == 12 && today.Day == 31) 7         return true; 8       else 9         return false;10     }11   }

复制代码

我们来为这个方法编写测试,显然需要两种条件。

复制代码

 1     [TestMethod] 2     public void TestTodayIsLastDateOfYear() 3     { 4       // Arrange 5  6       // Act 7       bool result = DateTimeHelper.IsTodayLastDateOfYear(); 8  9       // Assert10       Assert.IsTrue(result);11     }12 13     [TestMethod]14     public void TestTodayIsNotLastDateOfYear()15     {16       // Arrange17 18       // Act19       bool result = DateTimeHelper.IsTodayLastDateOfYear();20 21       // Assert22       Assert.IsFalse(result);23     }

复制代码

这么看来,在运行这两条单元测试时,肯定是一个是通过,一个是不通过。

为了解决这个问题,我们需要为系统类 System.DateTime 添加 Shim 类。

同样在程序集的引用列表中,在 System 上点击右键 "Add Fakes Assembly"。

然后会生成 System.Fakes 文件。

在测试代码中添加名空间 System.Fakes。

1 using System.Fakes;

现在,我们来修改代码,使用 Shim 来完成测试。

复制代码

 1     [TestMethod] 2     public void TestTodayIsLastDateOfYear() 3     { 4       // Arrange 5  6       // Act 7       bool result = false; 8       using (ShimsContext.Create()) 9       {10         ShimDateTime.NowGet = () => new DateTime(2013, 12, 31);11         result = DateTimeHelper.IsTodayLastDateOfYear();12       }13 14       // Assert15       Assert.IsTrue(result);16     }17 18     [TestMethod]19     public void TestTodayIsNotLastDateOfYear()20     {21       // Arrange22 23       // Act24       bool result = false;25       using (ShimsContext.Create())26       {27         ShimDateTime.NowGet = () => new DateTime(2013, 12, 9);28         result = DateTimeHelper.IsTodayLastDateOfYear();29       }30 31       // Assert32       Assert.IsFalse(result);33     }

复制代码

直接为 ShimDateTime 的 Now 属性 Get 来指定 Lambda 表达式函数。

1 ShimDateTime.NowGet = () => new DateTime(2013, 12, 31);

通过 Debug 我们可以看到,DateTime.Now 已经被成功的替换为指定的时间。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消