乐虎游戏|乐虎国际登录|欢迎你

gpk老虎机《Effective Unit Testing》 读书笔记 4 测量检验替身(Test Doubles)

日期:2020-02-06编辑作者:计算机资讯

老生常谈的好处

单元测试位于测试金字塔的最底层,越向上反馈的时间越长,实现的成本也越高。

gpk老虎机 1测试金字塔

测试的好处不仅仅是在编码时可以快速验证我们的程序是否满足预期,更大的好处是未来修改另一个功能时,可以帮助我们快速回归之前的所有测试,以确定此修改的影响范围。比人工的效率更高而且更加可靠。

三:Microsoft Fakes

Microsoft Fakes是UnitTestFramework下的高级组件。Microsoft Fakes可以帮我们隔离测试的代码。我们的代码/方法很多时候并不是完全独立的。一个方法会受到传入的参数的影响,一个方法也可能去调用另外一个方法才能正确的执行。所以当我们想要对一个方法进行单元测试的时候,如果有其他因素影响,那么我们很难确定这个方法失败错误的真实原因。所以我们进行单元测试的时候就要想办法消除这些影响。这个时候我们可以用Microsoft Fakes。Microsoft Fakes可以用来模拟接口,静态方法等,通过Microsoft Fakes模拟的方法,具有稳定的,可以预期的返回值,这个时候我们就可以认为消除了外部模块对单元测试的影响。

注意:Microsoft Fakes并不是所有版本的VisualStudio都支持,通常只有高级版本才包含。比如VisualStudio2015只有Enterprice版本才支持。

Microsoft Fakes主要有2大功能:

stub:

一个stub可以用来替换一个实现了某个接口的class。简单的说stub可以用来快速的实现一个接口,用来测试。使用stub来测试,你的程序必须是面向接口设计的。

shim:

一个shim可以用来替换一个你已经编译完成的库中的某个方法,当你的测试运行的时候,调用的是shim模拟的方法。shim可以用来模拟那些你无法修改的程序集的方法,比如.NET内置类库。

测试替身就是通过写一些代码来替代所测试代码会接触到的其他部分代码。首先需要回答的问题是我们为什么需要测试替身。作者列举了以下几点:

首先,这是一篇入门级的文章,高手可以无视。

一:什么是单元测试

style="color: #000000;">单元测试是对软件进行准确性验证的步骤。单元测试并不进行整个软件功能的测试,仅仅是对于最小工作单元的测试。一般最小工作单元就是指方法/函数等。

style="color: #000000;">这里并不打算对单元测试的概念及基础进行更多的介绍,需要了解更多的自行google。

  • Test Stub
  • Fake Object
  • Test Spy
  • Mock Object

参考资料

原文地址:

四:示例

1.使用Stub来模拟接口

style="color: #000000;">使用VisualStudio新建一个项目叫做MSFakeSample:

gpk老虎机 2

我们想象这样一个业务需求。我们需要把所有学生的名称组合成一个用逗号分隔的字符串。所有的学生信息存放在数据库里。

IStudentsRepository

这个接口描述的是Students仓储类需要实现的功能。

gpk老虎机 3

StudentsService

这类是用来实现Students的业务逻辑。

gpk老虎机 4

按照正常的开发逻辑,我们这个时候还需要去实现IStudentsRepository这个接口,也许是封装EF,也许是封装Dapper等等,然后才能去测试ConnectNames这个方法。但是尽管用EF等去实现了IStudentsRepository接口,我们的测试方法严重依赖了仓储层,数据库。也许为了测试我们还需要配置数据库连接,添加模拟数据到数据库。任何IStudentsRepository实现类的变化,或者数据库的变化,都可能影响到单元测试的结果。如果我们可以隔离这些变化那么我们的单元测试将变得非常完美。

有了Microsoft Fakes我们可以模拟一个实现了IStudentsRepository的类,来完全的隔离IStudentsRepository实现类的变化,或者数据库的变化。

创建单元测试类

在ConnectNames方法上右击,点击创建单元测试

gpk老虎机 5

在弹出的创建单元测试对话框上点击确定,程序就会自动创建一个以当前项目名称+Tests的项目,并且为你生成一个测试类StudentsServiceTests。

