谈谈个人对TDD(测试驱动开发)的理解

更新时间:2023-06-19 15:33:18 阅读: 评论:0

谈谈个⼈对TDD(测试驱动开发)的理解
⽂章⽬录
介绍
测试驱动开发:英⽂全称Test-Driven Development,简称,是⼀种不同于传统的新型的开发⽅法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进⾏。这有助于编写简洁可⽤和⾼质量的代码,并加速开发过程。 (),请⾃⾏ google、baidu
这篇⽂章主要是谈个⼈的感想,有理解错误的请指教,欢迎评论交流
(p.s. 我是⼀个 Dev,这个感想更多是从开发⾓度来看的)
主要包含如下内容:
我⼼中的 TDD
如何做 Tasking
举个例⼦ - Tasking 纵向拆分
如何写 Test
⼀个 class 可以有多个测试⽂件么?
⼀个 Tasking 可以有多个测试么?
业务规模 - 代码规模 - TDD 规模越来越⼤,如何维护?
TDD 的测试覆盖率能表⽰什么?
TDD 是测试⽅法么,是 Unit Test 么?
使⽤ TDD 对开发时间的影响
敏捷与 TDD, 不是敏捷项⽬可以 TDD 么?
其他的 TDD 经验 / 疑问
理想与现实
我⼼中的 TDD
TDD 的英⽂和中⽂意思都很好:测试驱动开发,Test-Driven Development。从⼩的教育就是⼀句话中的动词是很重要的,对于 TDD 我也是认为最重要的是“驱动”。⽽驱动的是什么呢?是“开发”,通过什么驱动呢?通过“测试”,于是我⼼中的优先级是:
1. 驱动
2. 开发
3. 测试
其实除了 TDD 以外还有很多 DD,⽐如 BDD - ⾏为驱动开发(Behavior Driven Development),再⽐如 ATDD - 验收测试驱动开发(Acceptance Test Driven Development),这些⽅法论都是从不同⽅⾯给开发提供驱动⼒,。
驱动开发实际上是为了给开发以⼀定辅助⼿段,让开发的正确率、效率、业务表述贴合度提⾼,⽆论是测试、⾏为、验收测试都只是为开发提供的⼀种⼯具,⼀种⽅法论,⼀种外界的驱动⼒
当然还有⼀种驱动⼒是⾃驱⼒,我相信⾃驱⼒,但我也不信⾃驱⼒,尤其是在复杂的业务关系、业务
逻辑、软件架构、代码设计、技术栈以及组织架构混合下,单纯以⾃驱⼒就能够对开发驾驭⾃如,我们需要优先把这种混合的⼤泥球拆解开。
再会到 TDD,我想它主要的⽬的是让开发⼈员尽可能在 coding 时,准确来说是在开发业务代码时,可以⼼⽆旁骛,沉浸于技术实现。那么想要以 TDD 的⽅式进⼊“开发阶段”:
⼀定要现有⼀系列的驱动⼒“测试的红、绿”
⽽想要有这⼀些列的测试就需要先对业务有⾜够全⾯的理解,并写出当前 story 的 tasking。
到此为⽌,按照上⾯所说的两个⼤步,我在使⽤ TDD 的时候,⽆论是明确的感知到每⼀步的进⾏还是潜意识帮我做了很多,实际上在做TDD 时,我都会间接地完成下⾯四步,
理解业务
细分业务
编写符合业务流程的测试
完成测试(开发业务代码)
回想⾃⼰写个⼈项⽬的经历,埋头苦⼲,边写、边思考业务,脑⼦⾥在并⾏性⼯作,产品愿景分析,开发⽬标是什么,业务分析,功能到底要怎么样,UI 怎么做更好,架构是否够优秀,是否现在要重构,增加这个还是下⼀个业务时重写⼀下之前可能有 bug 的功能,这端代码写完了是否正确,好像这⼀段写出来的好熟悉是不是重复了,我记着曾经看过某⼈写的博客这⾥可以引⼊和框架⼀⾏搞定,是不是这个功能和某产品类似了看起来是不是⽤处不⼤……稀⾥糊涂写完了代码,然后启动程序试⼀试------
怎么就不对了,我感觉写的对着呢!
稍等我找找原因,没看出来什么地⽅有问题呀 20min ……
这么多代码,在等我断点调试⼀下。
你看这⼀步数据是对的,下⼀步就错了,所以进⼊这个 method 看看
你看这⼀步数据是对的,下⼀步就错了,所以进⼊这个 method 看看(⼼理在喷,这不是我写的呀,或者是我很早以前写的了?这代码到底啥意思)
你看这⼀步数据是对的,下⼀步就错了,所以进⼊这个 method 看看
形状图片大全认识
哦对,⾝份证号不能为空,⼀个⼈怎么能没有⾝份证号呢,怎么这⾥返回的对象⾥是 null,我再看看它从哪来的
……
我在这忘记判断⾝份证号了,这⾥忘了,抱歉抱歉,真不好意思,现在就改:⾝份证号为 null 就是脏数据扔掉
重新编译,再启动,这个问题解决了,来咱们继续验收
刚才看过的就算了,咱们看看没看的功能
不对不对,这⾥刚才不是对着呢?还好⼜看了⼀眼
哦对了,我想起来当时写代码为什么⾝份证没有判断 null 了,因为⾝份证号可以为 null,有可能是护照等其他证件号
……
其实⽆论是 TDD 或者其他的任何驱动⽅式,⾸要⽬的都是为了给开发提供⼀个⼯具 / ⽅法,使其能够
在开发时只需要沉浸于技术,⽽不是业务流程+业务边界+代码实现+设计……⼈并⾮精密的机器,百密终有⼀疏。
除此以外,TDD 在执⾏过程中还是期待能够进⾏合理的 Tasking 拆分,以供持续集成,更进⼀步的是持续部署(CI / CD)。
三亚明珠广场如何做 Tasking
为什么要先写如何做 Tasking,因为做 Tasking 是应该在真正做某个测试、某个实现、某个重构之前的。换句话说,Tasking 是最优先的,应该相将 Tasking 完成,再根据 Tasking 的输出去做写进⾏红-绿-重构的步骤,⼀定不是想到⼀个 task -> 写⼀个测试 ->实现 -> 重构。
看到很多博客教程的⼤纲是这样的(我不认为这是合理的):
介绍 TDD
加入用英语怎么说
介绍教程的 Story
第⼀个测试
第⼀个实现
重构
第⼆个测试
第⼆个实现
n83重构皮肤癌的早期症状
上⾯这种⼤纲我并不认可,这样去写可能在这个简单的 demo 上看不出来什么,但是这样将会导致两个问题:
仍然没有将业务理解和开发分离,仅仅只是先写了个测试
如果开发复杂⼀些(相对⽐较⼤的 Story),写到后⾯就会对之前的测试、实现、最初对业务的理解有所遗忘,很可能导致整个 Story 的每⼀部分实现是基于随时间变化的不同的业务理解。
我认为最合适的 TDD Tasking 应该是如下的过程:
了解 Story,(如果是敏捷可以在开卡过程,甚⾄在开卡并了解了当前代码后,还可以继续再详细的根据当前业务实现现状询问 BA)根据 Story,识别整体价值,从价值⾓度初步拆分成交付步骤(这⾥
可能还不是 Task,只是 Story 的⼏⼤步,这⾥是为了应对持续部署、持续交付,可以随时交付价值,⽽不是只有完整的 Story 完成才可交付价值,同时也是为了下⾯ Task 拆分时有⾜够的依据)根据每个交付步骤完成主业务流程的 Task,写到⼀个⽂档⾥(⼀定要整理下来,只有整理并且有产出才好让⾃⼰进⾏业务与实现的分离,当然如果这个过程已经熟练掌握那可以不进⾏持久化,不进⾏持久化的前提是确保⾃⼰的记忆⼒可以保证对业务理解在整个 Story 过程不会朦胧、突变)
找到各种边界,将边界写成 Task,边界可以特例化的写,不需要遍历相同边界的所有可能,是为了驱动开发,⽽不是⾃动化校验所有可能。
总结⼀下,我认为从需求到实现的过程应该是:⾃顶向下,逐层剖析,纵向拆分
⾃顶向下指:从需求出发,以树的形式不断地分析出各个分⽀,最后到达实现代码。
逐层剖析:分析的过程应该是有层次的,⽽不是抓住⼀点⼀⼝⽓钻到底(需求-⼀个 task-⼀个test-红-绿-再研究出⼀个 task……),应该脚踏实地⼀步⼀个脚印的分析,通过逐层分析让我们解耦需求、设计、实现。
纵向拆分:就像上⾯的过程所说的从价值⾓度拆分,纵向就是
举个例⼦ - Tasking 纵向拆分
下⾯每个标题是按照流程写的,当然这个流程可能更像是个⼈开发者在和⾮技术⼈员沟通的过程,通过技术实现他⼈的 idea,如果项⽬明确的 BA or 业务负责⼈,可能技术拿到的第⼀份需求已经包含了⼀系列的 AC。
开发,没有正确的,只有最适合的,下⾯的也只是个范例,我只是期待能够通过这个详细的过程描述,来让更多的开发能够在实践TDD 时合理的拆分 Task,并提⾼持续部署、持续交付的意识,当然这个思考⽅式不⽌局限于 TDD,也不局限于敏捷。
Story 背景
A:您好,我这有个软件需要开发,主要是管理停车场的……我们商场的停车场⽐较⿇烦,⾸先我们地下⼀层有 100 个停车位,地下⼆层也有 90 个停车位,除此以外我们楼顶还有⼀个停车区域有 50 个停车位,我们希望能够更好地管理这些位置,现在我们都是⼈⼯管理,希望能够数字化,不需要很复杂的管理,三个停车区域没有价格差异,只是希望有位置就能让车停⼊,没有剩余位置就给出提⽰就可以了。
B:您好,近年来车辆越来越多,我感觉停车场是个很好的商机,我希望能够开发⼀个停车场管理系统,能够实现存车取车服务,现阶段希望能管理⼀个停车场就⾏了,以后可能会不断扩充,甚⾄可能需要合理的调度。
为什么要说背景?我不认为没有背景的 Story 是可以分析出价值的,甚⾄于会影响侧重点。这两个背景可以看出 A ⽤户是已经有了成熟的管理区域,希望数字化转型,B ⽤户是只有 idea,没有⼈,甚⾄可能也没有场地,是个探索性的商业产品开发。
当然为了简化(或者说我想要偷懒),这⾥只说 B ⽤户
Story – 粗略版
我希望我的客户能够在停车场存取车,客户存车以后可以得到⼀个存车卡,可以⽤卡取车。
Story – 清晰版
这⼀步可能是 BA 或者是 PM 也可能是⾃⼰根据交流得出来并持久化的 AC 内容。
AC 如下
1. ⽤户如果来存车,他可以得到⼀个存车⼩票。
2. ⽤户如果⽤这张⼩票来取车,那么他可以取到⾃⼰存进去的车。
Story – 扩充
现在让我们从 AC 还有了解到的现实情况来分析,并和通过不断和需求提供⽅问答,我们可能会得到⼀些边界信息,来扩充 AC
1. ⽤户如果过来存车,但是没有开着车,这样⽤户也会得到存车⼩票。
2. ⽤户如果⽤⼀个⽆效的⼩票(⽤过的,或者⾃⼰创造的假的),或者随便拿其他东西来,都⽆法取到东西。
3. ⽤户不会出现把⼀辆车存两次的情况,现实中会避免这种情况出现,不需要考虑
Task 过程
下⾯的 Task 可能会同时包含 Task 的实现,因为发现了很多⼈ Task 写的很好,但实现远远超出了 Task 内容
先来个 Task:
given ⼀辆车
when 存车
丽江粑粑then 得到⼩票
given ⼩票
when 取车
then 得到⼩票对应的车
……
实现的过程(JAVA):
void x1(){// poor naming无人机专业
ParkingLot parkingLot =new ParkingLot();
Vehicle myCar =new vehicle("京AAAA");
Ticket myTicket = parkingLot.park(myCar);
asrtNotNull(myTicket);
}
void x2(){// poor naming
ParkingLot parkingLot =new ParkingLot();
String vehiclePlateNumber ="京AAAA";
Vehicle myVehicle =new vehicle(vehiclePlateNumber);
Ticket myTicket = parkingLot.park(myVehicle);
Vehicle pickedUpVehicle = parkingLot.pickUp(myTicket);
asrtEquals(vehiclePlateNumber, VehiclePlateNumber());
如何改变手机字体}
这⾥,很多⼈写的 Task 并不是 “车牌号为 京A 12345” 的车,可能只是写了提供⼀个车,但是实现的过程⽤到了车牌号或者其他的号。
这⾥有两个问题:
⾸先:⽤车牌照号是⼀种对某个 vehicle object 的唯⼀识别⽅式,但是这个⾏为等于是对需求进⾏了脑补,上⾯写到的各种 AC 包括详细问答后扩充的 AC 都没有提到“车牌号”这个概念,若这个时候引⼊了车牌号,尤其是这个 Story 可以看出来⽐较简单,很可能只是第⼀个 Story,这样的⾏为很⼤概率会导致后续所有 Story 都被注⼊了全新的概念,最终结果这就是对整个业务注⼊了全新的概念,如果真的出现这个情况应该考虑先谈更改 AC,否则直接 new ⼀个⽆参、⽆私有成员的对象就⾜够了,每个对象都是唯⼀的。
如果使⽤ new ⼀个对象的⽅式,最后断⾔在 Java 注意使⽤ asrtSame,其他语⾔可找类似⽅案,要明确识别是对象完全⼀样(就是⼀个东西),还是对象的内容⼀样。
其次,是 Task 划分的建议:

本文发布于:2023-06-19 15:33:18,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1045737.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:开发   业务   实现   代码   可能   测试   能够   过程
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图