使用 Moq 测试.NET Core - Why Moq?

 什么是Mock

当对代码进行测试的时候, 我们经常需要用到一些模拟(mock)技术.

绿色的是需要被测试的类, 黄色是它的依赖项, 灰色的无关的类

在一个项目里, 我们经常需要把某一部分程序独立出来以便我们可以对这部分进行测试. 这就要求我们不要考虑项目其余部分的复杂性, 我们只想关注需要被测试的那部分. 这里就需要用到模拟(Mock)技术.

因为, 请仔细看. 我们想要隔离测试的这部分代码对外部有一个或者多个依赖. 所以编写测试代码的时候, 我们需要提供这些依赖. 而针对隔离测试, 并不应该使用生产时用的依赖项, 所以我们使用模拟版本的依赖项, 这些模拟版依赖项只能用于测试时, 它们会使隔离更加容易.

绿色的是需要被测试的类, 黄色Mock的依赖项

Mock技术带来的优点

使用Mock技术, 可以有如下的优点:

  • 提高测试运行速度, 例如可以模拟DB, Web Service等比较慢的服务, 以及算法等.
  • 支持并行开发, 例如实际的依赖项还没有完成开发, 或者等待其他团队开发依赖项.
  • 提高测试可靠性, 例如有时这个依赖项的bug太多了, 经常由于依赖项的原因导致测试失败, 那么就应该使用mock版本来验证我们自己写的代码.
  • 减少开发/测试成本, 有时程序可能依赖一些云服务, 这些服务是按调用次数收费的, 那么就可以使用Mock版本来节省这方面的开资, 当然了最后还是需要使用真正的服务测试才行; 有时候组建依赖项太费劲了, 就用mock版本吧, 省时省力.
  • 在有不确定性依赖项的情况下进行测试, 有些依赖项有不确定性, 可能无理由的造成测试失败, 这时候就应该使用mock版本的依赖.

单元测试

Mock技术通常在单元测试中使用, 可以使用xUnit来为.NET Core应用做单元测试, 这里有介绍xUnit的文章: https://www.cnblogs.com/cgzl/p/9178672.html#xunit

那么什么是一个单元? 

这个通常是由团队对系统的理解决定, 可以针对一个类, 也可以针对多个类.

单元测试通常具有以下特点:

  • 低级别
  • 高聚焦
  • 执行速度快
  • 容易测试所有执行路径上的代码

术语

  • Test Double (我认为可以翻译为测试替身), 是所有非真实依赖项的总称.
    • Fake, Fake是那种可以正常工作的实现, 尽管可以正常工作, 但是它们不可以用于生产环境, 例如EFCore里的内存数据库提供商.
    • Dummy, 有时候, 被测试方法需要一些参数, 但是这些参数实际上并没有用到, 这时就可以创建dummy, 它们的存在只是为了满足调用方法的参数要求.
    • Stub, (状态测试). 它可以使用很直接的方式模拟依赖项的行为. 例如我们可以使用Stub把相关数据放到内存里查询而不是查询真实的数据库; 如果某个测试类需要依赖项的某个Property的值, 那么stub就设定这个值就行.
    • Mock, (行为/交互测试). 与Stub不同的是, Mock期待的不是返回值, Mock期待的是动作的执行. 它是依赖项的动态包装, 它可以对哪个方法以什么样的顺序被待测试系统(SUT)调用的这个期待行为进行预编程. 也就是说被测试的系统只有按照特定的顺序调用mock依赖项的特定方法, 那么该系统才算测试通过.

还有其它的一些术语就不介绍了, 主要是这四个.

对于Stub 和 Mock ,可以看下面两张图例:

Moq

官网: https://github.com/moq/moq4

Moq框架可以用来创建dummy, stub 和 mock. 在本文里把这三个东西都叫做mock对象吧.

Moq使用一套API来创建stub和mock对象.

准备项目

一个简单的.NET Core控制台项目: https://github.com/solenovex/Moq-Tutorial-Code, 代码是里面的01 before.

该项目非常简单, 是关于球员转会业务, 它目前只有三个类. 

TransferApplication, 球员转会申请类:

TransferResult, 转会审批结果枚举:

还有TransferApproval, 转会审批类:

'

当前的逻辑是, 发起球员转会申请后, 进行审批: 如果总费用大于预算, 那么就直接拒绝; 如果总费用不超标, 并且球员小于30岁, 那么就批准; 但如果球员大于30岁, 并且是超级巨星的话, 这将由老板决定.

建立单元测试项目

在解决方案里建立一个xUnit类型的项目:

然后要保证该项目所用到的库都保持最新:

最后别忘了添加对FootballManager项目的引用:

打开Text Explorer, 可以看到里面有一个待测的单元测试:

做一个简单的单元测试

把UnitTest1改成下面这个简单的单元测试:

重新Build后, 可以看到单元测试的名称更新了.

点击Run All, 运行单元测试, 结果成功:

随后再添加一个简单的单元测试:

Build, 后就会出现这个测试:

Run All, 测试也会成功:

添加依赖

这时, 有一些需求的变化, 球员转会审批前, 需要通过体检.

首先在转会申请类里面添加两个球员的属性:

然后添加一个体检的接口:

这两个方法的作用是一样的, 但是调用方法略有不同.

但是此时, 该接口的实现类还没有开发完毕:

在转会审批类里面, 需要添加这个依赖, 使用的是接口:

在单元测试类里面, 我为转会球员添加了这两个属性, 但是审批类会报错, 因为没有加入依赖项:

所以测试的时候需要注入这个依赖项IPhysicalExamination, 但是PhysicalExamination类还没有做完(里面的方法都没有实现), 所以我们无法new出来这个类.

这时, 我们也许可以传null进去?

这时, 项目是不报错了.

跑单元测试, Run All:

测试失败, 抛出NullReferenceException. 而这个异常导致了测试无法正常进行.

所以, 我们需要Moq, 它可以提供一个Mock(模拟)版本的IPhysicalExamination, 并把它传递到审批类的构造函数里.

安装Moq

在单元测试项目添加Moq:

Moq的第一篇先到这.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏恰童鞋骚年

《大型网站技术架构》读书笔记之六:永无止境之网站的伸缩性架构

此篇已收录至《大型网站技术架构》读书笔记系列目录贴,点击访问该目录可获取更多内容。

10220
来自专栏信安之路

【读者投稿】wifi渗透-狸猫换太子

上期作者发布了一篇关于wifi钓鱼的方法,今天我来给大佬们带来一篇关于拿到wifi密码能干什么?

17800
来自专栏Golang语言社区

高可用性、负载均衡的mysql集群解决方案

一、mysql的市场占有率 二、mysql为什么受到如此的欢迎 三、mysql数据库系统的优缺点 四、网络服务器的需求 五、什么是mysql的集群 六、什么是负...

1.2K50
来自专栏黑白安全

iOS安全基础之钥匙串与哈希

本文最初是由Chris Lowe编写的,后来经过Ryan Ackermann(ios系统开发者)的修改,已经可以针对最新的Xcode 9.2,Swift 4,i...

12120
来自专栏影子

给Ionic写一个cordova(PhoneGap)插件

500100
来自专栏张善友的专栏

Sync Framework 2.0

Sync Framework 是一个功能完善的同步平台,实现了应用程序、服务和设备的协作和脱机访问。Sync Framework 提供了一些可支持在脱机状态下漫...

20570
来自专栏程序你好

SignalR介绍简单示例教程入门版

19240
来自专栏FreeBuf

SSL Strip的未来:HTTPS 前端劫持

作者 EtherDream 前言 在之前介绍的流量劫持文章里,曾提到一种『HTTPS 向下降级』的方案 —— 将页面中的 HTTPS 超链接全都替换成 HTTP...

31150
来自专栏指尖下的Android

内存和缓存的区别

今天看书的时候又看到了内存和缓存,之所以说又,是因为之前遇到过查过资料,但是现在又忘了(图侵删)。

1.3K20
来自专栏架构师之路

秒杀系统架构优化思路

一、秒杀业务为什么难做 1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表、群列表、个人信息); 2)微博系统,每个人读你关注的人的数据,一个人读多...

501100

扫码关注云+社区

领取腾讯云代金券