gpk老虎机 6

添加Fakes程序集

新增的Tests项目会自动引用MSFakeSample项目,在引用下右击MSFakeSample,点击添加Fakes程序集

gpk老虎机 7

点击添加Fakes程序集后VS会自动生成一个MSFakeSample.fakes的库并且引用。

gpk老虎机 8

使用stub来模拟接口

有了上面的这些操作,我们就可以开始真正的使用Fakes的Stub来模拟接口了。

gpk老虎机 9

我们直接new了3个StubIStudentsRepository类,并且用Lambda表达式直接定义了3个方法,分别返回null,空List,跟一个正常的List来描述3种情况。然后用Assert去断言跟预期的结果是否一致。

运行单元测试

Ctrl+T+R直接运行,会弹出单元测试运行窗口

gpk老虎机 10

可以看到ConnectNamesTest Passed,测试通过了。

通过使用Fakes的Stub功能,我们可以轻而易举的模拟接口。利用Lambda表达式来直接控制方法的返回值,使其稳定不变,从而为测试方法隔离接口。

不管你真正的接口如何实现,我测试的方法永远不会受到影响。

2.使用Shim模拟静态方法

太晚了,下回分解吧。晚安~

  • 隔离要测试的代码
  • 加快测试的执行
  • 让执行具有确定性
  • 模拟特殊的条件
  • 访问隐藏的信息

总结

花几分钟就可以写好一个单元测试,带来的回报却是巨大的。无论是后端还是前端都有相应的工具帮助我们轻松实现,做为 Java 程序员真的非常幸福,有 JunitMockitoPowerMockJMockit 这样一堆非常优秀的工具,前端也有很多优秀工具,就不一一列举,我在之前的几篇文章中也有单元测试的示例,欢迎有兴趣的同学翻看。

单元测试是一个软件保证质量的基础,也是作为一名优秀程序员的基本技能,后面会与大家在聊聊测试驱动开发,希望本文能给您带来收获。

二:UnitTestFramework

UnitTestFramework是微软开发的一套单元测试框架。类似的三方框架有NUnit等。UnitTestFramework为单元测试提供断言,自动化测试,管理界面等功能。与VisualStudio无缝集成。

如果Test Stub对于方法的简单实现(通常只有一行return代码)不能满足需求,就要用到下面的Fake Object了

在国内大部分的公司都不要求,或者难以要求,以至于工作了很多年的程序员都不知道如何去写一个正确的单元测试。当然,你会在网上看到很多文章告诉你如何使用 Junit,然后告诉你单元测试就是使用 Junit,真是无知又无耻,不仅写的人不知道什么是单元测试,还“人不倦”。实在忍受不了,便有了这篇文章。

模拟特殊的条件:比如断网的条件,或者某个外部服务意外报错的情况。这里作者搞笑了一把,说如果通过代码远程遥控乐高机器人把现实中的网线拔了当然是更牛逼。

更有效的方法-测试替身(Test Double)

测试替身可以帮助我们与真实的数据库、网络等其他外部依赖有效的隔离开,从而只测试我们关心的逻辑部分。

代码中不包含逻辑,作为替身只返回固定数据,不做测试以外的任何事。我们以 Java 为例,看一下代码:

public class LogStub implement Logger { public String getLevel() { return 'DEBUG'; }}

大多时候我们需要根据数据库中的数据进行判断,例如下面的代码:

