001-用Microsoft Fakes隔离测试代码
单元测试是对程序的小单元进行测试,一个测试不应该包含两个或更多单元,它对方法、属性的编码正确性进行验证。但是一个方法往往又会调用其他的方法或属性,这些统称为外部依赖。因为外部依赖会影响程序单元的测试结果,要避免这样的情况就不得不使用一些外部依赖的模拟工具进行隔离(Isolate),比如Microsoft Fakes、Moq、Rhino Mocks、Type Mock。
1.什么是Microsoft Fakes?
Microsoft Fakes可以提供成员模拟的方法,以方便进行单元测试。如果不使用模拟方法,我们要关心很多东西,如数据库的数据变化、接口调用导致的变化,文件和其他资源的访问等问题。使用模拟我们则可以只关心我们需要测试的那部分逻辑。
Fakes通过使用stub或者shim来替换应用程序的某个部分,从而起到隔离代码的作用。因为被测试代码完全独立,所以如果测试失败,原因就是被测代码本身。
Fakes提供了两种类型成员的方式,一下两种方式的替代实现都可以由委托来重新实现。
- Stub Type,存根类型
--用实现相同接口的备用代码来替换类,处理依赖于接口或抽象方法的情况。
--要使用Stubs,必须在设计应用程序时,使每个组件仅仅依赖于接口,而不是其他组件。(组件,是指程序集中的一个或几个类的集合)
- Shim Type,填充类型
--用测试中提供的shim代码来替换具体的方法调用,处理依赖于具体类的情况。

2.选择stub还是shim?
在VS解决方案内的调用,使用stubs。而其他应用的程序集调用,则使用shims。因为解决方案内可以定义接口,实现组件间的解耦。但是外部的程序集,比如System.dll,没有提供独立的接口定义,所以必须使用shim。
其他的一些考虑有:
- 性能。运行时使用shim重写会影响性能,而stub使用的是虚方法,则无此问题。
- 静态方法(Static methods)和密封类型(Sealed types)。stub类型只可以重写虚方法,因此它不能用来替代静态方法、非虚方法、密封类中的方法等。
- 内部类型(Internal types)。对于标记了InternalsVisibleToAttribute的内部类型,stubs和shims都可以起作用。
- 私有方法(Private methods)。如果private方法签名上的所有类型都是可见类型,那么可以通过shim来替换实现,而stub只能替换可见方法。
- 接口和抽象方法。stub提供了可用于测试的接口和抽象方法的实现。因为没有方法体,shims不能检测接口和抽象方法。
总之,我们建议你使用stub类型去隔离你的代码基(codebase)中的依赖,把组件隐藏在接口后面。shims用于隔离没有提供可测试API的第三方组件,或者那些耦合很大、可测试性很差的代码。
3.开始使用stubs
(1)注入接口
要使用stubs,你必须以这样的方式编写代码,即没有显式指定应用程序中的其他组件。应该使用接口声明变量和参数,其他组件通过工厂模式来传递或创建其他组件的实例。例如如果StockFeed是一个应用程序中的其他组件类,那么下面这样写是非常不好的:
return (new StockFeed()).GetSharePrice("COOO");
实际上,应该定义一个由其他组件实现的接口,并且我们在测试中就可以使用stub来实现这个接口。
public int GetContosoPrice(IStockFeed feed) { return feed.GetSharePrice("COOO"); }
(2)添加Fake的程序集
a.在解决方案资源管理器中,打开测试项目的引用列表。
b.添加Fakes的程序集应用
c.重新生成解决方案
(3)在测试中,构建stub的实例,并为它提供代码
[TestClass] class TestStockAnalyzer { [TestMethod] public void TestContosoStockPrice() { // Arrange: // Create the fake stockFeed: IStockFeed stockFeed = new StockAnalysis.Fakes.StubIStockFeed() // Generated by Fakes. { // Define each method: // Name is original name + parameter types: GetSharePriceString = (company) => { return 1234; } }; // In the completed application, stockFeed would be a real one: var componentUnderTest = new StockAnalyzer(stockFeed); // Act: int actualValue = componentUnderTest.GetContosoPrice(); // Assert: Assert.AreEqual(1234, actualValue); } ... }

浙公网安备 33010602011771号