TDD学习笔记【二】---单元测试简介
大纲
Testing 的第一个切入点:单元测试。
本篇文章将针对单元测试进行简介,主要内容包含了5W:
- Why
- What
- Where
- Who
- When
而How 的部分,属于实现部分,将于下一篇文章介绍工具与简单的范例。
最后会提到测试用例所代表的意义与其重要性。
前言
单元测试,是开发人员最该写的测试程序,却也是最容易被忽略的测试。
大家常碰到的测试相关问题是:
- 往往一堆人写测试程序时,自以为是在写单元测试,却压根就不是单元测试,而是集成测试。
- 生产代码是我写的,如果测试程序也是我写,那有什么意义?所以应该给QA/QE 来写才能测出盲点。
- 我程序都写完了,跑起来也都对,这时写测试程序一点意义都没有。
- 测试程序要跑好久。
- 没有测试环境,要怎么写测试。
看完这几篇单元测试的相关文章后,希望大家可以获得一些想法,解决这些问题。
Why
先举几个在开发上常见的问题:
- 怎么让UI, Service, Data Access 平行开发?
- 要到真实环境方能测试程序无误
- 页面发生错误,到底是谁错了?
- 交付的程序,到底测过哪些东西了?
- 我改了这支程序,会不会害别的程序挂掉?
这些问题,可以有哪些Unit Test 相关的方式来解决:
- Unit Test 中使用stub/mock object,达到关注点分离
- Unit Test 使用stub/mock object 来模拟外部回传的数据
- 把input 值当做test case,跑一次Unit Test
- 交付的程序,包括Unit Test 程序
- 改完程序就跑一次Unit Test 吧
总而言之,没有被测试涵盖到的程序,即使它可能是对的,也没人敢拍胸脯保证。而有了测试用例来辅助说明与保护,至少可以拍胸脯保证,在这样的测试用例下,这个对象的设定,肯定如同预期般执行。
而单元测试可以提供回归测试的保护,在每一次异动完程式,可以单键执行就知道是否破坏了原本对对象行为的预期。
单元测试可以透过一些辅助设计,来达到与外部环境、服务、相依隔绝,而仅测试该物件本身的逻辑,以及与外部的互动是否符合预期。
造成问题的测试案例,往往是最珍贵的,因为最具代表性,也最具价值。因为它提供了我们修正bug的方向以及指标。而针对发生问题的测试案例,来执行单元测试,马上就可以知道是否是该对象的内部问题。
最后,单元测试由于具备与外界服务、相依隔绝的特性,所以可以帮助撰写实际的对象时,具有可测试性、低耦合性,彼此之间只相依于抽象或接口。进而通过IoC 的设计,让我们可以做到关注点分离,让开发各个对象的developer,可以透过接口来沟通,不相依于彼此实现,就能平行开发。
What
Unit Test 的定义与基本准则,如下图所示:
- 一个测试案例只测一种方法
- 最小的测试单位
- 不与外部(包括项目、数据库、网络、服务、对象、类型)直接相依
- 不具备逻辑
- 测试案例之间相依性为零
Unit Test的特性,一个字:FIRST。如下图所示:
- Fast:快速。
- Independent:独立。
- Repeatable:可重复。
- Self-Validating:可反应验证结果。单元测试不论成功或失败,都应该要从测试的reporting 直接了解其意义或失败原因。
- Timely:及时。单元测试应该恰好在使其通过的production code 之前撰写。
即:优良的单元测试具有以下的特点:简称为 A-TRIP。
- 自动性(Automatic)
- 完备性(Thorough)
- 可重复性(Repeatable)
- 独立性(Independent)
- 专业性(Professional)
Where
单元测试的覆盖范围,以定义来说,单元测试是最小的测试单位,在面向对象中,就是测试一个方法。而方法一定会在某个对象上(即使是静态方法,也是在类型对象上)。
所以,单元测试通常就只关注在测试的目标对象上,而不管目标对象以外的东西,例如:目标对象所相依的实体对象、相依服务、相依资源、相依环境等等...
单元测试,简单的说,就是用来模拟外部如何使用这个目标对象,或是如何与这个目标对象互动。所以我们所撰写的单元测试程序,就是模拟与目标对象互动的程序。测试案例,就是该互动下的情境。接着验证物件的行为是否符合我们预期。
因此,单元测试程式,既然是模拟外部如何使用目标物件,所以也只会针对目标对象对外开放的方法。
而基本上,单元测试透过哪些方式去验证对象的行为符合预期呢?简单来说,有三种:
- 验证目标对象的回传值,如下图所示:
- 验证目标对象的状态改变,如下图所示:
- 验证目标对象与外部相依接口的互动方式,如下图所示:
Who
单元测试该由谁来撰写,就如同前言所说,最应该撰写的是developer,而非QA/QE。
就如Where段落所说,单元测试简单的说,是我们在设计对象的时候,预期外部该如何使用这个对象,进而衍生出对象该提供什么样的功能、具备什么样的行为。正因为对象的设计人、使用人,都是developer,所以单元测试的程式,当然由developer来设计,最为妥当。尤其由用的人来写,最为精准。
归纳几个基本要点:
- 想要达到什么需求,就是测试案例。而对象的设计,只是为了满足需求,需求即测试案例。即生产代码只为了满足测试程序上的测试案例。
- 设计对象的人员,才能知道对象该怎么给外面使用。
- 由外部使用对象的角度来设计测试案例。
When
撰写单元测试的时机点,简单分成三个:
- 外部需要使用对象,并对其执行结果有所预期时( developing )
- feature的异动时( modifying )
- 出现非预期执行结果时( bug fixing )
想清楚,外部的需求是什么,才能设计出符合需求的对象。
当需求异动时,自然需要针对新的需求,来设计新的测试程序,因为这样才能驱使目标物件行为的改变。
当出现非预期的执行结果时,通常代表目标物件有着非预期的行为发生,有可能是当初测试案例不足,所以要增加我们的「预期」。
也有可能是当初预期的结果就错了,那其实就可以当作是第二点,需求的异动。(当然对使用端来说,还是属于bug,但对对象设计来说,测试案例方向就错了)
Test Cases的意义
大家买过3C产品或电器吧,基本上拿到一个东西,我们都会先看使用说明书。
大家肯定也写过一堆「系统分析书」、「代码规格书」、「SA/SD 文件」等等...但这些文件,跟最后线上的代码,究竟有多少是相同的呢?文件越详细,代表后面修改的effort 越大。
因为软件设计,本来就是个需求频繁变动的过程,往往大家只想「冻结需求」,却很常因为「冻结需求」搞到作出来的系统难用,因为不符合使用者需求。
我们期望的是,每一次的需求异动,都是软件进化的动力,每一次的异动,都是品质的累积,以及更符合使用者的需求。
而文件呢?只有一开始分析、设计爽的,因为代码写下去,跟文件搭不搭的起来,只有三个人知道,一个已经离职了,一个是我,另一个我不能说。
鲜少会有文件跟着代码一直进行更新的。
但文件却又是辅助了解与说明很重要的东西,那怎么办?很简单,会一直活着的,就只有代码。要验证代码是否符合我们预期,最简单的方式,就是用代码验证它的行为,一翻两瞪眼,现在的物件究竟满足了那些功能,哪些情境下可以跑出预期结果,测试案例一目了然。
所以,测试案例的意义与价值是什么?
- 可自动执行、马上执行、快速执行的对象使用说明书,不会有过期或漏了更新的问题。
- 不管什么情况发生,不管在什么环境底下,都能确保其执行结果如同预期。
代码即文件,高兴什么时候产生文件,就什么时候产生,保证即时、可运作、童叟无欺。测试案例上面有的,肯定work,而测试案例上面没有的,不一定会错,但不打包票。
小结
一句话总结:「Working software is based on working test cases」。
Working software 是TDD 的整个骨架,也是user 最需要的东西。
备注:这个系列是我毕业后时隔一年重新开始进入开发行业后对大拿们的博文摘要整理进行学习对自我的各个欠缺的方面进行充电记录博客的过程,非原创,特此感谢91 等前辈
TDD学习笔记【二】---单元测试简介的更多相关文章
- amazeui学习笔记二(进阶开发2)--Web组件简介Web Component
amazeui学习笔记二(进阶开发2)--Web组件简介Web Component 一.总结 1.amaze ui:amaze ui是一个web 组件, 由模板(hbs).样式(LESS).交互(JS ...
- [Firefly引擎][学习笔记二][已完结]卡牌游戏开发模型的设计
源地址:http://bbs.9miao.com/thread-44603-1-1.html 在此补充一下Socket的验证机制:socket登陆验证.会采用session会话超时的机制做心跳接口验证 ...
- ES6学习笔记<二>arrow functions 箭头函数、template string、destructuring
接着上一篇的说. arrow functions 箭头函数 => 更便捷的函数声明 document.getElementById("click_1").onclick = ...
- JDBC学习笔记二
JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...
- MongoDB学习笔记二- Mongoose
MongoDB学习笔记二 Mongoose Mongoose 简介 之前我们都是通过shell来完成对数据库的各种操作, 在开发中大部分时候我们都需要通过程序来完成对数据库的操作 而Mongoose就 ...
- WPF的Binding学习笔记(二)
原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...
- AJax 学习笔记二(onreadystatechange的作用)
AJax 学习笔记二(onreadystatechange的作用) 当发送一个请求后,客户端无法确定什么时候会完成这个请求,所以需要用事件机制来捕获请求的状态XMLHttpRequest对象提供了on ...
- JMX学习笔记(二)-Notification
Notification通知,也可理解为消息,有通知,必然有发送通知的广播,JMX这里采用了一种订阅的方式,类似于观察者模式,注册一个观察者到广播里,当有通知时,广播通过调用观察者,逐一通知. 这里写 ...
- C#.NET学习笔记1---C#.NET简介
C#.NET学习笔记1---C#.NET简介 技术qq交流群:JavaDream:251572072 教程下载,在线交流:创梦IT社区:www.credream.com -------------- ...
- java之jvm学习笔记二(类装载器的体系结构)
java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...
随机推荐
- Swift—调用系统相册和相机
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px Menlo; color: #000000 } p.p2 { margin: 0.0px 0. ...
- Bubble Cup 8 finals I. Robots protection (575I)
题意: 有一个正方形区域, 要求支持两个操作: 1.放置三角形,给定放置方向(有4个方向,直角边与坐标轴平行),直角顶点坐标,边长 2.查询一个点被覆盖了多少次 1<=正方形区域边长n<= ...
- gradle多模块开发
参考文档:gradle的官方userguide.pdf文档的chapter 55和chapter 56.gradle的多模块或项目开发一定不会比maven差,在我看来!大的项目分成多个模块来开发是常事 ...
- JavaScript - 正则表达式
正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功:一旦有匹配不成功的字符则匹配失败. 正则表达式通常用于在文本中查找匹配的字符串.Python里数量词默 ...
- ASP.NET 验证码控件
public class ValidateCode : WebControl { /// <summary> /// 默认构造函数,暴露的属性接口 /// </summary> ...
- Block formatting context(块级格式化上下文)
今天看到豆瓣面试官的一篇文章,讲到关于CSS中的一个知识点:Block formatting context ,感觉这个确实挺有用,同时我也挺赞同作者的一些观点的,这里就不展开谈我的感受了, 此文只 ...
- python中不同包之间调用方法、
在pycharm中... 当两个py文件在同一个文件夹下的时候.直接from 文件名 import * 即可 当两个文件在不同的文件夹下的时候.需要在文件中加入 _init_.py 文件.里面可以什 ...
- css3动画第一式--简单翻滚
在w3cschool上面查阅css3的动画语法手册时,发现“css3 动画”栏目首页放了一个翻滚的div动画案例,觉得挺好看的,于是就自己模仿着写了一下,感觉还行O(∩_∩)O哈哈~ 查看原地址 下面 ...
- ASP.NET、JAVA跨服务器远程上传文件(图片)的相关解决方案整合
一.图片提交例: A端--提交图片 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string u ...
- 百度地图API 批量添加 带检索功能的信息窗口
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...