TDD学习笔记【二】---单元测试简介

大纲

Testing 的第一个切入点:单元测试。

本篇文章将针对单元测试进行简介,主要内容包含了5W:

  1. Why
  2. What
  3. Where
  4. Who
  5. When

而How 的部分,属于实现部分,将于下一篇文章介绍工具与简单的范例。

最后会提到测试用例所代表的意义与其重要性。

前言

单元测试,是开发人员最该写的测试程序,却也是最容易被忽略的测试。

大家常碰到的测试相关问题是:

  1. 往往一堆人写测试程序时,自以为是在写单元测试,却压根就不是单元测试,而是集成测试。
  2. 生产代码是我写的,如果测试程序也是我写,那有什么意义?所以应该给QA/QE 来写才能测出盲点。
  3. 我程序都写完了,跑起来也都对,这时写测试程序一点意义都没有。
  4. 测试程序要跑好久。
  5. 没有测试环境,要怎么写测试。

看完这几篇单元测试的相关文章后,希望大家可以获得一些想法,解决这些问题。

Why

先举几个在开发上常见的问题:

  1. 怎么让UI, Service, Data Access 平行开发?
  2. 要到真实环境方能测试程序无误
  3. 页面发生错误,到底是谁错了?
  4. 交付的程序,到底测过哪些东西了?
  5. 我改了这支程序,会不会害别的程序挂掉?

这些问题,可以有哪些Unit Test 相关的方式来解决:

  1. Unit Test 中使用stub/mock object,达到关注点分离
  2. Unit Test 使用stub/mock object 来模拟外部回传的数据
  3. 把input 值当做test case,跑一次Unit Test
  4. 交付的程序,包括Unit Test 程序
  5. 改完程序就跑一次Unit Test 吧

总而言之,没有被测试涵盖到的程序,即使它可能是对的,也没人敢拍胸脯保证。而有了测试用例来辅助说明与保护,至少可以拍胸脯保证,在这样的测试用例下,这个对象的设定,肯定如同预期般执行。

而单元测试可以提供回归测试的保护,在每一次异动完程式,可以单键执行就知道是否破坏了原本对对象行为的预期。

单元测试可以透过一些辅助设计,来达到与外部环境、服务、相依隔绝,而仅测试该物件本身的逻辑,以及与外部的互动是否符合预期。

造成问题的测试案例,往往是最珍贵的,因为最具代表性,也最具价值。因为它提供了我们修正bug的方向以及指标。而针对发生问题的测试案例,来执行单元测试,马上就可以知道是否是该对象的内部问题。

最后,单元测试由于具备与外界服务、相依隔绝的特性,所以可以帮助撰写实际的对象时,具有可测试性、低耦合性,彼此之间只相依于抽象或接口。进而通过IoC 的设计,让我们可以做到关注点分离,让开发各个对象的developer,可以透过接口来沟通,不相依于彼此实现,就能平行开发。

What

Unit Test 的定义与基本准则,如下图所示:

  1. 一个测试案例只测一种方法
  2. 最小的测试单位
  3. 不与外部(包括项目、数据库、网络、服务、对象、类型)直接相依
  4. 不具备逻辑
  5. 测试案例之间相依性为零

Unit Test的特性,一个字:FIRST。如下图所示:

  1. Fast:快速。
  2. Independent:独立。
  3. Repeatable:可重复。
  4. Self-Validating:可反应验证结果。单元测试不论成功或失败,都应该要从测试的reporting 直接了解其意义或失败原因。
  5. Timely:及时。单元测试应该恰好在使其通过的production code 之前撰写。

即:优良的单元测试具有以下的特点:简称为 A-TRIP。

  • 自动性(Automatic)
  • 完备性(Thorough)
  • 可重复性(Repeatable)
  • 独立性(Independent)
  • 专业性(Professional)

Where

单元测试的覆盖范围,以定义来说,单元测试是最小的测试单位,在面向对象中,就是测试一个方法。而方法一定会在某个对象上(即使是静态方法,也是在类型对象上)。

所以,单元测试通常就只关注在测试的目标对象上,而不管目标对象以外的东西,例如:目标对象所相依的实体对象、相依服务、相依资源、相依环境等等...

单元测试,简单的说,就是用来模拟外部如何使用这个目标对象,或是如何与这个目标对象互动。所以我们所撰写的单元测试程序,就是模拟与目标对象互动的程序。测试案例,就是该互动下的情境。接着验证物件的行为是否符合我们预期。

