[转]前后端分离开模式下后端质量之管 —— 单元测试

正文转自:http://www.cnblogs.com/jesse2013/p/magic-of-unittesting.html\#3451709

 

概述

  于今,
前后端分离既是首选的一个支出模式。这对后端团队来说实在是一个吓信息,减轻任务而还注意。在测试者,就更加依懒于单元测试对于API以及后端业务逻辑的较验。当然单元测试并非以左右端分离流行之后才来,它怪已经在,只是鲜有人重视都真正能够用好她。而于上下端分离开模式下,特别是双方交付时间别很老的状况时,后端可能得更加地依懒于单元测试来保证代码的是。

  本文主要围绕单元测试展开,从单元测试的基本功概念说自,对比单元测试和合测试,同时我们尚会见聊一聊单元测试与测试驱动开发的别。在咱们了解了单元测试的定义之后,我们会追究一下争的单元测试算得上是好之单元测试,它们有哪些特征,如何用隔离框架来援助我们针对有的扑朔迷离的零部件进行测试。最后一个内容吧是本文想使阐释的严重性:
单元测试是开发人员写的,那么开发人员在描绘自己之代码的时节,如何增强协调代码的而是测试性?
什么样的代码算的直达是对单元测试友好之代码?
带在这些题材,我们及时便来起我们的单元测试之同。

目录

  1. 啊是单元测试?
    1. 单元测试与测试
    2. 单元测试与集成测试
    3. 单元测试与测试驱动开发
    4. 一个单元测试的事例
    5. Mock和Stub的区别
  2. 怎才好不容易好的单元测试?
    1. 测试用例都发怎么样?
    2. 自动化——持续集成
  3. 增长代码的可是测试性
    1. 整架构层面的设想
    2. 保持类似的援/依懒关系清晰,可注入
    3. 依懒于接口而未实现

嗬是单元测试?

  有人可能勾过单元测试,但是却未知道怎么而描写单元测试,有人理解为什么要描绘单元测试,但不确定哪些勾勒才是好的单元测试。但是对于“测试”
我们每个人且耳熟能详, 你省下面的效果是否如已相识?

C语言 1

单元测试与测试

  测试项目分为好多种:单元测试、集成测试、系统测试、压力测试、负载测试、验收测试等等
,我们今天休打算也未可知拓展系统性的介绍。作为开发人员,我们平常所说之“测试”。也就是说你代码写了了,老大问你测试通过了吗?你说了了,然后就好Check
in
代码了。这里的“测试”,实际上指的凡未整的机能测试。为什么说它们不完整,是以由标准测试的角度来讲,还欲定义规范的测试用例,用例写了事后还要开发以及测试人员一起评审等等 。
而我们只是于脑际中先行想了瞬间其当什么行事的,应该受我呀结果相当,然后运行一下,咦,还真是如此的,那咱们的测试就通过了。
会有些许Bug,就在我们这个预想有多细心了,往往有时我们只好想到死少一统份,这时候专业独立的测试人员就派上用场了。同时会开和测试的人头是杀有优势的,自己会管写出来的软件之质地,这为是现代迅猛开发集团所追求的,但是这样的食指总是少之又少。

  单元测试是由此将一个应用程序拆分成可测试的十足小之组成部分,然后将每一样片段及其他具有功能隔离开,单独对当时等同部分进行测试。而者“可测试的足足小之局部”就称“单元“,在C语言中一个单元可以是一个函数,在C#受到单元测试可以是一个像样。
如果拥有的单元都能够如我们所预期的健康办事,那么把他们统一起来便会保证最少不会见起异常惨重的谬误。

单元测试与集成测试 

  
为什么要把立即半码将出去对比,是为及时片起十分轻混淆,一不小心你就可能将单元测试写成并测试了,这也是胡单元测试有时候看起那么坏之重要性由。我们地方说单元测试是把各国一个单元孤立出来,在测试的时光不可知同任何其他的单元有另外关系,这是单元测试,反过来你要在您的测试代码中引入了另外一个单元,那若将开始小心,你是匪是现已初步写集成测试了。
当然有时往往不是引入了另的局部单元,有或是部分组件,下面列有了有单元测试和集成测试的重要特色,希望会拉大家别单元测试与合测试。

