在上节中,完成了第一个单元测试,研究了各种特性,在本节,将介绍一些更实际的例子。SUT依赖于一个不可操控的对象,最常见的例子是文件系统,线程,内存和时间等。

本系列将分成3节:

  1. 单元测试基础知识
  2. 打破依赖,使用模拟对象,桩对象,隔离框架
  3. 创建优秀的单元测试

本节索引:

伪对象(fake) 桩对象(stub) 模拟对象(mock)

伪对象是一个通用术语,它即可指桩对象,也可指模拟对象。

桩对象是指对系统中现有依赖项的一个替代品,可人为控制。

模拟对象是用来决定一个单元测试是通过还是失败的伪对象。

说明:fake是stub和mock的统称,因为看起来都像是真的对象。如果是用来检查交互的就是模拟对象,否则就是桩对象

桩对象:

模拟对象:

为什么需要伪对象

  1. 外部依赖(系统中代码与其交互的对象,而且无法对其做人为控制)
  2. 反测试(而一旦测试中存在外部依赖,那么这个测试就是一个集成测试。运行慢,需要配置,依赖异常)

如何处理?

本质上都是外部依赖导致的,所以要做的是消除依赖。

  1. 分析接口
  2. 实现可人为控制的接口

注入桩对象

  1. 在构造函数上接受一个接口,并保存在一个字段里,以备后用。
  2. 保存在属性上
  3. 在调用方法前,使用方法参数,工厂类,依赖注入等

隐藏桩对象(由于生产环境等其他原因,我们不希望暴露桩对象)

  1. 使用条件编译
  2. 使用条件特性
  3. 使用internal和[InternalVisibleTo]

手工新建伪对象

使用桩对象(适用于模拟返回值,不适用于检查对象间的交互情况。)

这是非常常见的方式,但是这种方式受限制很多,如文件需要配置,运行慢。

  public class Config
{
public bool IsCheck(string name)
{
var str = File.ReadAllText("1.txt");
return str == name;//此处可能是大量的逻辑处理
}
}

改写注入

    public class Config
{
private IManager manager;
//提供注入接口
public Config(IManager manager)
{
this.manager = manager;
}
public bool IsCheck(string name)
{
var str = manager.GetConfig();
return str == name;
}
}
//真实的实现
public class FileManager : IManager
{
public string GetConfig()
{
return File.ReadAllText("1.txt");
}
}
//测试使用的实现
public class StubManager : IManager
{
public string GetConfig()
{
return "str";
}
}
//抽象出的接口
public interface IManager
{
string GetConfig();
}

测试代码

    [TestClass]
public class ConfigTests
{
private Config config; [TestInitialize]
public void Init()
{
config = new Config(new StubManager());
} [TestMethod]
public void IsCheckTest()
{
Assert.IsTrue(config.IsCheck("str"));
} [TestCleanup]
public void Clean()
{
config = null;
}
}

使用模拟对象(适用于对象之间的交互)

当上面的方法返回false的时候,需要调用别的web服务记录下。而web服务还未开发好,即使开发好了,测试的时间也会变长很多。

这里其实也体现了,stub的优点,可以任意的控制返回结果。

新建一个mock

    public class Config
{
private IManager manager;
public IWeb Web { get; set; }
public Config(IManager manager)
{
this.manager = manager;
}
public bool IsCheck(string name)
{
var str = manager.GetConfig();
var rst = str == name;
if (!rst)
Web.Log("错误");
return rst;
}
}
/// <summary>
/// 模拟对象
/// </summary>
public class MockWeb : IWeb
{
public string Erro { get; set; }
public void Log(string erro)
{
Erro = erro;
}
} public interface IWeb
{
void Log(string erro);
}

测试代码

    [TestClass]
public class WebTests
{
[TestMethod]
public void LogTest()
{
var web = new MockWeb();
//注入的方式非常多
var config = new Config(new StubManager()) { Web = web };
config.IsCheck("s");
//最终断言的是模拟对象。
Assert.AreEqual("错误", web.Erro);
}
}