public class UserService { private UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void register(User user) { User tempUser = userRepository.getUserByEmail(user.getEmail; if (tempUser != null) { throw new Exception("已经存在相同的邮箱,请重新输入。"); } userRepository.save; // 发邮件... }}

UserRepository 是对数据库的访问,然而我们并不需要真正的数据库,只需要对 UserRepository 制作一个基于内存的 Fake 对象即可,如下:

public class UserRepositoryFake extends UserRepository { private Map<String, User> usersMap = new HashMap<String, User>(); public UserRespositoryFake() { usersMap.put('111@111.com', new User; usersMap.put('222@222.com', new User; // ... } public void save(User user) { usersMap.put(user.getEmail; } public user getUserByEmail { return usersMap.get; }}

Fake 是更加接近于生产行为的替身,但并不能用于生产环境。

对于有返回值的方法我们可以使用 Stub 很容易测试,但对于没有返回值的方法,例如下面这段:

public class EmailSender { public void send(User user) { // 发邮件给用户... }}public class UserService { private EmailSender emailSender; public UserService(EmailSender emailSender) { this.emailSender = emailSender; } public void register(User user) { // 注册逻辑... emailSender.send; }}

在用户注册之后需要给用户发邮件,虽然发邮件不是我们关注的点,但我们仍然关心是否被成功调用,邮件的 send() 没有返回值,此时需要放出小间谍来帮助我们来完成测试:

class EmailSenderSpy extends EmailSender { private Boolean called = false; public boolean getCalled() { return this.called; } @Override public void sender(User user) { this.called = true; }}// 测试部分@Testpublic void should_called_send_email_to_user() { EmailSenderSpy spy = new EmailSenderSpy(); UserService userService = new UserService; User user = new User; assertEquals(false, spy.getCalled; userService.register; assertEquals(true, spy.getCalled;}

Mock 则是根据特定条件,返回特定的值,以验证代码的执行结果是否正确,典型的应用场景就是 Mock Server

这四种工具的分类界限比较模糊,我个人认为这样的分类并不合理,其他资料和框架的解释也不一致,但大神如此划分必定有其理由,读者不需要在此处过多纠结,在复杂的测试中可能一次就实现 StubFakeSpy 几种工具,只要知道实现方法,多多实践。

访问隐藏的信息:一般说来,如果测试代码需要访问类内部实现的话,多半是在类设计上了问题。对java来说意味着要访问一些私有变量之类。但是如果在测试中真发现需要为类添加仅仅为了测试的方法的时候,就该考虑用一个测试替身作为子类继承原来的类,把这些方法加到测试替身这个子类上。

单元测试的要求

好处说完了,怎么样测试才算是单元测试呢?

  • 有明确的预期
  • 可重复运行
  • 没有副作用
    • 时间
    • 随机数
    • 并发性
    • 基础设置
    • 持久化
    • 网络

有和都比较好理解,可这要如何做到呢?我们的业务系统做的所以事情都是依赖数据和网络的。

这需要把代码做更合理的划分,软件的最大价值是实现业务逻辑,而不仅仅是将数据放入数据库,假设我们的服务运行在一个内存无限大,永远不宕机的环境上,我们是否还需要使用数据库,有点扯远了。

要把业务逻辑中外部依赖中解耦出来,使职责更加的清晰也更好的测试。说了这么多估计一半同学已经睡着了,后面介绍一些极其有用方法,请往下看~

Mock Object:这是一种加强版的Stub,在所替代的方法上,规定当接收到特定的参数时候表现出指定的行为。同时又可以说是一种简化版的test spy,因为基本只是记录方法的入参和调用的次数,并不记录其他内部状态。这时候我们可以选择库而不是自己实现,对Java而言,如JMock,Mockito,EasyMock。实际上以上四种替身类型,除了Fake Object可能不太合适外,基本都可以用mock的库来实现。

本文由乐虎游戏发布于计算机资讯,转载请注明出处:gpk老虎机《Effective Unit Testing》 读书笔记 4 测量检验替身(Test Doubles)

关键词:

在SpringMVC和Mybatis中央银行使LocalData提姆e

!--LocalDateTime-- dependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId version2.9.2/version /dependen...

详细>>

Nginx的特性-实现优点

轻量级 CPU亲和 超强的静态文件管理技艺 缘由风度翩翩:IO多路复用epoll [TOC] 成效模块少 什么是IO复用? 1.四项确认...

详细>>

程序员,是知识工作者,还是体力工作者?

看了标题,你们或许以为我要吐槽程序员了。其实,这只是我在看《卓有成效的管理者》时,萌发的一个想法。 在现...

详细>>

Oracle与MySQL字段类型对照

序号 ORACLE MYSQL 说明 1 VARCHAR2 VARCHAR 2 NUMBER INT MYSQL有很多整数类型:tinyint、smallint、mediumint、int、integer、bigint 3 NUMBE...

详细>>