.net测试篇之Moq框架简单使用

Moq简介

Moq是.net平台下的一个非常流行的模拟库,只要有一个接口它就可以动态生成一个对象,底层使用的是Castle的动态代理功能.

它的流行赖于依赖注入模式的兴起,现在越来越多的分层架构使用依赖注入的方式来解耦层与层之间的关系.最为常见的是数据层和业务逻辑层之间的依赖注入,业务逻辑层不再强依赖数据层对象,而是依赖数据层对象的接口,在IOC容器里完成依赖的配置.

这种解耦给单元测试带来了巨大的便利,使得对业务逻辑的测试可以脱离对数据层的依赖,单元测试的粒度更小,更容易排查出问题所在.

大家可能都知道,数据层的接口往往有很多方法,少则十几个,多则几十个.我们如果在单元测试的时候把接口切换为假实现,即使实现类全是空也需要大量代码,并且这些代码不可重用,一旦接口层改变不但要更改真实数据层实现还要修改这些专为测试做的假实现.这显然是不小的工作量.

幸好有Moq,它可以在编译时动态生成接口的代理对象.大大提高了代码的可维护性,同时也极大减少工作量.

除了动态创建代理外,Moq还可以进行行为测试,触发事件等.

Moq安装

Moq安装非常简单,在Nuget里面搜索moq,第一个结果便是moq框架,点击安装即可.

本示例中要使用到的代码如下

 public class MyDto{public string Name { get; set; }public int Age { get; set; }}public interface IDataBaseContext<out T> where T:new(){T GetElementById(string id);IEnumerable<T> GetAll();IEnumerable<T> GetElementsByName(string name);IEnumerable<T> GetPageElementsByName(string name, int startPage = 0, int pageSize = 20);IEnumerable<T> GetElementsByDate(DateTime? startDate, DateTime? endDate);}public class MyBll{private readonly IDataBaseContext<MyDto> _dataBaseContext;public MyBll(IDataBaseContext<MyDto> dataBaseContext){_dataBaseContext = dataBaseContext;}public MyDto GetADto(string id){if (string.IsNullOrWhiteSpace(id)) return null;return _dataBaseContext.GetElementById(id);}}

MyDto为业务层和数据层交互的对象,IDataBaseContext为数据层接口,MyBll为我们的业务逻辑层

我们要测试的是业务逻辑层的代码.这里业务逻辑类并没有无参构造函数,如果手动创建起来非常麻烦,里面的坑前面说过.下面看如何使用Moq来模拟一个IDataBaseContext对象

我们编写以下测试类

       [Test]public void SimpleTest(){var moq = new Mock<IDataBaseContext<MyDto>>();MyBll bll = new MyBll(moq.Object);var result = bll.GetADto(null);Assert.Null(result);}

由于bll的GetADto如果传的参数是null或者空就会返回一个null对象,因些返回的结果是Null,以上测试会通过.

这里我们首先创建了一个moq对象,它的Object属性就是我们要模拟的IDataBaseContext对象,我们在创建MyBll对象时把它作为参数传入.

我们再为MyBll添加以下方法

 public IEnumerable<MyDto> GetDtos(string name){if (string.IsNullOrWhiteSpace(name)) return null;var dtos = _dataBaseContext.GetElementsByName(name);return dtos;}

我们编写如下测试方法

       [Test]public void ShouldReturn_A_Collection_Of_Dtos(){var moq = new Mock<IDataBaseContext<MyDto>>();MyBll bll = new MyBll(moq.Object);var dtos = bll.GetDtos("sto");}

以上测试方法调用了bll的GetDtos方法,我们知道GetDtos内部调用了数据访问接口的GetElementsByName方法,我们在调试模式下看看返回的结果是什么.

640?wx_fmt=png

它返回了一个空集合,实际上不管我们提供的是什么样的字符串,它都返回一个空集合,这是默认行为,因为_dataBaseContext.GetElementsByName并不知道我们的真实逻辑是什么.

这样很显然并不是总能满足我们的要求,很多时候我们在测试业务逻辑层的时候需要具体的数据,然后才能继续往下走.

比如以下方法,我们获取数据库里的所有数据,然而通过一系列逻辑进行过滤,最终返回过滤后的结果.

 public IEnumerable<MyDto> GetAllDtos(){var all = _dataBaseContext.GetAll().ToList();if (!all.Any()) return Enumerable.Empty<MyDto>();var filteredDtos = all.Where(a => a.Age > 20);var orderDtos = filteredDtos.OrderBy(a => a.Name);return orderDtos;}