注意:一个测试只有一个mock,其他伪对象都是stub,如果存在多个mock,说明这个单元测试是在测多个事情,这样会让测试变得复杂和脆弱。

使用隔离框架创建伪对象

隔离框架简介

手写stub和mock非常麻烦耗时,而且不易看懂等缺点。

隔离框架是可以方便的新建stub和mock的一组可编程API。

.net下常见的有Rhino Mocks,Moq

这里使用RhinoMocks做示例(将使用录制回放模式和操作断言2种)

 

录制回放

新建mock对象

来实现一个和上面mock的例子

        [TestMethod]
public void LogMockTest()
{
var mocks = new MockRepository();
      //严格模拟对象
var mockWeb = mocks.StrictMock<IWeb>();
using (mocks.Record())//录制预期行为
{
mockWeb.Log("错误");
}
var config = new Config(new StubManager()) { Web = mockWeb };
config.IsCheck("s");
mocks.Verify(mockWeb);
}

严格模拟对象:是指只要出现预期行为以外的情况,就报错。

非严格模拟对象:是指执行到最后一行,才会报错。

新建stub对象

        [TestMethod]
public void LogStubTest()
{
var mocks = new MockRepository();
//非严格对象
var mockWeb = mocks.DynamicMock<IWeb>();
//桩对象
var stubManager = mocks.Stub<IManager>();
using (mocks.Record())
{
mockWeb.Log("错误1");
stubManager.GetConfig();
LastCall.Return("str1"); //录制桩对象返回值
}
var config = new Config(stubManager) { Web = mockWeb };
config.IsCheck("str");
mocks.Verify(stubManager); //桩对象不会导致测试失败
mocks.VerifyAll(); //启用非严格对象,测试直到这里才会确认是否报错
}

 操作断言

        [TestMethod]
public void LogReplayTest()
{
var mocks = new MockRepository();
var mockWeb = mocks.DynamicMock<IWeb>();
var config = new Config(new StubManager()) { Web = mockWeb };
//开始操作模式
mocks.ReplayAll();
config.IsCheck("str1");
//使用Rhino Mocks断言
mockWeb.AssertWasCalled(o => o.Log("错误"));
}

  

注意:使用框架创建的动态伪对象,肯定没手工编写的伪对象执行效率高。

本文作者:Never、C

本文链接:http://www.cnblogs.com/neverc/p/4749197.html

