TDD的简单实践
最近有幸跟随资深ThoughtWorks咨询师熊节老师一起学习测试驱动设计,经过短暂的十几天培训,对测试驱动设计的基本原则、实践模式、技巧有了一点点初步的认识。
在此之前,经常自嘲我经历的公司实践也似乎是TDD, 这种实践往往都是由测试工程师来驱动开发者完成bug的修改,虽然也是测试来驱动开发,但是却与真正的TDD大相径庭。
在维基百科中是这样对TDD下定义的:
测试驱动开发(英语:Test-driven development,缩写为TDD)是一种软件开发过程中的应用方法,由极限编程中倡导,以其倡导先写测试程序,然后编码实现其功能得名。测试驱动开发始于20世纪90年代。测试驱动开发的目的是取得快速反馈并使用“illustrate the main line”方法来构建程序。
测试驱动开发是戴两顶帽子思考的开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,通过去除冗余的代码,提高代码质量。测试驱动着整个开发过程:首先,驱动代码的设计和功能的实现;其后,驱动代码的再设计和重构。
测试驱动开发也是国外许多优秀开发者向开发者们推荐的一种普遍适用的开发模式,而在熊节老师的培训课程中,他时刻在向开发者灌输来自TDD的三条原则,要求我们的编写生产代码前,一定应该先编写单元测试。
定律一:在编写不能通过的单元测试前,不可编写生产代码。
定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过。
定律三:只可编写刚好足以通过当前失败测试的生产代码。
在我之前的编码实践过程中,总是习惯梳理一遍逻辑后,在根据项目的实际情况对代码进行重构,而随着我自以为掌握了单元测试的技巧之后,就开始把逻辑代码往单元测试上套,导致这样的单元测试实际上并非为了实现测试,而仅仅只是程序的入口而已。
如果使用TDD的方法,则需要首先规划需要实现的目标,然后再定义测试方法和测试需要实现的逻辑。
例如,代码大概是这样的:
![]()
我的目标是实现对Schema对象的解析,测试类采用SchemaUnitTest,并采用“should_xxx_when_xxx”的命名方式,定义了测试方法“should_return_true_when_bool”,然后定义一个Schemas的类,再定义其需要实现的需求(断言),以及需求的实现。
对单元测试方法的命名,不同的书籍有不同的命名方法,在这个项目实践中,采用的是should命名方法,而在之前看过的《单元测试的艺术》一书中,使用的is_when_return_xxx的方式,这两者只是命名方法的不同,本质上没有任何区别;使用xunit和mstest实际上也没有太多区别。
此时,这个定义的方法GetParameter是未实现的,所以会进入一个“红-绿-重构”的工作流程。
![]()
1)编写一个会失败的测试,以证明产品中代码或功能的缺失。编写代码时,要假设产品代码已经能工作了,这样测试的失败就说明产品代码中有缺陷。例如我定义的GetParameter方法使用xunit进行测试会提示失败, 只有在添加需要的代码后,编译才能通过。
2)编写符合测试预期的产品代码,使测试通过,产品代码应该尽量简单。
3)重构代码。如果测试通过了,你就可以编写下一个单元测试,或者重构,消除异味或提高代码可读性。
最终,我完成了一个这样的方法。(即便是这样的代码,依然有许多可以进一步提升的空间。)
![]()
显然这是一个逻辑非常简单的代码,但是如果采用全键盘操作,不使用鼠标来完成,仍然耗费了我不少时间,这个过程中,也让我对Visual Studio的快捷键操作更加熟练。
在我们的产品研发过程中,经常遇到以下三种不同形式的测试
![]()
- 端到端测试:端到端测试侧重于软件功能应用层面的测试,主要使用人工或自动化的形式对用户界面进行测试。往往需要覆盖系统的各个功能,需要耗费的人力物力较大。
- 服务测试:主要集中在服务接口层的测试,可以通过PostMan等测试工具对接口的稳定性和可用性进行测试。侧重于接口行为实现。
- 单元测试:针对代码层面,例如单个方法或单个类实现的测试。属于白盒测试的一种。
三种测试从上到下实施的容易程度递增,但是测试效果递减。端到端测试最费时费力,但是通过测试后我们对系统最有信心。单元测试最容易实施,效率也最高,但是测试后不能保证整个系统没有问题。
在我们的项目实践中,更多的采用的依然是端到端测试的模式,似乎只有通过测试者的人肉测试,才能让我们的代码更加令人满意。
单元测试事实上极少在我们的项目中得到实践,其主要原因大概是因为要掌握单元测试方法,本身需要对开发者的主观能动性提出了更高的要求,但是996开发者...太容易内卷化了。
写好单元测试从来就是技术活,有一段时间过分在意理论概念和工具的用法,忽略了实践,所以实际上看了好几本书,依然不知道如何写单元测试,这次参与了培训,终于摸到了一点点影子。
现阶段我大概可以这样做来逐步提高自己的技能水平:
- 1、小步快跑,注意节奏:不要过度在意某个需求的快速实现,而是编写能够在五分钟内快速完成的代码,并确保其通过。代码行控制在五行以内,代码的缩进层次,控制在两到三层。
- 2、练习,练习,再练习:写代码从来不是一件容易的事情,按照一万小时定律的说法,如果指望几天就熟练掌握显然不太现实,未来需要更加积极的练习,才能真正掌握。
- 多思考、努力写好代码:写几行代码其实并不难,难的是写高质量的代码。不要急于代码实现,要多思考上下文逻辑,让代码更加优美。
参考资料:
- https://docs.microsoft.com/zh-cn/dotnet/core/testing/unit-testing-best-practices
- https://juejin.im/post/5c715e7d6fb9a04a0c2f12f4
TDD的简单实践的更多相关文章
- Thrift简单实践
0.什么是RPC RPC(Remote Procedure Call - 远程过程调用),是通过网络从远程计算机上请求服务,而不需要了解底层网路技术的细节.简单点说,就是像调用本地服务(方法)一样调用 ...
- Java 异步处理简单实践
Java 异步处理简单实践 http://www.cnblogs.com/fangfan/p/4047932.html 同步与异步 通常同步意味着一个任务的某个处理过程会对多个线程在用串行化处理,而异 ...
- Android 设计随便说说之简单实践(合理组合)
上一篇(Android 设计随便说说之简单实践(模块划分))例举了应用商店设计来说明怎么做模块划分.模块划分主要依赖于第一是业务需求,具体是怎么样的业务.应用商店则包括两个业务,就是向用户展示appl ...
- c#中,委托Func的简单实践
c# 委托Func的简单实践最近才真正的接触委托,所以针对Func类型的委托,做一个实践练习. 首先说一些我对委托的初级理解:"就是把方法当做参数,传进委托方法里". 我平时用到的 ...
- kafka原理和实践(二)spring-kafka简单实践
系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...
- SQL知识以及SQL语句简单实践
综述 大家都知道SQL是结构化查询语言,是关系数据库的标准语言,是一个综合的,功能极强的同时又简洁易学的,它集级数据查询(Data Quest),数据操纵(Data Manipulation),数据定 ...
- ZooKeeper分布式锁简单实践
ZooKeeper分布式锁简单实践 在分布式解决方案中,Zookeeper是一个分布式协调工具.当多个JVM客户端,同时在ZooKeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁 ...
- Spring 学习二-----AOP的原理与简单实践
一.Spring AOP的原理 AOP全名Aspect-Oriented Programming,中文直译为面向切面(方面)编程.何为切面,就比如说我们系统中的权限管理,日志,事务等我们都可以将其看 ...
- VueRouter爬坑第一篇-简单实践
VueRouter系列的文章示例编写时,项目是使用vue-cli脚手架搭建. 项目搭建的步骤和项目目录专门写了一篇文章:点击这里进行传送 后续VueRouter系列的文章的示例编写均基于该项目环境. ...
随机推荐
- 第五章 Unity中的基础光照(2)
目录 1. Unity中的环境光和自发光 2. 在UnityShader中实现漫反射光照模型 2.1 实践:逐顶点光照 2.2 实践:逐像素光照 2.3 半兰伯特模型 1. Unity中的环境光和自发 ...
- TypeError: 'list' object cannot be interpreted as an integer
TypeError: 'list' object cannot be interpreted as an integer 类型错误,不能将list对象转换为一个整数. 错误代码,例如如下例子: arg ...
- 深入理解 BigDecimal
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- Cisco 7200 路由 PPPOE 拨号详解
1.1配置虚拟拨号接口 R1(config)#vpdn enable #启用vpdn虚拟专用拨号网络 R1(config)#interface dialer 1 ...
- springcloud-Eureka组件
一.简介 Eureka 是 Netflix 开发的服务发现框架,本身是一个基于 REST 的服务,主要用于定位运行在 AWS 域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringC ...
- hibernate查询方式(三)
QBC查询 (Query By Criteria) *****QBC查询有三个特点 **查询时不写sql语句 而是用方法来查询 **操作实体类和属性 **通过criteria对象来实现 1.查询所有 ...
- IOS原生方法实现二维码生成与扫描
转自:http://www.jianshu.com/p/d6663245d3fa 二维码的生成有好多第三方库,如Z-Xing.但是为了控制安装包的大小,或者并不需要其他的一些额外的功能,用系统的方法即 ...
- border-radius:50%和100%的区别
border-radius 值类型-百分比 border-radius的值是百分比的话,就相当于盒子的宽度和高度的百分比. 我们知道在一个正方形内做一个面积最大的圆形,这个圆的半径就为正方形边长的一半 ...
- Java修炼——基于TCP协议的Socket编程_双向通信_实现模拟用户登录
首先我们需要客户端和服务器端. 服务器端需要:1.创建ServerSocket对象.2.监听客户端的请求数据.3.获取输入流(对象流)即用户在客户端所发过来的信息. ...
- [TimLinux] MySQL 中的CASE/WHEN语法
1. 介绍章节 MySQL 5.7-en.a4.pdf文档的第12章:Functions and Operators中的12.4Control Flow Functions介绍了流控制操作符:CASE ...