如果是默认行为(调用模拟的接口方法,引用对象返回null,集合返回空,简单对象返回默认值),则代码很快就返回了,if下面的业务逻辑测不到了.下面我们看下如何配置接口方法的返回值

这里其实主要用到了 新建moq对象的setup方法,我们可以在setup里设置方法,属性的值.

       [Test]public void ShouldReturn_A_Collection_Of_Dtos(){var moq = new Mock<IDataBaseContext<MyDto>>();moq.Setup(a => a.GetAll()).Returns(new List<MyDto>{new MyDto{Name="baidu",Age=15},new MyDto{Name="sto",Age=32},new MyDto{Name="zto",Age=24},new MyDto{Name="yto",Age=12}});MyBll bll = new MyBll(moq.Object);var dtos = bll.GetAllDtos().ToList();dtos.Should().HaveCount(2);dtos.Select(a => a.Name).Should().BeInAscendingOrder();}

我们看以上代码,我们我们让数据访问接口的代理对象返回一个MyDto类型集合,一共四个元素,由我们的业务可知,我们只要年龄大于20的元素,并且名字按正序排列.因此以上测试应该返回成功,实际上也是测试通过了.

带参数的方法设置

以上的GetAll是不带参数的,带参数的方法我们可以显式的指定一个参数,我们也可以使用Moq框架提供的方法来模糊指定参数,比如我们可以指定方法是任意字符,任意数字,任意范围的数字等.

我们再看前面的一个方法

 public MyDto GetADto(string id){if (string.IsNullOrWhiteSpace(id)) return null;return _dataBaseContext.GetElementById(id);}

这个方法接收一个类型为字符串的id,只要字符串不是空字符串或者null时我们都返回一个MyDto对象.

测试方法如下

        [Test]public void ShouldReturn_A_Dto_If_QueryBy_Id_With_Valid_Parameter(){var moq = new Mock<IDataBaseContext<MyDto>>();moq.Setup(a => a.GetElementById(It.IsAny<string>())).Returns(new MyDto());MyBll bll = new MyBll(moq.Object);var dto = bll.GetADto("afakeid");dto.Should().NotBeNull();}

这里我们使用到了Moq里的It.Is方法,这个方法接受一个Func<T,bool>类型的委托,我们的条件是不管它是一个什么样的string,总是返回一个new MyDto();

[warning]注意这里配置的是Moq对象(即moq.Object)的方法返回值,而不是bll对象的方法的返回值,如果我们传入的字符串是空字符串,则GetADto直接返回了null,数据访问对象就没机会执行了.

It里面还有很多静态方法,用于指定数字是否是否在某一范围,对象是否是列表中的对象,字符串是否满足正则等.语义都非常明确,大家可以自己研究一下.

指定参数的配置

以上使用到了It.IsAny方法.It里面还有一个Is方法,接受一个Func<T,bool>类型委托,用于指定对象为满足特定条件的对象,而不是任意对象.

Bll层新增以下方法

 public bool IsVip(string id){if (string.IsNullOrWhiteSpace(id)) return false;var dto = _dataBaseContext.GetElementById(id);if (dto?.Name?.Contains("sto")) return true;return false;}

我们判断一个dto是否是vip,如果传入id为null返回false,如果不是则获取一个对象,如果对象的名字包含sto关键字则返回true

比如我们知道id为9527的对象为sto,因此它是个vip,我们的测试方法如下

        [Test]public void ShouldReturn_True_If_Id_Is_9527(){var moq = new Mock&lt;IDataBaseContext&lt;MyDto&gt;&gt;();moq.Setup(a =&gt; a.GetElementById(It.Is&lt;string&gt;(t =&gt; t.Trim() == "9527"))).Returns(new MyDto { Name = "sto", Age = 24 });MyBll bll = new MyBll(moq.Object);bool isVip = bll.IsVip("9527");Assert.True(isVip);}

以上测试通过.

MOCk.Of

我们以上仅配置了接口代表的一个方法,有时候需要配置多个,这样需要多个Setup,这时候我们可以使用Mock.Of,注意Mock.Of创建出来的是一个代理对象,而不是一个mock对象.

       [Test]public void MockOf_Test(){var obj = Mock.Of&lt;IDataBaseContext&lt;MyDto&gt;&gt;(a =&gt;a.GetAll()==new List&lt;MyDto&gt;(){new MyDto()}&amp;&amp;a.GetElementById(It.IsAny&lt;string&gt;())==new MyDto()&amp;&amp;a.GetElementsByName(It.IsAny&lt;string&gt;())==new MyDto[3]);var all = obj.GetAll();var one = obj.GetElementById("s");var some = obj.GetElementsByName("somename");Assert.Multiple(() =&gt;{Assert.AreEqual(1, all.Count());Assert.NotNull(one);Assert.AreEqual(3, some.Count());});}