单元测试

  • 可是再运行的
  • 不止长期有效,并且返回一致的结果
  • 在内存中运行,没有外部依懒组件(比如说真实的数据库,真实的文书存储等)
  • 快捷回到结果
  • 一个测试方法只测试一个题目

并测试

  • 运用真实的外部依懒(采用真实的数据库,外部的Web
    Service,文件存储系统等)
  • 当一个测试之中或者会见多单问题(数据库正常确,配置,系统逻辑等)
  • 可在运作较长时间之后才返回测试结果

单元测试与测试驱动开发(TDD)

  测试驱动开发其实我们所以一个题材就是好说清楚,那即便是“你哟时写单元测试?”
有人精选在出之代码写了后再写,这样咱们的支付过程是:
理解需要-》编写代码-》针对代码结合需求写单元测试。后来大家发现,往往以写单元测试的时刻发现自己有些需求没有理解掌握,或者这些要求原本计划的时便不曾考虑到,所以又再度转原来的代码。
于是有人便说,为什么我们不干脆反过来? 先写单元测试,再写代码?
 所以我们付出的进程尽管成了这么:理解需要-》针对需求写单元测试 -》
编写代码让单元测试通过。 最开头是叫测试先行(TFD: Test First
Development) ,后来就是提高变成我们熟悉的”测试驱动开发”了。

  测试驱动开发极老之补益是,让开发人员更好之明亮需要,甚至是掏需求下再也展开开发。
当然,我们无容许一次性把具备的测试代码都勾出来后还写代码,这是一个重新迭代的过程:

C语言 2

  由于TDD不是咱本篇的显要内容,这里只有希望能够吃大家一个针对性TDD的浅认识的又了解及TDD与单元测试的牵连。到此,我们对于单元测试的概念就介绍的差不多了,接下是代码时间。:)
我们来达成一个真正的事例更形象之了解一下单元测试。

一个单元测试的例子

  那么问题来了,我们为此什么来案例来形容了一个单元测试的例子也?既然这样,那么我们就用前少首我们以世界模型驱动设计受到说道到之用户注册的例证吧。在用户的世界服务备受,UserService提供了一个Register的法门,通过用户称、邮箱与密码三独参数来创造一个用户之目标。
像所有注册逻辑一样,邮箱是未能够还的,这是咱们现在之圈子服务受到较主要的作业逻辑,所以我们的单元测试必须使埋至。
我们的测试

// UserServiceTests.cs

C语言 3C语言 4

C语言 5😉

 1 namespace RepositoryAndEf.Domain.Tests
 2 {
 3     public class UserServiceTests
 4     {
 5         private IRepository<User> _userRepository = new MockRepository<User>();
 6 
 7         [Fact]
 8         public void RegisterUser_ExpectedParameters_Success()
 9         {
10             var userService = new UserService(_userRepository);
11             var registeredUser = userService.Register(
12                 "hellojesseliu@outlook.com",
13                 "Jesse",
14                 "Jesse");
15 
16             var userFromRepository = _userRepository.GetById(registeredUser.Id);
17 
18             userFromRepository.Should().NotBe(null);
19             userFromRepository.Email.Should().Be("hellojesseliu@outlook.com");
20             userFromRepository.Name.Should().Be("Jesse");
21             userFromRepository.Password.Should().Be("Jesse");
22         }
23 
24         [Fact]
25         public void RegisterUser_ExistedEmail_ThrowException()
26         {
27             var userService = new UserService(_userRepository);
28             var registeredUser = userService.Register(
29                 "hellojesseliu@outlook.com",
30                 "Jesse",
31                 "Jesse");
32 
33             var userFromRepository = _userRepository.GetById(registeredUser.Id);
34             userFromRepository.Should().NotBe(null);
35 
36             Action action = () => userService.Register(
37                 "hellojesseliu@outlook.com",
38                 "Jesse_01",
39                 "Jesse");
40             action.ShouldThrow<ArgumentException>();
41         }
42 
43         public void RegisterUser_ExistedName_ThrowException()
44         {
45             var userService = new UserService(_userRepository);
46             var registeredUser = userService.Register(
47                 "hellojesseliu@outlook.com",
48                 "Jesse",
49                 "Jesse");
50 
51             var userFromRepository = _userRepository.GetById(registeredUser.Id);
52             userFromRepository.Should().NotBe(null);
53 
54             Action action = () => userService.Register(
55                 "hellojesseliu_02@outlook.com",
56                 "Jesse",
57                 "Jesse");
58             action.ShouldThrow<ArgumentException>();
59         }
60 
61     }
62 }