[Test] 单元测试艺术(2) 打破依赖,使用模拟对象,桩对象,隔离框架的更多相关文章

  1. [Test] 单元测试艺术(1) 基础知识

    单元测试不是软件开发的新概念,在1970年就一直存在,屡屡被证明是最理想的方法之一. 本系列将分成3节: 单元测试基础知识 打破依赖,使用模拟对象,桩对象,测试框架 创建优秀的单元测试 本节索引: 单 ...

  2. 单元测试(四)-隔离框架NSubstitute

    之前学习了单元测试的基础知识,以及桩对象和模拟对象的不同作用.但在实际应用中,往往不会直接手写桩对象或者模拟对象,而是使用隔离框架动态的创建这些对象,这可以让测试变得更简便.快捷,还可以更好地应对复杂 ...

  3. .net单元测试——常用测试方式(异常模拟、返回值测试、参数测试、数据库访问代码测试)

    最近在看.net单元测试艺术,我也喜欢单元测试,今天介绍一下如何测试异常.如何测试返回值.如何测试模拟对象的参数传递.如何测试数据库访问代码.单元测试框架使用的是NUnit,模拟框架使用的是:Rhin ...

  4. Spring依赖注入 --- 模拟实现

    Spring依赖注入 --- 模拟实现 面向接口编程,又称面向抽象编程, 数据库如果发生更改,对应的数据访问层也应该改变多写几个实现,需要用谁的时候在service里new谁就可以了面向抽象编程的好处 ...

  5. 模拟new实例化对象。

    使用new和字面量的的方法是两种主流创建对象的方法,两种最终都能达到同样的实例化的对象,本章主要围绕new关键字来实例化一个对象并且讲一个不使用new但是完全与new实例化对象相同的例子. 在使用ne ...

  6. .NET单元测试艺术(3) - 使用桩对象接触依赖

    List 3.1 抽取一个设计文件系统的类,并调用它 [Test] public bool IsValidLogFileName(string fileName) { FileExtensionMan ...

  7. 重构12-Break Dependencies(打破依赖)

    有些单元测试需要恰当的测试“缝隙”(test seam)来模拟/隔离一些不想被测试的部分.如果你正想在代码中引入这种单元测试,那么今天介绍的重构就十分有用.在这个例子中,我们的客户端代码使用一个静态类 ...

  8. .NET Core之单元测试(二):使用内存数据库处理单元测试中的数据库依赖

    目录 定义一个待测试API 测试用例 为减少篇幅,隐藏了SampleEntity和SqliteDbContext 定义一个待测试API 如下,我们定义了一个名为Sample的API,其中有一个外部依赖 ...

  9. .NET单元测试艺术(1) - 单元测试的基本知识

    List 1.1 一个要测试的SimpleParser类 using System; namespace AOUT.CH1.Examples { public class SimpleParser { ...

随机推荐

  1. 用CSS box-shadow画画

    原理:找一幅画,每隔5 pixel取一个点的RGB,在CSS中用box-shadow描绘出这个点 Python from PIL import Image if __name__ == '__main ...

  2. MVC使用基架添加控制器出现的错误:无法检索XXX的元数据

    环境 vs2012 框架 mvc3 数据库  sqlservercompact4.0 出现的错误如下: “ ---------------------------Microsoft Visual St ...

  3. OAuth2.0和SSO授权

    一.OAuth2.0授权协议 一种安全的登陆协议,用户提交的账户密码不提交到本APP,而是提交到授权服务器,待服务器确认后,返回本APP一个访问令牌,本APP即可用该访问令牌访问资源服务器的资源.由于 ...

  4. 360随身WiFi驱动下载

    一场不算太好的体验,但还是解决问题了 360随身WiFi驱动下载地址 事情经过: 某天在家里组装起PC,才发现当时没有在这屋里预留网线接口,走明线穿堂过户肯定是不合适的,还是买个无线网卡吧 自然还是要 ...

  5. android studio 翻译插件

    插件下载地址 https://github.com/Skykai521/ECTranslation/releases 使用说明: http://gold.xitu.io/entry/573d8d92a ...

  6. 【Cocos2d-Js基础教学(2)类的使用和面向对象】

    类的使用和面向对象 大家都知道在cocos2d-x 底层是C++编写的,那么就有类的概念和继承机制. 但是在JS中,是没有类这个概念的,没有提供类,没有C++的类继承机制. 那么JS是通过什么方式实现 ...

  7. Winform 数据库连接app.config文件配置 数据库连接字符串

    1.添加配置文件 新建一个winform应用程序,类似webfrom下有个web.config,winform下也有个App.config;不过 App.config不是自动生成的需要手动添加,鼠标右 ...

  8. Objective-C学习备忘录:Clang编译器编译运行Objective-C代码

    我们都知道可以通过Apple公司的Xcode工具来学习Objective-C编程语言,但是能不能脱离XCode这个IDE进行Objective-C学习呢?当然是可以的.首先作为计算机科班出身的程序员都 ...

  9. RestTemplate 使用总结

    场景: 认证服务器需要有个 http client 把前端发来的请求转发到 backend service, 然后把 backend service 的结果再返回给前端,服务器本身只做认证功能. 遇到 ...

  10. DS1337 时钟芯片在 C8051F 上的实现

    一.DS1337介绍 DS1337串行实时时钟芯片是一种低功耗.全部采用BCD码的时钟日历芯片,它带有两个可编程的定时闹钟和一个可编程的方波输出.其地址和数据可通过I2C总线串行传输,能提供秒.分.时 ...