以上测试会通过.

注意以上的xxx==xxx并不是比较两个对象,Mock利用它进行赋值

很多初接触单元测试的朋友看完以上代码后可能感觉一脸懵,完全不理解利用moq在dao层生成一些看似无意义的假数据有什么意义,其实大家要明白单元测试的目的是什么,单元测试是以代码块为基础(通常是一个方法),测试这一个单元逻辑的正确性,在dao层,我们只关心这一层拿到数据后的处理逻辑.很多朋友可能知道ef可以搭建内存服务器来模拟真实数据库,这样也同样不依赖于外部的数据库.其实大家也可以这样做,也可以不这样而使用moq来模拟一个数据库连接上下文对象.因为在单元测试里,真实的数据是什么样的并不是首要关心的问题,而是这个代码单元逻辑的正确性.如果是做集成测试,我们则需要模拟一个真实环境,这个时候我们就需要使用内存服务器甚至使用外部服务器.当然,如果要做压力测试,我们还需要模拟产品运行时真实的物理环境,网络环境等条件(当然,很多时候直接在真实的运行环境进行测试了).总之我们要搞清楚不同的测试要解决什么样的问题,要达到什么样的目的,剩下的才是工具框架的使用.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314145.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LibreOJ #6207. 米缇(杜教筛 + 拉格朗日插值)

#6207. 米缇 推式子 ∑i1n∑j1ndK(ij)∑i1n∑j1n∑x∣i∑y∣j[gcd(x,y)1]ixkyk∑i1n∑j1n∑x∣i∑y∣j∑d∣gcd(x,y)μ(d)ixkyk∑d1nμ(d)dk∑i1nd∑x∣iixk∑j1nd∑y∣iyk∑d1nμ(d)dk∑i1nd∑x∣ixk∑j1nd∑y∣iyk∑d1nμ(d)dk(∑i1nd∑x∣ixk)2∑d1nμ(d)dk(∑x1ndxk∑x∣i…

Java修炼之路——基础篇——值传递

什么是值传递&#xff1f;引用传递&#xff1f; 值传递&#xff1a;值传递是将变量的一个副本传递到方法中&#xff0c;方法中如何操作该副本&#xff0c;都不会影响原变量的值。引用传递&#xff1a;引用传递是将变量的地址传递到方法中&#xff0c;方法中操作该变量&#xf…

A. 树与路径(树论/多项式/分治FFT)

A. 树与路径 首先考虑一个dp的方法&#xff0c;对于这种链划分的题目&#xff0c;有一个很重要的思想就是按照每个点的角度考虑&#xff0c;实际上链划分就是匹配问题&#xff0c;每个点只能出一条边和入一条边&#xff0c;所以我们拆点之后就是匹配&#xff0c;这也是网络流最…

.NET Core on K8S学习实践系列文章索引(持续更新)

近期在学习Kubernetes&#xff0c;基于之前做笔记的习惯&#xff0c;已经写了一部分文章&#xff0c;因此给自己立一个2019年的flag&#xff1a;完成这个《.NET Core on K8S学习实践》系列文章&#xff01;这个系列会持续更新&#xff0c;先发个草稿列表&#xff0c;后续更新&a…

Java修炼之路——基础篇——数据类型

基础数据类型&#xff1a; 整型&#xff08;byte short int long &#xff09;、浮点型、布尔型、字符型boolean:只表示一位的信息&#xff0c;true,false。默认为false&#xff0c;基本上占一字节char: 16位&#xff0c;2字节&#xff0c;Java内部使用Unicode字符集最小值是’…

dft + fft(模板)

dft(多项式相乘模板) /*Author : lifehappy */ #pragma GCC optimize(2) #pragma GCC optimize(3) #include <bits/stdc.h>using namespace std;const double pi acos(-1.0);const int N 3e6 10;struct Complex {double r, i;Complex(double _r 0, double _i 0) : …

P4245 【模板】任意模数多项式乘法

P4245 【模板】任意模数多项式乘法 https://www.luogu.com.cn/blog/AzusaCat/solution-p4245 首先这类问题指的是对于一个非NTT模数&#xff0c;我们如何计算多项式乘法&#xff0c;对于NTT不容易找到单位根&#xff0c;对于FFT又会爆精度。 方法1&#xff1a;三模数NTT 寻找…

2019-02-22-算法-进化

题目描述&#xff1a; 给定一个字符串&#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。示例 2: 输入: "bbbbb" 输出: 1 …

TomatoLog-1.1.0实现ILoggerFactory

TomatoLogTomatoLog 是一个基于 .NETCore 平台的产品。The TomatoLog 是一个中间件&#xff0c;包含客户端、服务端&#xff0c;非常容易使用和部署。客户端实现了ILoggerFactory&#xff0c;使用服务注入成功后即可使用&#xff0c;对业务入侵非常小&#xff0c;也支持通过客户…

HDU 4609 3-idiots(FFT)

3-idiots 思路 多项式卷积的经典应用了&#xff1a; 看样例一: 4 1 3 3 4我们用多项式系数表示得到一个与aia_iai​有关的多项式&#xff1a;010210\ 1\ 0\ 2\ 10 1 0 2 1&#xff0c; 也就说明原序列有0个1&#xff0c;1个1&#xff0c;0个2&#xff0c;2个3&#xff0c;…

P5488 差分与前缀和(多项式/生成函数)

P5488 差分与前缀和 对于这道题需要我们快速对一个数列求解前缀和和差分&#xff0c;那么我们利用生成函数的知识&#xff0c;就可以知道实际上等价于乘一个多项式&#xff0c;然后我们就有了一个ln和exp的方法&#xff0c;然后比较简短的方法就是将其利用广义二项式定理展开&…

2019-02-23-算法-进化

题目描述&#xff1a;给定一个字符串 s&#xff0c;找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 示例 1&#xff1a; 输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。示例 2&#xff1a; 输入: "cbbd" 输…

Docker(二)-在Docker中部署Nginx实现负载均衡(视频)

一、前言在前面的文章中我们已经介绍了如何在Centos7系统中安装Docker以及利用Docker进行Asp.Net Core应用的部署。在本文中&#xff0c;我们将继续介绍利用Docker部署Nginx服务实现负载均衡&#xff0c;我们通过视频方式向大家进行演示。注&#xff1a;查看公众号历史文章&…

P4389 付公主的背包(生成函数/多项式)

P4389 付公主的背包 https://www.luogu.com.cn/problem/solution/P4389 经典生成函数问题 求解无限背包问题&#xff0c;我们可以将每个物品看作一个多项式&#xff0c;那么最后的结果就是这些多项式的卷积的系数&#xff0c;然后我们实际上就可以考虑分治NTT了&#xff0c;但…

HDU 6265 Master of Phi

Master of Phi 推式子 ∑d∣nϕ(nd)d给出了n的唯一分解形式我们先对上面式子进行化简通过组合枚举d&#xff0c;d的取值分别可以通过∏i1m∑j0qipij&#xff0c;一个多项式组合得到那么上述的式子有没有可能也通过这种新式得到呢&#xff0c;好像是可以的∑d∣nϕ(nd)d∏i1m∑…

Docker系列之镜像瘦身(五)

本节我们来讲讲在我们在构建镜像过程中不出问题&#xff0c;同时使得最后所构建的镜像文件大小尽可能最小。缓存(cache)Docker的优势之一在于提供了缓存&#xff0c;加速镜像迭代构建&#xff0c;我们知道构建镜像使用docker build命令&#xff0c;也就是说通过docker build的缓…

2019-02-24-算法-进化

题目描述 将一个给定字符串根据给定的行数&#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; L C I R E T O E S I I G E D H N之后&#xff0c;你的输出需要从左往右逐行读…

P5641 【CSGRound2】开拓者的卓识(多项式)

P5641 【CSGRound2】开拓者的卓识 https://www.luogu.com.cn/problem/solution/P5641 经典的讨论贡献的题目&#xff0c;如果一层一层展开就太暴力了&#xff0c;我们直接考虑每个数被计算了多少次&#xff0c;那么应该是它的左边放k-1个左括号&#xff0c;右边放k-1个右括号的…

2019-02-25-算法-进化

题目描述&#xff1a; 给出一个 32 位的有符号整数&#xff0c;你需要将这个整数中每位上的数字进行反转。 示例1&#xff1a; 输入: 123 输出: 321示例2&#xff1a; 输入: -123 输出: -321示例3&#xff1a; 输入: 120 输出: 21我的解法&#xff1a; public int reverse(…

一张图了解.Net Core和.NetFx和.Net Standard和Xamarin关系

一张图了解.Net Core和.Net Framework和.Net Standard和Xamarin关系总结.NET Standard是一项API规范&#xff0c;每一个特定的版本&#xff0c;都定义了必须实现的基类库。.NET Core是一个托管框架&#xff0c;针对构建控制台、云、ASP.NET Core和UWP应用程序进行了优化。每一种…