Mockito 框架用于单元测试
单元测试中的Mock
Mock和Stub是单元测试工作中经常听到的名词。那么什么是Mocking?简而言之,就是创建一个受自己控制的实例(instance)或实现(implemention),替换被测代码中的依赖部分。
所谓受控的实例,是指依赖的行为是按自己所预期那样设计的,因而可以消除运行时的不确定性,解决测试用例运行时的环境依赖等问题,只关注核心代码逻辑的测试。
用一个实际业务系统来举例子,大多数的应用分为三层,分别是用户接口(User Interface),业务层(Business Layer)和数据接入层(Data Access Layer)。如下图所示。
在图中可以看到,业务层有3个依赖,分别是数据接入层和两个其他的服务。设想这是一个地图App,那么它们包括:
- 一个比如Mysql的数据库或其他NoSql的数据库,用来存储数据。
- 一个外部的服务,例如定位服务,提供经纬度信息。
- 一个外部的服务,例如交通信息服务,提供实时交通信息。
若要用单元测试来测试这样一个地图App的业务逻辑,除非先部署好这3个依赖,否则测试用例就运行不起来。
采用Mock方法可以应对这种情况,不管依赖服务是否已经运行起来。因为我们将使用自己设定的结果替换掉依赖的服务。
测试替身(Test doubles)的分类
Mock本质上是一种“测试替身”——这是一个技术术语。“测试双”本质上是指一个被等效的实际对象实例或依赖所替代的对象。
测试替身有多种分类,具体来说:
1. Facks(伪替身)
伪依赖是一个和真实依赖一样能运行的实例,唯一的不同是Fack位于本地系统。
例如,在测试中使用一个简单的集合数据结构,或者内存数据,来代替实际依赖的生产数据库。
2. Stubs(打桩)
打桩是使用一个预设的返回值代替调用实际依赖组件而返回的结果。
3. Spies(监视)
顾名思义,Spy是在调用实际依赖函数的时候,提供一种监视机制。这样就可以验证代码是否调到了某个函数,并拿到传入的参数进行验证。
4. Mocks(模拟)
Mock是一种特殊的对象实例,集合了打桩、监视的功能。既可以设定打桩,或者预设的返回值,也可以在事后验证mock对象的方法是否被正确调用到了。
例如,有一个报表生成器的函数,它在运行过程中向指定的地址发送电子邮件。由于我们不想发送实际的电子邮件,在测试期间,EmailService会被一次又一次地mock(发送电子邮件的电子邮件方法在被调用时被配置为不执行任何操作)。在测试结束时,我们只需要验证邮件服务确实调用到了我们mock的方法,并传进去了正确的邮箱地址。
Mock 框架
几乎所有的语言都提供了不同类型的mocking 框架。这里以Java的Mockito来举例。
假设我们要对一个应用程序进行单元测试,该应用程序计算一个学生在所有科目中的总分,并将其写入数据库。函数如下。
public void calculateSumAndStore(String studentId, int[] scores) {int total = 0;for (int score : scores) {total = total + score;} // write total to DBdatabaseImpl.updateScores(studentId, total);}
我们要为方法calculateSumAndStore编写单元测试,但我们可能没有一个真正的数据库实现来存储总数。在这种情况下,我们将永远无法对该函数进行单元测试。
但是有了Mock之后,我们可以简单地传递一个用于数据库服务的Mock,并验证其余的逻辑。
测试代码的样例如下:
@Testpublic void calculateSumAndStore_withValidInput_shouldCalculateAndUpdateResultInDb() {// ArrangestudentScores = new StudentScoreUpdates(mockDatabase);int[] scores = { 60, 70, 90 };Mockito.doNothing().when(mockDatabase).updateScores("student1", 220);// ActstudentScores.calculateSumAndStore("student1", scores);// AssertMockito.verify(mockDatabase, Mockito.times(1)).updateScores("student1", 220);
}
我们在被测代码的父类中提供了一个mock的数据库对象mockDatabase,并在第6行通过打桩设定了它的返回结果:
(Mockito.doNothing().when(mockDatabase).updateScores(“student1”, 220);)
其中的要点是:
- 需要给mock对象里可能被调用的函数设置打桩结果。
- 创建打桩时的输入参数可以是特定的,也可以是通用化的。在本例中,打桩函数的入参就是特定的student1” & 220,因为我们明确知道代码调用它时的参数。
- 在验证结果时,我们检验:
- mockDatabase.updateScores方法被调用了
- 调用时传入的参数分别是student1” 和220
- uodataScores方法总共被调用了1次
我们接着改变一下测试代码,将打桩的入参从特定的“student1”改为anyString()和anyInteger(). anyxxx()是Mockito提供的匹配器,表示该类型任意的值。
测试代码如下:
@Testpublic void calculateSumAndStore_withValidInput_shouldCalculateAndUpdateResultInDb() {// ArrangestudentScores = new StudentScoreUpdates(mockDatabase);int[] scores = {60, 70, 90};Mockito.doNothing().when(mockDatabase).updateScores(anyString(), anyInt());// ActstudentScores.calculateSumAndStore("student1", scores);// AssertMockito.verify(mockDatabase, Mockito.times(1)).updateScores("student1", 220);}
这个时候,测试用例依旧是能通过的。
我们再改变一下代码,这次将验证语句中的值改掉。将220改成230.
Testpublic void calculateSumAndStore_withValidInput_shouldCalculateAndUpdateResultInDb() {// ArrangestudentScores = new StudentScoreUpdates(mockDatabase);int[] scores = {60, 70, 90};Mockito.doNothing().when(mockDatabase).updateScores(anyString(), anyInt());// ActstudentScores.calculateSumAndStore("student1", scores);// AssertMockito.verify(mockDatabase, Mockito.times(1)).updateScores("student1", 230);}
这时测试用例就通不过了,报异常信息:
Argument(s) are different! Wanted:
mockDatabase.updateScores(“student1”, 230);
-> at com.mocking.sampleMocks.StudentScoreUpdatesUnitTests.calculateSumAndStore_withValidInput_shouldCalculateAndUpdateResultInDb(StudentScoreUpdatesUnitTests.java:37)
Actual invocation has different arguments:
mockDatabase.updateScores(“student1”, 220);
告诉我们“student1”, 230这样的组合参数没有被调到。
小结
以上通过一个简单而直观的例子展示了使用Mockito进行mock的方法。
虽然简单,但只要知道了Mockito,其他语言、其他大多数通过mock进行的单元测试,流程和原理都是类似的。Mockito为广泛的mock需求提供了大量高级配置/支持,使用依赖注入注入模拟实例,提供了Spies来监视真正的方法调用并验证调用结果。
示例完整代码
接口 IDatabase.java
public interface IDatabase {public void updateScores(String studentId, int total);
}
被测代码 StudentScoreUpdates.java
public class StudentScoreUpdates {public IDatabase databaseImpl;public StudentScoreUpdates(IDatabase databaseImpl) {this.databaseImpl = databaseImpl;}public void calculateSumAndStore(String studentId, int[] scores) {int total = 0;for (int score : scores) {total = total + score;}// write total to DBdatabaseImpl.updateScores(studentId, total);}
}
测试用例类 – StudentScoreUpdatesUnitTests.java
public class StudentScoreUpdatesUnitTests {@Mockpublic IDatabase mockDatabase;public StudentScoreUpdates studentScores;@BeforeEachpublic void beforeEach(){MockitoAnnotations.initMocks(this);}@Testpublic void calculateSumAndStore_withValidInput_shouldCalculateAndUpdateResultInDb(){
// ArrangestudentScores = new StudentScoreUpdates(mockDatabase);int[] scores = {60,70,90};Mockito.doNothing().when(mockDatabase).updateScores(anyString(), anyInt());// ActstudentScores.calculateSumAndStore("student1", scores);// AssertMockito.verify(mockDatabase, Mockito.times(1)).updateScores("student1", 230);}}
Mockito 框架用于单元测试相关推荐
- 使用Mockito进行Java单元测试
Google在3月份推出了一个关于Android MVP架构的官方Sample,除MVP架构本身之外,在这个Sample中配备了完善的单元测试用例,这对学习如何在Android中进行单元测试具有极高的 ...
- Android测试最新框架,Android单元测试-常见的方案比较
前言 本文将介绍在Android Studio中,android单元测试的介绍和实现.相关代码托管在github上的AndroidJunitDemo中,涉及到的用例代码收集于google官方提供的测试 ...
- Mockito框架学习
Mockito框架学习 在做单元测试的时候,有的时候用到的一些类,我们构造起来不是那么容易,比如HttpRequest,或者说某个Service依赖到了某个Dao,想构造service还得先构造dao ...
- 使用Spock框架进行单元测试
阅读目录 2.1.1.单元测试是什么 2.1.2.单元测试的定位 2.2.1.单元测试的资料不够全 2.2.2.单元测试难以理解和维护 2.2.3.单元测试难以去除依赖 3.1.1.groovy 3. ...
- sql初学者指南_使用tSQLt框架SQL单元测试面向初学者
sql初学者指南 tSQLt is a powerful, open source framework for SQL Server unit testing. In this article, we ...
- Mockito一个用于Java开发的伟大的模拟框架
原文地址:https://www.codeproject.com/articles/516360/mockito-a-great-mock-framework-for-java-developmen ...
- boost::test模块命名函数参数框架的单元测试
boost::test模块命名函数参数框架的单元测试 实现功能 C++实现代码 实现功能 boost::test模块命名函数参数框架的单元测试 C++实现代码 #define BOOST_TEST_M ...
- Python使用pytest框架进行单元测试
在使用pytest框架进行单元测试之前,VsCode中需要已经安装Python插件,并且当前项目的环境中需要引入pytest依赖库: poetry add pytest 默认情况下单元测试功能是关闭的 ...
- 实验三,基于Unittest框架的单元测试
实验三,基于Unittest框架的单元测试 一,实验目的 (1) 掌握单元测试技术,并按单元测试的要求设计测试用例. (2) 能熟练应用功能性测试技术进行测试用例设计: (3) 能熟练应用结构性测试技 ...
最新文章
- 13委托和事件在观察者模式中的应用
- jsp 学习 第2步 - tag 使用
- Java如何查看死锁?
- MATLAB数据分析3
- iOS逆向工程(简单利用dumpdecrypted给ipa砸壳)
- python 判断文件夹或文件是否存在
- nginx查看配置文件nginx.conf路径
- MICROSOFT REPORT VIEWER 2012之无法加载相关的dll
- Spark(Shuffle)
- maven 打包javadoc乱码解决方案
- c性能大容量cket_5千左右预算,既轻薄(高颜值)又高性能的笔记本推荐(畅玩LOL、CF、DNF、流放之路、梦幻西游)...
- iphone手机如何修改Apple ID密码
- 【NLP-笔记】开篇
- 流行和声(5)minor7和弦
- 微服务入门到入土(07)-分布式搜索ElasticSearch
- nginx gzip压缩
- 【python】20行代码实现有道翻译api接口调用
- 2022春季中国餐厅周来了!集结16大城市600余家高端食府饕餮飨宴
- 单片机音频节奏灯_如何用单片机做出用音乐节奏来控制LED灯?
- mac 可以连上网,但是自带浏览器和谷歌浏览器打不开网页
热门文章
- JAVA学习从软件工程导论课自动出题软件编程项目开始
- C语言函数大全--f开头的函数(下)
- git强制覆盖分支代码 A branch -- B branch
- [2021年最新]国产时序性数据TDenige入门
- vue-tree-chart 组织架构-树形图-流程图(含鼠标右击事件)
- 项目经理如何让自己更强大,执行力很关键
- 【剑指 Offer_15】二进制中1的个数_PythonJava_逐位相与解法
- 微服务与Spring Cloud简介
- 备战Noip2018模拟赛3(B组) T2 Dance 开场舞蹈
- 一个学计算机的打字速度慢,小学初中学生打字速度慢的原因及解决办法