单元测试(三)-模拟对象
前面使用了桩对象来解除被测代码对外部的依赖,以便于独立地测试代码的内部逻辑。但桩对象只能测试系统的返回值或者状态的改变,如果要测试对象之间的交互,则需要使用模拟对象。
1 三种测试类型
a) 三种类型的测试分别为:
Value-based testing测试方法的返回值
State-based testing 测试状态的改变
Interaction testing 测试对象之间的交互
b) 交互测试测试的是某种特定的行为(如给另一个对象传递信息)。作者认为,交互测试应该尽量少用,在能使用值测试和状态测试的场合,就不要使用交互测试,因为交互测试会增加复杂度
c) 用自动给植物浇水的装置举例来说明状态测试与交互测试的区别:状态测试就是在一天结束后,通过查看土壤中的水分、植物的生长情况来测试浇水效果;而交互测试则要实时记录浇水次数、水量等信息。状态测试得出结论需要一定的过程和准备工作;但交互测试实施起来却不方便
d) 桩对象用于值测试和状态测试,模拟对象则用于交互测试
2 桩对象与模拟对象的区别
模拟对象是一种可以记录对自身调用情况的伪对象。模拟对象可用于交互测试,可认为是在桩对象的基础上,增加了记录调用情况的功能。它们之间的区别如下:
a) 桩对象(stub)替代被测代码对第三方对象的依赖,然后要针对被测代码进行断言
b) 模拟对象(mock)则模拟第三方依赖,然后为了判断被测代码是否与第三方依赖正确交互,要针对模拟对象进行断言
c) 模拟对象可以让测试失败,但桩对象不会
3 模拟对象的应用
a) 还是使用之前的LogAnalyzer类,假设业务场景为:当文件名称太短时,调用web服务发送信息。但web服务是外部依赖,web传输也比较费时,而且现在测试的是LogAnalyzer对web服务的调用行为(交互)。
LogAnalyzer如下
在测试代码中,将FakeWebService对象作为模拟对象传递给LogAnalyzer,并要针对FakeWebService对象进行断言。
在模拟对象中,属性LastError会记录上一次交互的内容以供断言
b) stub和mock有时需要同时使用,比如测试场景改为:调用WebService时可能会发生异常,如果有异常,则发送邮件给管理员,测试要做的是观察发送邮件行为是否正确。
LogAnalyzer比上一个的复杂
在测试代码中,FakeWebService作为桩对象,FakeEmailService作为模拟对象,首先设置FakeWebService要抛异常,最后针对FakeEmailService记录的邮件内容进行断言,测试LogAnalyzer与其的交互是否正确
可以设置FakeWebService是否要抛异常
FakeEmailService作为模拟对象,与上一个例子中FakeWebService的职责类似,记录交互信息以供断言
以上便是stub与mock的区别,以及mock的基本使用方法,在第二个例子中,同时使用了stub与mock,最后针对mock进行断言,这也是mock使用的基本原则,一个测试中最多只能有一个mock,其它都为stub;mock存在时便只对mock断言;在一个测试中只应该验证一件事,如果有多个mock意味着在测试不止一件事。
参考资料:
The Art of Unit Testing with examples in C#, 2nd Edition by Roy Osherove