.net测试篇之测试神器Autofixture在几个复杂场景下的使用示例以及与Moq结合
为String指定一个值.
在第三节里我们讲了如何使用自定义配置加上一个自定义算法生成一个自定义字符串,然而有些时候我们仅仅是需要某个字段是有意义的,这个时候随便生成的字符串也满足不了我们的需求.在一些简单场景下,我们可以显式的给一个字段指定一个值.
看以下代码
[Test]
public void FixValueTest()
{
var fix = new Fixture();
var psn= fix.Build<Person>().With(a => a.Name,"xiaodu").Create();
}
这里的Build方法返回一个IcustomizationComposer对象,这个对象有很多方法,其中一个为with,可以指定一个要赋值的字段,然后给它指定一个值.这样生成出来的对象的指定字段的值就是我们确切想要的了.
两个属性有一定关系
前面我们讲到过一个很普遍的场景,与时间有关的业务往往要求结束时间大于开始时间,我们前面讲了一种自定义的处理方法.这种方法比较完美的实现是结合自定义Attribute来实现,然而为了实现测试去扩展现有项目代码有些不妥,我们采用的是基于特征的办法(即预先约定开始时间带字段名带有start,结束字段名带有end).这样也会带来问题,项目中的过多自定义惯例会给后来维护者带来不小的压力.并且它只解决了一个问题,实际业务中还可能有其它的关系:比如可能是一个int字段的值必须要大于另一个int字段值,用户的全名是由姓和名结合成的等等.并且最致命的一个问题是我们如果要给一个现有的项目写单元测试,现有项目早于我们的规则之前出现,它的字段已经确定了,这时候我们不太可能去修改业务字段去适应单元测试.这是一个不小的成本!
下面讲一下如何像上面一样通过行内配置解决这一问题.
我们看以下代码
[Test]
public void FixValueTest()
{
var fix = new Fixture();
var psn = fix.Build<CustomDate>().Without(a => a.StartTime).Without(a=>a.EndTime).Do(a =>
{
var dt = DateTime.Now;
a.StartTime = dt;
a.EndTime = dt.AddDays(3);
}).Create();
}
这里使用Without方法显式指示AutoFixture在生成对象的时候不要按照默认逻辑生成这两个字段,然后执行一个Do方法,这个Do方法接受一个Action类型委托,T即我们要Build的对象,我们通过这个Do方法来执行一些赋值操作.
注意Without是必须的,不然AutoFixture在生成对象的时候会覆盖Do方法,仍然执行它内部的生成逻辑.
AutoFixture会忽略Without里面指定的参数,其它没有忽略的按它内置的逻辑生成.
集合中元素之间有关系.
有一个这样的业务场,大学新生入学时,会给同学们生成一个惟一编号,这个编号一般是根据入学时间+院系编码+专业编码+自增字段生成的.假设我们要对学生管理系统进行测试,现在要模拟一批学生,我们可以用AutoFixture生成一个学生集合,然而学生的编码不是任意数字,必须是指定规则的一串数字.这里我们仍然可以通过Do函数来解决这个问题.
我们把Person类当作学生类
public string Code { get; set; }
[StringLength(10)]
public string Name { get; set; }
[Range(18,42)]
public int Age { get; set; }
public DateTime BirthDay { get; set; }
[RegularExpression("\\d{11}")]
public string Mobile { get; set; }
public string IDCardNo { get; set; }
测试代码如下
[Test]
public void FixValueTest()
{
var fix = new Fixture();
int inc = 1;
var students = fix.Build<Person>().Without(a => a.Code).Do(a =>
{
string code = $"{inc++:20070102000#}";
a.Code = code;
}).CreateMany(15);
以上测试代码中,20070102为固定值,后面四位为增加值.我们通过对数字格式化生成了15满足以上规则的学生编号.
AutoFixture结合AutoData注解.
在本章刚开始的时候我们就介绍了使AutoFixture与Nunit相结合,为Nunit提供测试数据.当时讲碰到一个问题就是它生成集合对象时默认一个包含三个元素的集合.并且也无法在AutoData注解里改变这个默认.这里我们讲下如何结合后来的章节的知识实现可以在注解中自定义生成元素集合的个数.这样,如果我们只是需要数据,就不需要每都次创建一个fix的对象然后再配置了.
我们要实现以上只需要创建一个类继承AutoData就行了.下面看看这个类如何创建的.
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute() : base(() => new Fixture(){RepeatCount=10})
{
}
}
我们前面的章节介绍过,可以在创建fixture时给Repeatcount参数指定值,这样就可以生成指定数量元素的集合了.
测试类添加上这个CustomAutoDataAttribute注解就可以生成包含有10个元素的集合啦.
[Test]
[CustomAutoData]
public void FixValueTest(IEnumerable<string> str)
{
Assert.True(str.Count() == 10);
}
这样虽然好了一些,但是仍然不够灵活,要是能做到可以手动指定每次生成的个数就好了.
这个其实就很简了.
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute(int count=4) : base(() => new Fixture(){RepeatCount=count})
{
}
}
我们给构造函数增加一个count参数就ok啦.
我们再来看一个更复杂一点的,就是上一节刚讲到过的一个日期必须晚于另一个日期的配置,如何做成是AutoData的配置.
由于DateTimeSpecimenBuilder是一个ISpecimenBuilder类型对象,它是通过fix.Customizations.add来添加的.我们再看上面的示例,我们的功能实际上通过给base的构造函数传入一个Func委托来完成的.而fix.Customizations.add方法返回的是void类型,因此无法在这里使用了.这里的配置更为复杂一些.
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute() : base(() => new Fixture().Customize(new ValidDateRangeCustomization()))
{
}
}
其中使用到的ValidDateRangeCustomization类定义如下
public class ValidDateRangeCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new DateTimeSpecimenBuilder());
}
}
我们在这里添加DateTimeSpecimenBuilder
这个builder是我们上节创建的.它的代码如下
public class DateTimeSpecimenBuilder:ISpecimenBuilder
{
private readonly Random _random = new Random();
private DateTime startDate = DateTime.Now;
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi != null && pi.Name.ToLower().Contains("start") &&
(pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?)))
{
var stDate = context.Create<DateTime>();
startDate =stDate ;
return startDate;
}
if (pi != null && pi.Name.ToLower().Contains("end") &&
(pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(DateTime?)))
{
var endDate = startDate.AddDays(_random.Next(1,20));
return endDate;
}
return new NoSpecimen();
}
测试代码如下
[Test]
[CustomAutoData]
public void FixValueTest(CustomDate custom)
{
}
通过以上讲解,应该基本的把自定义配置转成autodata配置的问题都能搞定了.
AutoFixture结合Moq
通过前面介绍我们可能已经发现AutoFixture在生成测试数据方面非常强大.然而它有一个不足:那就是它仅仅是在运行的时候通反射获取类型信息,然后根据一定算法为类型的字段进行赋值,因此如果一个类的构造函数里都是接口它就无能为力了.我们知道Moq则可以在编译阶段为接口生成代理类型.如果能将两者结合起来就完美了.AutoFixture可能听到了我们的呼声,特为AutoFixture制作了一个结合Moq的扩展.
为什要把二者结合起来
前面说过,AutoFixture结合Moq主要是为扩展
比如说有以下这样一个类型
public class XXXBll{
public XXXBll(Interface1 x1,Interface1 x2,Interface1 x3,Interface1 x4,Interface1 x5,Interface1 x6)
}
以上一个Bll类依赖6个注入对象,实际过程中可能有的bll远比这要多,可能是十几个甚至几十个.
我们通过New创建这个类型他带来维护上的麻烦,前面已经说过,如果某个依赖对象移除了,则测试代码也要改.这倒罢了,麻烦一点就算了,这里面还可能有一个致命的问题,那就是如果这个Bll还依赖于一个对象而不是接口,这样就更麻烦了.
public class XXXBll{
public XXXBll(Interface1 x1,Interface1 x2,Interface1 x3,Interface1 x4,Interface1 x5,Interface1 x6,SMSServicexxx)
private SMSService service;
}
比如说我们业务层还依赖于一个短信服务,这个服务是第三方提供的,它只有一个类,并没有接口.这便是AutoFixture与Moq结合的理想场景,AutoFixture创建对象,遇到接口由moq创建.此时可维护性与可读性都大大提高.
下面我们介绍如何结合二者.
首先,在Nuget包管理器里面输入autofixture automoq 进行搜索
其中红框标识的包即为我们想要下载的包.实际项目中,只需要安装下面的AutoFixture和这个包就行了,因为它依赖于Moq会自动下载Moq.
Person类现在改成如下这样
public interface IPerson { }
public interface IMember { bool IsMember(string name);}
public interface IDoWork { }
public class SMSService { }
public class Person
{
private readonly IPerson _person;
private readonly IMember _member;
private readonly IDoWork _doWork;
private readonly SMSService _service;
public Person(IPerson person,IMember member,IDoWork doWork,SMSService service))
{
_person = person;
_member = member;
_doWork = doWork;
_service = service;
}
public bool isMember(string name)
{
if (string.IsNullOrEmpty(name)) return false;
return _member.IsMember(name);
}
测试代码如下
[Test]
public void FixValueTest()
{
var fix = new Fixture();
fix.Customize(new AutoMoqCustomization());
var psn = fix.Create<Person>();
}
AutoFixture与Moq结合的工作是由AutoFixture来完成的,我们并不需要特别复杂的配置即可实现非常好的扩展性和可维护性.这里的关键代码就是在Customize方法里传入一个AutoMoqCustomization对象,这个对象是由AutoFixture提供的,并不需要我们自己创建.
我们启用调试模式查看以下生成的对象
可以看到前三个接口实体是由Moq生成的,而最后一个SMSService则是由AutoFixture生成的.这样就完美解决了我们的问题.
新问题解决
这个做又引入了一个新的问题:我们知道Moq出现的类型是一个默认实现,没有任何功能,它会把默认值赋值给值类型,把null赋值给引用类型.比如以上IMember里的IsMember只是会返回默认值false,而实际测试中我们要根据用户名类型用户是否是会员,有的是,有的不是,如果全返回false显然对单元测试不利,更为要命的是很多方法如果是null就抛出异常或者返回了,这就会导致业务方法很快返回,很多业务代码会覆盖不到.
我们知道.Moq可以通过配置让moq的属性或者方法返回指定值.然而看我们以上测试代码,没有一行跟Moq有关.这该怎么办呢.
我们仍然通过示例讲解
[Test]
public void FixValueTest()
{
var fix = new Fixture();
var member = fix.Freeze<Mock<IMember>>();
member.Setup(a => a.IsMember(It.Is<string>(t => t.Contains("vip")))).Returns(true);
fix.Customize(new AutoMoqCustomization());
var psn = fix.Create<Person>();
Assert.True(psn.isMember("vipxiaoming"));
}
与前面相比,我们这里使用了fix对象的Freeze方法,后面创建接口的模拟实现的时候会自动调用这个冻结的对象.
冻结的这个对象是一个Moq对象,我样我们就可以像以前在Moq章节里讲到过的方法来配置它了.
.net测试篇之测试神器Autofixture在几个复杂场景下的使用示例以及与Moq结合的更多相关文章
- .net测试篇之测试神器Autofixture基本配置一
系列目录 实际工作中我们需要的数据逻辑万千,千变万化,而AutoFixture默认是按照一定算法随机生成一些假数据,虽然这在多数时候是ok的,但是可能不能满足我们的所有业务场景,有些时候我们需要进行一 ...
- .net测试篇之测试神器Autofixture Generator使用与自定义builder
有了上一节自定义配置,很多问题都能解决了,但是如果仅仅是为了解决一个简单问题那么创建一个类显得有点繁重.其实AutoFixture在创建Fixture对象时有很多方便的Fluent配置,我们这里介绍一 ...
- Maven测试篇
maven的生命周期: 讲解Maven测试篇之前将首先介绍一下Maven生命周期的相关概念,如果你熟知这部分概念可以略过此小节内容. 大多数时候,我们在构建一个项目时,不外乎是对其进行清理.编译.测 ...
- Java自动化测试框架-11 - TestNG之annotation与并发测试篇 (详细教程)
1.简介 TestNG中用到的annotation的快速预览及其属性. 2.TestNG基本注解(注释) 注解 描述 @BeforeSuite 注解的方法只运行一次,在当前suite所有测试执行之前执 ...
- 十一、Abp vNext 基础篇丨测试
前言 祝大家国庆快乐,本来想国庆之前更新完的,结果没写完,今天把剩下的代码补了一下总算ok了. 本章节也是我们后端日常开发中最重要的一步就是测试,我们经常听到的单元测试.集成测试.UI测试.系统测试, ...
- 项目Alpha冲刺(团队)-测试篇
格式描述 课程名称:软件工程1916|W(福州大学) 作业要求:项目Alpha冲刺(团队)-代码规范.冲刺任务与计划 团队名称:为了交项目干杯 测试用例:测试用例文档.zip 作业目标:描述项目的测试 ...
- <转>iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!
原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新 获取"产品付费数量等于0 ...
- app测试与web测试的区别
1.从功能测试的来讲的话,在流程和功能测试上是没有区别的.系统测试和一些细节可能会不一样. 那么我们就要先来了解,web和app的区别. web项目,一般都是b/s架构,基于浏览器的,而app则是c/ ...
- 用 Python 测试框架简化测试
用 Python 测试框架简化测试 摘要:本文将向您介绍了三种流行 Python 测试框架(zope.testing,py.test,nose)的基本特性,并讨论新一代的测试风格. 最近出现了行业级的 ...
随机推荐
- Spring Boot 中 Redis 的使用
Spring Boot 对常用的数据库支持外,对 Nosql 数据库也进行了封装自动化,如Redis.MongoDB等,本文主要介绍Redis的使用. Redis 介绍 Redis 是目前业界使用最广 ...
- C语言中的“>>”和“
先说左移,左移就是把一个数的所有位都向左移动若干位,在C中用<<运算符.例如: int i = 1; i = i << 2; //把i里的值左移2位 也就是说,1的2进制是00 ...
- IrisSkin2.dll 添加皮肤
使用说明:把控件拖到你的form上,只需一行代码,即可实现整个form包括其所有控件的皮肤的更换,总共有几十套皮肤供使用,非常方便.省去你设计开发软件皮肤系统的时间和精力.全部源代码就一行: skin ...
- Promise异步编程解决方案
Promise是ES6中新增的异步编程解决方案,体现在代码中它是一个对象,可以通过 Promise 构造函数来实例化. 其最基本的使用 new Promise(function(resolve,rej ...
- 物联网网关MQTT应用与配置测试介绍
1.MQTT介绍: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),作为除Modbus外最常用的协议之一,因其基于发布/订阅的模式,具有资源消 ...
- Java秒杀系统实战系列~商品秒杀代码实战
摘要: 本篇博文是“Java秒杀系统实战系列文章”的第六篇,本篇博文我们将进入整个秒杀系统核心功能模块的代码开发,即“商品秒杀”功能模块的代码实战. 内容: “商品秒杀”功能模块是建立在“商品详情”功 ...
- Rust写时复制Cow<T>
写时复制(Copy on Write)技术是一种程序中的优化策略,多应用于读多写少的场景.主要思想是创建对象的时候不立即进行复制,而是先引用(借用)原有对象进行大量的读操作,只有进行到少量的写操作的时 ...
- ubuntu 13.10 eclipse 菜单栏不可用的问题
最近手贱,从官网上下载了最新的版eclipse,把一原来的3.8版本替换了,然后就发现eclipse菜单栏废了,吓我一跳,以为Ubuntu又初问题,又重新弄了一次eclipse(我直接用的压缩包),上 ...
- poj 1503 高精度加法
把输入的数加起来,输入0表示结束. 先看我Java代码,用BigINteger类很多东西都不需要考虑,比如前导0什么的,很方便.不过java效率低点,平均用时600ms,C/C++可以0ms过. im ...
- web图形验证码逻辑
逻辑:前端生成一个UUID以URL方式发送给后端,后端准备Redis数据库缓存数据,后端拿到UUID后,调用captcha.generate_captcha()生成图片和图片的标签,Redis数据库保 ...