使用 Microsoft Fakes 进行单元测试

本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。

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

  • 依赖于接口或抽象类
  • 依赖于具体类

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

依赖于接口或抽象类

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

   public interface IEmailSender
{
bool SendEmail(string content);
} public class Customer
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
} public interface ICustomerRepository
{
Customer Add(Customer customer);
} public class CustomerRepository : ICustomerRepository
{
private IEmailSender _emailSender; public CustomerRepository(IEmailSender emailSender)
{
_emailSender = emailSender;
} public Customer Add(Customer customer)
{
_emailSender.SendEmail(customer.ToString());
return customer;
}
}

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

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

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

     [TestMethod]
public void TestCustomerRepositoryWhenAddCustomerThenShouldSendEmail()
{
// Arrange
IEmailSender stubEmailSender = new EmailSender(); // Act
CustomerRepository repository = new CustomerRepository(emailSender);
Customer customer = new Customer() { Name = "Dennis Gao" };
repository.Add(customer); // Assert
Assert.IsTrue(isEmailSent);
}

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

 IEmailSender stubEmailSender = new EmailSender();

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

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

使用 Microsoft Fakes 进行单元测试

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

使用 Microsoft Fakes 进行单元测试

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

 using ConsoleApplication17_TestFakes;
using ConsoleApplication17_TestFakes.Fakes;

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

使用 Microsoft Fakes 进行单元测试

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

     [TestMethod]
public void TestCustomerRepositoryWhenAddCustomerThenShouldSendEmail()
{
// Arrange
bool isEmailSent = false;
IEmailSender stubEmailSender = new StubIEmailSender()
{
SendEmailString = (content) =>
{
isEmailSent = true;
return true;
},
}; // Act
CustomerRepository repository = new CustomerRepository(stubEmailSender);
Customer customer = new Customer() { Name = "Dennis Gao" };
repository.Add(customer); // Assert
Assert.IsTrue(isEmailSent);
}

依赖于具体类

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

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

   public class Order
{
public long Id { get; set; }
public override string ToString()
{
return Id.ToString();
}
} public interface IOrderRepository
{
Order Add(Order order);
} public class EmailSender : IEmailSender
{
public bool SendEmail(string content)
{
return true;
}
} public class OrderRepository : IOrderRepository
{
public OrderRepository()
{
} public Order Add(Order order)
{
IEmailSender emailSender = new EmailSender();
emailSender.SendEmail(order.ToString());
return order;
}
}

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

     [TestMethod]
public void TestOrderRepositoryWhenAddOrderThenShouldSendEmail()
{
// Arrange
bool isEmailSent = false; using (ShimsContext.Create())
{
ShimEmailSender.AllInstances.SendEmailString = (@this, content) =>
{
isEmailSent = true;
return true;
}; // Act
OrderRepository repository = new OrderRepository();
Order order = new Order() { Id = };
repository.Add(order);
} // Assert
Assert.IsTrue(isEmailSent);
}

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

使用 Microsoft Fakes 进行单元测试

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

使用 Shim 来控制系统类

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

   public static class DateTimeHelper
{
public static bool IsTodayLastDateOfYear()
{
DateTime today = DateTime.Now;
if (today.Month == && today.Day == )
return true;
else
return false;
}
}

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

     [TestMethod]
public void TestTodayIsLastDateOfYear()
{
// Arrange // Act
bool result = DateTimeHelper.IsTodayLastDateOfYear(); // Assert
Assert.IsTrue(result);
} [TestMethod]
public void TestTodayIsNotLastDateOfYear()
{
// Arrange // Act
bool result = DateTimeHelper.IsTodayLastDateOfYear(); // Assert
Assert.IsFalse(result);
}

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

使用 Microsoft Fakes 进行单元测试

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

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

然后会生成 System.Fakes 文件。

使用 Microsoft Fakes 进行单元测试

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

 using System.Fakes;

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

     [TestMethod]
public void TestTodayIsLastDateOfYear()
{
// Arrange // Act
bool result = false;
using (ShimsContext.Create())
{
ShimDateTime.NowGet = () => new DateTime(, , );
result = DateTimeHelper.IsTodayLastDateOfYear();
} // Assert
Assert.IsTrue(result);
} [TestMethod]
public void TestTodayIsNotLastDateOfYear()
{
// Arrange // Act
bool result = false;
using (ShimsContext.Create())
{
ShimDateTime.NowGet = () => new DateTime(, , );
result = DateTimeHelper.IsTodayLastDateOfYear();
} // Assert
Assert.IsFalse(result);
}

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

 ShimDateTime.NowGet = () => new DateTime(, , );

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

使用 Microsoft Fakes 进行单元测试

参考资料

本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。

上一篇:Web Service随笔


下一篇:OpenCV(三) 之 基本数据结构 CvMat和 IplImage