Moq框架简单使用

 

系列目录

Moq库简介及安装

Moq简介

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

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

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

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

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

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

Moq安装

Moq安装非常简单,在Nuget里面搜索moq,第一个结果便是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对象时把它作为参数传入.

Moq基本配置

我们再为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方法,我们在调试模式下看看返回的结果是什么.

它返回了一个空集合,实际上不管我们提供的是什么样的字符串,它都返回一个空集合,这是默认行为,因为_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<IDataBaseContext<MyDto>>();
moq.Setup(a => a.GetElementById(It.Is<string>(t => 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<IDataBaseContext<MyDto>>(a =>a.GetAll()==new List<MyDto>(){new MyDto()}
&&a.GetElementById(It.IsAny<string>())==new MyDto()
&&a.GetElementsByName(It.IsAny<string>())==new MyDto[3]);
var all = obj.GetAll();
var one = obj.GetElementById("s");
var some = obj.GetElementsByName("somename");
Assert.Multiple(() =>
{
Assert.AreEqual(1, all.Count());
Assert.NotNull(one);
Assert.AreEqual(3, some.Count());
});
}

以上测试会通过.

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

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

简单使用Moq框架的更多相关文章

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

    系列目录 Moq库简介及安装 Moq简介 Moq是.net平台下的一个非常流行的模拟库,只要有一个接口它就可以动态生成一个对象,底层使用的是Castle的动态代理功能. 它的流行赖于依赖注入模式的兴起 ...

  2. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  3. PHP之简单实现MVC框架

    PHP之简单实现MVC框架   1.概述 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种 ...

  4. iOS开发网络篇—简单介绍ASI框架的使用

    iOS开发网络篇—简单介绍ASI框架的使用 说明:本文主要介绍网络编程中常用框架ASI的简单使用. 一.ASI简单介绍 ASI:全称是ASIHTTPRequest,外号“HTTP终结者”,功能十分强大 ...

  5. Maven+Spring+Hibernate+Shiro+Mysql简单的demo框架(二)

    然后是项目下的文件:完整的项目请看  上一篇 Maven+Spring+Hibernate+Shiro+Mysql简单的demo框架(一) 项目下的springmvc-servlet.xml配置文件: ...

  6. AsMVC:一个简单的MVC框架的Java实现

    当初看了<从零开始写一个Java Web框架>,也跟着写了一遍,但当时学艺不精,真正进脑子里的并不是很多,作者将依赖注入框架和MVC框架写在一起也给我造成了不小的困扰.最近刚好看了一遍sp ...

  7. 一个简单的web框架实现

    一个简单的web框架实现 #!/usr/bin/env python # -- coding: utf-8 -- __author__ = 'EchoRep' from wsgiref.simple_ ...

  8. 最简单的Java框架

    框架framework的目的是定义骨架式方案,处理各种相同的底层细节:而开发人员使用框架时,能够依照自己的需求实现自己的功能--仅仅须要填入自己的东西/flesh. 最简单的框架,类似于JUnit,它 ...

  9. [angularjs] MVC + Web API + AngularJs 搭建简单的 CURD 框架

    MVC + Web API + AngularJs 搭建简单的 CURD 框架 GitHub 地址:https://github.com/liqingwen2015/Wen.MvcSinglePage ...

随机推荐

  1. HelloPlatform

    #include <stdio.h> int main() { #if _PLATFORM_ == _PLATFORM_TRU64 printf("Hello _PLATFORM ...

  2. tomcat catalina

    脚本catalina用于启动和关闭tomcat服务器,是最关键的脚本 在tomcat里有此文件 写这个脚本的人为什么命名为catalina一开始我还以为是其初恋女友,后来才知道原来是一个小岛的名字.

  3. Nuxt项目支持import写法的最新解决方案

    最近在看Nuxt开发vue项目的视频,视频中讲到Nuxt项目不支持es6的import写法.并提供了解决方案: 1.在package.json中添加我标红的部分: "scripts" ...

  4. python--递归函数讲解

    递归算法是一种直接或间接调用自身算法的过程. 递归算法解决问题的特点: (1)递归就是在过程或函数里调用自身 (2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口. (3)递归算法解题通 ...

  5. Codeforces Round #603 (Div. 2) C. Everyone is a Winner! (数学)

    链接: https://codeforces.com/contest/1263/problem/C 题意: On the well-known testing system MathForces, a ...

  6. (尚032)Vue_案例_消息订阅与发布(组件订阅与发布(PubSubJS库))

    组件订阅与发布(PubSubJS库) npm info pubsub-js 安装库 npm install --save pubsub -js ============================ ...

  7. Javascript搞笑图,哈哈哈哈

  8. learning shell monitor prog function

    [Purpose]        Shell script monitor prog function   [Eevironment]        Ubuntu 16.04 bash env   [ ...

  9. 套接字编程简介: IPV4套接字地址结构/ 通用套接字地址结构/ IPV6套接字地址结构/新通用套接字地址结构

    IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>头文件中. struct in_addr { in_addr_t ...

  10. 洛谷P1962 斐波那契数列题解

    题目背景 大家都知道,斐波那契数列是满足如下性质的一个数列: • f(1) = 1 • f(2) = 1 • f(n) = f(n-1) + f(n-2) (n ≥ 2 且 n 为整数) 题目描述 请 ...