C语言 6😉

View
Code

   以斯事例中我们因此到了
Fluentassertions、XUnit这片只开源组件。另外Moq作为一个毋庸置疑的单元测试Mock框架为推荐给大家。

  • Fluentassertions:相对于.NET测试工具本身提供的Assert,Fluentassertions提供依据链式构建的片复人性、易懂的措施来助写起再好明的单元测试代码

    上面代码中我们所用到之ShoudBe、NotBe、以及ShoudThrow等艺术就是来于Fluentassertions,还有复多道可以交官方文档上查询。
  • Xunit:这是一个开源之单元测试工具
  • Moq:为了为单元测试可以完全脱离外部组件,我们得运用一些Mock对象与Stub对象,而Moq是一个开源的Mock类框架可以拉我们落实这些功能
    。我们地方代码中之所以到的MockRepository是我们团结用List封装的一个IRepository实例,支持增删改查,相当给我们拿多少持久化于内存中。

C语言 7C语言 8

C语言 9😉

 1 namespace RepositoryAndEf.Data
 2 {
 3     public class MockRepository<T> : IRepository<T> where T : BaseEntity
 4     {
 5         private List<T> _list = new List<T>();
 6 
 7         public T GetById(Guid id)
 8         {
 9             return _list.FirstOrDefault(e => e.Id == id);
10         }
11 
12         public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
13         {
14             return _list.Where(predicate.Compile());
15         }
16 
17         public bool Insert(T entity)
18         {
19             if (GetById(entity.Id) != null)
20             {
21                 throw new InvalidCastException("The id has already existed");
22             }
23 
24             _list.Add(entity);
25             return true;
26         }
27 
28         public bool Update(T entity)
29         {
30             var existingEntity = GetById(entity.Id);
31             if (existingEntity == null)
32             {
33                 throw new InvalidCastException("Cannot find the entity.");
34             }
35 
36             existingEntity = entity;
37             return true;
38         }
39 
40         public bool Delete(T entity)
41         {
42             var existingEntity = GetById(entity.Id);
43             if (existingEntity == null)
44             {
45                 throw new InvalidCastException("Cannot find the entity.");
46             }
47 
48             _list.Remove(entity);
49             return true;
50         }
51     }
52 }

C语言 10😉

MockRepository.cs

   我们吧可就此Moq框架在单元测试中临时初始化一个MockRepository

C语言 11C语言 12

C语言 13😉

 1 private readonly IRepository<User> _userRepository;
 2         private List<User> _userList = new List<User>();
 3         public UserServiceTests()
 4         {
 5             var mockRepository = new Mock<IRepository<User>>();
 6 
 7             // 初始化新增方法 
 8             mockRepository.Setup(r => r.Insert(It.IsAny<User>())).Returns((User user) =>
 9             {
10                 if (_userList.Any(u => u.Id == user.Id))
11                 {
12                     throw new InvalidCastException("The id has already existed");
13                 }
14 
15                 _userList.Add(user);
16                 return true;
17             });
18 
19             _userRepository = mockRepository.Object;
20         }

C语言 14😉

View
Code

  以单元测试代码中即初始化Mock
repository

  • 还灵敏:可以就初始化用到的道 
  • 重新胜似之控制能力:可以起外表(单元测试代码内)定义有的行 
  • 多态性:与任何单元测试类隔离,可以起例外的作为

Mock和Stub的区别

  因为来成百上千测试框架将Mock和Stub区别对待,初家也会见针对立即简单只概念表示含糊不清。简单的来说,Mock与
Stub最酷的界别是:

  Stub主要用来隔断其它的组件为单元测试可以健康的拓,我们不见面对Stub来进展Assert。

       C语言 15

  Mock则用来和测试代码进行互,可以说咱见面指向Mock来形容测试代码,也会对其进行
Assert来说明我们的代码。

  于咱们地方的代码中,我们才所以到了一个Mock(MockRepository),如果同是用户注册的事务,有哪些地方是咱们或需要因此到Stub的?
试想转手切实可行的挂号场景,如果用户注册成功了,
我们是未是亟需吃用户发送注册成功之邮件通知?这里发生一些急需小心的是,注册用户相关的代码属于我们圈子服务的天职,但是注册成功发送邮件、发送短信、甚至你若提到有体系有关的初始化操作都是属于应用层的事情。关于这点,大家还可回忆之前的一定量篇有关DDD的篇章。如果我们本着应用层的代码编写单元测试,那么我们就需要拿有些零部件比如邮件、日志等用Stub隔离掉,来管测试代码的运作。

哪才终于好的单元测试?

什么是一个好之单元测试?

  • 凡自动化的同而更运行的
  • 老轻实现
  • 频频发生因此
  • 任何人如轻松的触发一下按钮就可运行
  • 运转不见面花费尽长之时日
  • 一直返回同样的结果(如果您无移任何代码或参数)
  • 单元测试是全切断的,不应当产生另外其它的依懒
  • 当单元测试失败的时候,应该一眼便盼是以什么原因造成的是失败
  • 一个测试方法只验证一个case,只用一个Mock,Stub可以是基本上单
  • 好之命名,最好是得自道名相以下三独因素(所以一般我们下三段命名法):
    • 测试对象
    • 条件 
    • 该抱的结果

纪念知道您写的单元测试是不是好的单元测试么?

  • 2个星期,或者2单月竟然2年前写的单元测试还能运作而取得相同的结果也?
  • 团组织中的外人吗得以运作而2独月前写的单元测试么?
  • 好点击一下按钮就运行而有着的单元测试,并返回正确的结果吗?
  • 具备的单元测试可以在几分钟里就吗?

    C语言 16

测试用例都来怎样?

  写单元测试的代码可能是开发的一些加倍,这句话是的确的!在于你的单元测试用例覆盖的发差不多大面积,比如说我们地方针对用户注册就一个政工场景描写了3独测试用例,其实是远远不够的。

非预期的用例

  任我们地方很完全成功注册的用例,还是另外两个由邮箱与名称还设尚未注册成功的用例。这三个用户都是意料的,如果是无预期的,比如:

  • 而邮箱地址不是一个科学格式的信箱?
  • 使我邮箱不填?用户名不填?

疆测试

  • 只要自己的信箱名称或用户称长度超过最充分范围?

回归测试

  修改bug是均等宗难以了的工作,在纷繁且耦合度很高之体系下修改bug是同样件难以了且胆破心惊的事情,那么你感受一下:在纷繁且耦合度很高的体系下非决的改及一个bug会是平等种植怎样的情绪。我们后期维护代码的时候对新增的变更呢欲加上对应的测试代码来确保单元测试的完整性。

自动化——持续集成

  持续集成里面早已包含了单元测试的自动化。它提倡团开支成员要常常集成他们的劳作,甚至每天还可能发高频并入。而每次的集成都是经过自动化的构建来说明,包括活动编译、发布和测试,从而尽快地发现并错误,让集体能更快之开发内聚的软件。感兴趣的同桌可以活动了解,这是一个关于DevOps的话题,就未以本文作过多的发挥。光想象一下那种不随便谁出代码check
in都引发所有单元测试代码的活动运行,在单元测试覆盖的咸的情景下中心好过滤掉很多底潜在bug。 

增长代码的但是测试性

  我们大部分相逢的门类之富有大少看单元测试的代码大概是坐以下的几只由:

  • 管理者不重视 ,团队内没有是风气
  • 列不过艰难,根本无让日(可能吗产生官员不强调的原因)
  • 开发人员对于单元测试不成熟悉
    ,不亮什么样写好就测试。(不好的单元测试代码,写了说不定相当于白写,因为一向未曾人去运作它们)
  • 釜底抽薪方案中的事务层向未曾艺术写单元测试(耦合度太胜,重依懒,这是当自家排前3独困难后,常常遇到的尾声一道坎)

  关于终极一点是内需架构师、或者比起经验以开发者在极端开头规划系统结构的时刻用考虑到之。如果尽开始没有设想到怎么处置?
