使用 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"。
然后会新增一个 Fakes 目录,并生成一个带 .Fakes 的文件。
下一步,在测试类中添加 {被测试工程名称}.Fakes 名空间。
using ConsoleApplication17_TestFakes;
using ConsoleApplication17_TestFakes.Fakes;
当在代码中输入 Stub 时,智能提示会显示出已经自动生成的 Stub 类了。
现在,我们就可以使用 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() 来创建。
通常,如果遇到使用 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);
}
这么看来,在运行这两条单元测试时,肯定是一个是通过,一个是不通过。
为了解决这个问题,我们需要为系统类 System.DateTime 添加 Shim 类。
同样在程序集的引用列表中,在 System 上点击右键 "Add Fakes Assembly"。
然后会生成 System.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 已经被成功的替换为指定的时间。
参考资料
- Isolating Code Under Test with Microsoft Fakes
- Better Unit Testing with Microsoft Fakes
- Visual studio 2012 Fakes
- Using shims to isolate your application from other assemblies for unit testing
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。
使用 Microsoft Fakes 进行单元测试的更多相关文章
- 使用Microsoft Fakes进行单元测试(2)
接上一篇使用Microsoft Fakes进行单元测试(1) 下面进行Shim的演示. 2.使用Shim替换静态方法 假设我们需要一个工具方法用来格式化当前时间为字符串,因为DateTime.Now一 ...
- Microsoft Fakes进行单元测试
使用Microsoft Fakes进行单元测试(1) 一:什么是单元测试 单元测试是对软件进行准确性验证的步骤.单元测试并不进行整个软件功能的测试,仅仅是对于最小工作单元的测试.一般最小工作单元就 ...
- 使用Microsoft Fakes进行单元测试(1)
一:什么是单元测试 单元测试是对软件进行准确性验证的步骤.单元测试并不进行整个软件功能的测试,仅仅是对于最小工作单元的测试.一般最小工作单元就是指方法/函数等. 这里并不打算对单元测试的概念及基础进行 ...
- VS2012 Unit Test——Microsoft Fakes入门
如题,本文主要作为在VS2012使用Fakes的入门示例,开发工具必须是VS2012或更高版本. 关于Fakes的MSDN地址:http://msdn.microsoft.com/en-us/libr ...
- 使用Microsoft Fakes隔离测试代码
在单元测试(Unit Test)中我们遇到的问题之一是:假如被测试组件(类或项目)为A,组件A依赖于组件B,那么在组件A的单元测试ATest中测试A时,也需要依赖于B,在B发生改动后,就可能影响到A的 ...
- Microsoft Fakes
http://baike.baidu.com/view/9602275.htm?fr=aladdin http://technet.microsoft.com/zh-cn/magazine/hh549 ...
- C#单元测试面面观
标题有点标题党,但相信各位看完这篇文章一定会所收获,如果之前没有接触过单元测试或了解不深通过本文都能对单元测试有个全新认识.本文的特点是不脱离实际,所测试的代码都是常见的模式. 写完这篇文章后,我看了 ...
- 使用IdleTest进行TDD单元测试驱动开发演练(1)
[前言] 开发工具:Visual Studio 2012 测试库:Visual Studio 2012自带的MSTest DI框架:Unity 数据持久层:Entity Framework 前端UI: ...
- 解决问题:无法对 System程序集 添加Fakes程序集
为了在单元测试中指定DateTime.Now的值,我采用Microsoft Fakes技术的Shim. 主要参考了园里的http://www.cnblogs.com/FreeDong/p/335311 ...
随机推荐
- NO.2
虚拟语气的终结版.英语语法的终结时刻.迎接新的英语挑战!!!
- java 字节数组转int
4字节数组转int类型 小端模式 /** * 数组转int类型 * * @param src * @return */ public static int bytesToInt(byte[] src) ...
- TJI读书笔记11-多态
TJI读书笔记11-多态 再说说向上转型 多态的原理 构造器和多态 协变返回类型 使用继承进行设计 多态是数据抽象和继承之后的第三种基本特征. 一句话说,多态分离了做什么和怎么做(再次对埃大爷佩服的五 ...
- Origin9.1如何使用原始数据(Raw Data)绘制风向玫瑰图
核心提示:今天为大家简单介绍下如何使用原始数据绘制风向玫瑰图.本例以Origin 9.1进行演示.1.本例所用数据截图如下,列A为风向,列B为风速.2.选中两列数据,进入Plot下的Specializ ...
- stm32串口之存储与解析
最近在做一个小项目,需要用stm32串口接受Arduino发送的一个不定长的数据,并且解析数据,执行其中的命令:秉着不在中断中做过多任务的思想,我们将从串口中接受到的字符放到一个数组当中. 定义数组 ...
- 第3章 C#中的委托和事件
.NET框架中的委托和事件 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- IP验证
function isIP(str) { var IP = '(25[0-5]|2[0-4]\\d|1\\d\\d|\\d\\d|\\d)'; var IPdot = IP + '\\.'; var ...
- TCP/IP、Http的区别
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据.关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍:“我们在传输数据时,可以只 ...
- HTML5和CSS3基础教程(第8版)-读书笔记(2)
第7章 CSS构造模块 7.1 构造样式规则 样式表中包含了定义网页外观的规则.样式表中的每条规则都有两个主要部分:选 择 器(selector) 和 声 明 块(declaration block) ...
- Asp.net MVC生命周期
Asp.net应用程序管道处理用户请求时特别强调"时机",对Asp.net生命周期的了解多少直接影响我们写页面和控件的效率.因此在2007年和2008年我在这个话题上各写了一篇文章 ...