因此,单元测试程式,既然是模拟外部如何使用目标物件,所以也只会针对目标对象对外开放的方法。

而基本上,单元测试透过哪些方式去验证对象的行为符合预期呢?简单来说,有三种:

  1. 验证目标对象的回传值,如下图所示:
  2. 验证目标对象的状态改变,如下图所示:
  3. 验证目标对象与外部相依接口的互动方式,如下图所示:

Who

单元测试该由谁来撰写,就如同前言所说,最应该撰写的是developer,而非QA/QE。

就如Where段落所说,单元测试简单的说,是我们在设计对象的时候,预期外部该如何使用这个对象,进而衍生出对象该提供什么样的功能、具备什么样的行为。正因为对象的设计人、使用人,都是developer,所以单元测试的程式,当然由developer来设计,最为妥当。尤其由用的人来写,最为精准。

归纳几个基本要点:

  1. 想要达到什么需求,就是测试案例。而对象的设计,只是为了满足需求,需求即测试案例。即生产代码只为了满足测试程序上的测试案例。
  2. 设计对象的人员,才能知道对象该怎么给外面使用。
  3. 由外部使用对象的角度来设计测试案例。

When

撰写单元测试的时机点,简单??分成三个:

  1. 外部需要使用对象,并对其执行结果有所预期时( developing )
  2. feature的异动时( modifying )
  3. 出现非预期执行结果时( bug fixing )

想清楚,外部的需求是什么,才能设计出符合需求的对象。

当需求异动时,自然需要针对新的需求,来设计新的测试程序,因为这样才能驱使目标物件行为的改变。

当出现非预期的执行结果时,通常代表目标物件有着非预期的行为发生,有可能是当初测试案例不足,所以要增加我们的「预期」。

也有可能是当初预期的结果就错了,那其实就可以当作是第二点,需求的异动。(当然对使用端来说,还是属于bug,但对对象设计来说,测试案例方向就错了)

Test Cases的意义

大家买过3C产品或电器吧,基本上拿到一个东西,我们都会先看使用说明书。

大家肯定也写过一堆「系统分析书」、「代码规格书」、「SA/SD 文件」等等...但这些文件,跟最后线上的代码,究竟有多少是相同的呢?文件越详细,代表后面修改的effort 越大。

因为软件设计,本来就是个需求频繁变动的过程,往往大家只想「冻结需求」,却很常因为「冻结需求」搞到作出来的系统难用,因为不符合使用者需求。

我们期望的是,每一次的需求异动,都是软??件进化的动力,每一次的异动,都是品质的累积,以及更符合使用者的需求。

而文件呢?只有一开始分析、设计爽的,因为代码写下去,跟文件搭不搭的起来,只有三个人知道,一个已经离职了,一个是我,另一个我不能说。

鲜少会有文件跟着代码一直进行更新的。

但文件却又是辅助了解与说明很重要的东西,那怎么办?很简单,会一直活着的,就只有代码。要验证代码是否符合我们预期,最简单的方式,就是用代码验证它的行为,一翻两瞪眼,现在的物件究竟满足了那些功能,哪些情境下可以跑出预期结果,测试案例一目了然。

所以,测试案例的意义与价值是什么?

  1. 可自动执行、马上执行、快速执行的对象使用说明书,不会有过期或漏了更新的问题。
  2. 不管什么情况发生,不管在什么环境底下,都能确保其执行结果如同预期。

代码即文件,高兴什么时候产生文件,就什么时候产生,保证即时、可运作、童叟无欺。测试案例上面有的,肯定work,而测试案例上面没有的,不一定会错,但不打包票。

小结

一句话总结:「Working software is based on working test cases」。

Working software 是TDD 的整个骨架,也是user 最需要的东西。

备注:这个系列是我毕业后时隔一年重新开始进入开发行业后对大拿们的博文摘要整理进行学习对自我的各个欠缺的方面进行充电记录博客的过程,非原创,特此感谢91,小朱等前辈

时间: 2024-11-10 08:06:46

TDD学习笔记【二】---单元测试简介的相关文章

NFC学习笔记二——Libnfc简介与安装