那顶好了,因为许多品种最好开头都无考虑到,所以我们的单元测试代码总是盛行不起。(可怜这无异范畴的架构师也是少之又少,倒是有成千上万绑架构师活跃于诸大论坛讲高起、各种分布式组件,能挽起袖子去重构/优化代码结构的口真少之又少。因为实际太累,而且整治不好还好失误,属于最为有挑战,但其实也一再不叫业主器重的同一桩苦差事)遇到比较多之问题(包括BAT级别之品种,可能外面的官气、整体架构图画出来那是很的良好,但是要是涉及到工作范围的代码….后面我不怕不说了。)

整体架构层面的设想

  如果我们本凡是还开始增加建筑同等法系统,那咱们得以怎么开始?或者说而我们来胆魄以及立志去重构一效系统,我们该于哪方向去运动?——
从DDD的支行架构说由

    分层
首先是通过分层把业务及其他基础零部件隔离开,不要受有犯邮件、记日志、写文件等这些基础零部件混合了咱们的事情,在应用层将世界工作以及这些吗应用服务的底蕴功能整合起来。在事先的同等首文章
《初探领域让设计——为复杂性工作而充分》有具体的牵线。

    C语言 17

  世界工作层无依懒

  以洋葱架构中,核心(Core)层是暨天地还是技术无关之基本功构件块,它包含了一些通用的构件块,例如list、case类或Actor等等。核心层不分包其他技术面的定义,例如REST或数据库等等。 

  C语言 18

  如果出依懒,请依懒于接口抽象,而不实际的实现,比如我们例子中之IRepository。这些架构思想实际已经充分老好老矣,但是我们大部分底品种还栖息于再次还老的老三层架构思想及,说好的艺极客们还失去哪了?

保类似的援/依懒关系清晰,可注入

  永不使用静态方案

  且毫无说有些面向对象的风味没有法用及,一旦开始了之口子。天喻乃的代码里面会依懒于有些只外表静态方法,并且完全没有章程在测试代码中将它mock掉,万一若以静态方法里面还要生另依懒,那对于单元测试来说就是均等庙会完毕。

  保一个类似具有的表引用易见

  1.  享有外部引用易见   2.  表面引用可注入/替换

  C语言 19

  除了构造函数注入以外,我们尚可利用构造函数注入、字段、以及艺术注入的章程,将我们的方替换掉。这种艺术不仅是指向单元测试友好,更是如出一辙种植理想的代码组织办法,是可能提供代码的易读性,以及可维护性的。若果明代码主要是让丁阅览的,只是偶然被机器执行一下。如果生跳槽经验的同桌应该都产生了那种到了一个店家,有一个坏复杂的体系,但是没外的文档(稍微好一些底或许会见生表字典)的感想,唯一了解系统业务的方式是play
with the system 然后,看代码。
对于种无法一眼望各国个类之间的干之代码,特别是一个看似里有好几百独方式、上万行代码的时候, 虽然我对涉及这种工作已深谙,但就底心态难免还是多少激(操)动(蛋)的。

 依懒于接口/抽象,而非实现 

  这点自己怀念呢便未待细述了,在单元测试这个情景里。我们任重而道远是用工作与非业务相关职能因此接口隔离开,那么我们以单元测试中虽可以充分灵巧的所以Mock或者Stub来替换。比如:读写文件、访问数据库、远程请求等等。

最后

   编写单元测试虽然简单,但是考验之也是细心和针对工作的知道程度。而且一再写单元测试代码所花的时刻较写功能代码还要多,在职责时间进度紧、又休给尊重的图景下,自己可怜少有人会积极性愿意去写。但是,好的单元测试代码确实于漫长会反映出其的价值。

作者:Jesse 出处:http://jesse2013.cnblogs.com/

正文版权归作者和博客园共有,欢迎转载,但未经作者同意要保留这个段子声明,且在文章页面明显位置让闹原文连接,否则保留追究法律责任的权。如果看还有助的话,可以接触一下右手下角的【推荐】),希望能持续的呢大家带来好的技艺文章!想与自家并向前步么?那即便【关注】)我吧。