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);
    }
    ...
}

 

posted @ 2015-04-14 15:56  RunningYY  阅读(531)  评论(0)    收藏  举报