一直想把自己对过的英文文章做一下翻译记录下来,趁着学习NFC,现将libnfc首页的对libnfc介绍和在不同操作系统上对libnfc安装的文章做一下翻译,一方面提高一下自己的英语,另一方面学习一下libnfc. 原文地址:http://nfc-tools.org/index.php?title=Libnfc 公共平台独立的近场通讯(NFC)库 libnfc是GNU公共许可正下发布的第一个免费的底层的NFC开发包和编程API.它对任何人事完全免费和公开的.这个列表显示了libnfc支持的功能.l

Android学习笔记二

17. 在ContentProvider中定义的getType()方法是定义URI的内容类型. 18. SQLiteDatabase类中的insert/delete/update/query方法其实也挺好用的,我在EquipmentProvider类中做了实现 19. Android专门有个单元测试项目(Android Test Project),在这个项目中,可以新建一个继承AndroidTestCase类的具体测试类来单元测试某个功能.我新建了一个AndroidTestProject项目,在

Caliburn.Micro学习笔记(二)----Actions

Caliburn.Micro学习笔记(二)----Actions 上一篇已经简单说了一下引导类和简单的控件绑定 我的上一个例子里的button自动匹配到ViewModel事件你一定感觉很好玩吧 今天说一下它的Actions,看一下Caliburn.Micro给我们提供了多强大的支持 我们还是从做例子开始 demo的源码下载在文章的最后 例子1.无参数方法调用 点击button把textBox输入的文本弹出来 如果textbox里没有文本button不可点,看一下效果图 看一下前台代码 <Stac

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

小猪的数据结构学习笔记(二)

小猪的数据结构学习笔记(二) 线性表中的顺序表 本节引言: 在上个章节中,我们对数据结构与算法的相关概念进行了了解,知道数据结构的 逻辑结构与物理结构的区别,算法的特性以及设计要求;还学了如何去衡量一个算法 的好坏,以及时间复杂度的计算!在本节中我们将接触第一个数据结构--线性表; 而线性表有两种表现形式,分别是顺序表和链表;学好这一章很重要,是学习后面的基石; 这一节我们会重点学习下顺序表,在这里给大家一个忠告,学编程切忌眼高手低,看懂不代表自己 写得出来,给出的实现代码,自己要理解思路,自己

JavaScript--基于对象的脚本语言学习笔记(二)

第二部分:DOM编程 1.文档象模型(DOM)提供了访问结构化文档的一种方式,很多语言自己的DOM解析器. DOM解析器就是完成结构化文档和DOM树之间的转换关系. DOM解析器解析结构化文档:将磁盘上的结构化文档转换成内存中的DOM树 从DOM树输出结构化文档:将内存中的DOM树转换成磁盘上的结构化文档 2.DOM模型扩展了HTML元素,为几乎所有的HTML元素都新增了innerHTML属性,该属性代表该元素的"内容",即返回的某个元素的开始标签.结束标签之间的字符串内容(不包含其它

马哥学习笔记二十四——分布式复制快设备drbd

DRBD: 主从 primary: 可执行读.写操作 secondary: 文件系统不能挂载 DRBD: dual primay, 双主(基于集群文件系统的高可用集群) 磁盘调度器:合并读请求,合并写请求: Procotol:drbd数据同步协议 A: Async, 异步  数据发送到本机tcp/ip协议栈 B:semi sync, 半同步  数据发送到对方tcp/ip协议 C:sync, 同步  数据到达对方存储设备 DRBD Source: DRBD资源 资源名称:可以是除了空白字符外的任意

【Unity 3D】学习笔记二十八:unity工具类

unity为开发者提供了很多方便开发的工具,他们都是由系统封装的一些功能和方法.比如说:实现时间的time类,获取随机数的Random.Range( )方法等等. 时间类 time类,主要用来获取当前的系统时间. using UnityEngine; using System.Collections; public class Script_04_13 : MonoBehaviour { void OnGUI() { GUILayout.Label("当前游戏时间:" + Time.t

Spring Batch学习笔记二

此系列博客皆为学习Spring Batch时的一些笔记: Spring Batch的架构 一个Batch Job是指一系列有序的Step的集合,它们作为预定义流程的一部分而被执行: Step代表一个自定义的工作单元,它是Job的主要构件块:每一个Step由三部分组成:ItemReader.ItemProcessor.ItemWriter:这三个部分将执行在每一条被处理的记录上,ItemReader读取每一条记录,然后传递给ItemProcessor处理,最后交给ItemWriter做持久化:It

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource