Re: [讨论] 工作上写单元测试的比例

楼主: TonyQ (自立而后立人。)   2024-05-02 15:45:45
※ 引述《langrisser19 (lan)》之铭言:
: 总之每个方式都有一些共用,或是非共用的行为
: 目前的程式像这样
: func 储值(方式){
: switch 方式{
: case 方式1:
: if 符合条件1 {
: if 符合条件2 {
: if 呼叫api-1 成功{
: 更新接口1
: }
: }
: }
: case 方式2:
: 要符合不同的巢状条件,然后呼叫另一只api,一样根据结果更新不同的接口
: case 方式3:
: 又是不同的条件跟api
: }
: }
: 像这样的程式,不知道测试怎么写?
ㄅ是啊,你应该是先有需求才有测试,
通常是先假设已经在线上的已经经过线上考验。
如果没有这种需求,你根本就不应该整理。
我个人认为任何在没有需求的前提下情况下整理程式码,
是一个浪费自己时间又没意义的行为。
有需求,你就会相对清楚你要处理的边界在哪,
而不是在思考我要处理多宽的问题。
比方说,今天是要修方式3里面的某算式好了好了。
那就是先用重构工具把 //又是不同的条件跟 api 的部分抽出函示,
然后把 member 成员的部分都转成 input 。
(这是我的作法啦,这算是应急的第一步,
这个可以再透过重构的推跟拉来把他摊成 module,
但假设今天的情况是比较快的模式。)
xxx(){
......
switch(xxx){
......
case 方式3:
方式3_implement(xxx,yyy,zzz);
}
}
public static 方式3Response 方式3_implement(xxx,yyy,zzz){
//又是不同的条件跟api
}
Test case:
public class 方式3TestCase{
public void Test方式3(){
var mockxxx="xxx", mockyyy="yyy", mockzzz="zzz";
var res = xxx.方式3_implement(mockxxx,mockyyy,mockzzz);
方式3Response.aaa.should().be(xxx);
}
}
通常我会先把“已经存在的案例”先写成测资,
因为老话一句,改前改后都要对的东西可以当对照组。
========
通常以上在有正常重构工具的情况应该是几秒钟的事情,
没有的话就是自己搬变量比较烦,应该也是十几分钟要能搞定。
这里可能会有一个例外,就是方式3 如果没有明确可验证的回传值,
(也就是 void )
这时候我自己习惯的是,自己根据重要的地方补 true/false 的 bool,
只验这个必要的 bool,这就要花点技巧处理的地方。
总之你要有“可验证点”,这是可验证性中很重要的一环。
然后考虑到可验证点,你一开始就把“所有方式”,
写成一个大 test case 讲白了只是在找自己麻烦。
你完全没有必要去做一个这么难的 normalize,除非结构真的超级漂亮。
我会说你原文作的事情,其实是蛮 anti pattern 的,
真实世界中往往80%以上的情况是带不进去的。XD
会做到一半发现当初分开是有理由的,然后又走回起点。
我的习惯就是方式1做到写一个 方式2做到写一个,
起码当我需要测试的时候,我有一个子点。
然后不会强求展开,就是“有发生问题的补”、“怀疑会发生问题的补”,
你没有无穷的时间穷举,所以问题永远是限缩跟抽样。
必要的时候我会把我不关心的选项直接写成 if(xxx) throw new exception();
表示此路不通,来减少子支。
========
再来是推进 方式3_implement 的开发。
这时候我会评估一下 方式3_implement 的复杂度,
如果很大而且很难肉眼比对的话,我会复制一个
方式3v2_implement ,内容跟方式3_implement 一样。
然后稍微调整 Test case:
public class 方式3TestCase{
public void Test方式3(){
//因为是static 所以可以呼叫,
//这里可能会需要 visible to internal 或暂时开成public
var res = xxx.方式3_implement(mockxxx,mockyyy,mockzzz);
res.aaa.should().be(xxx);
var res2 = xxx.方式3v2_implement(mockxxx,mockyyy,mockzzz);
res2.aaa.should().be(res.aaa);
//这是为了避免自己脑残,当然这样改的前提是 input/output ,
//新旧一致的情况,通常都会有重叠场景,如果没有就不用这么做。
}
}
接着就开始从 方式3v2_implement 下写新的实作,
然后反复跑 Test方式3 ,然后看状况加新的 test method ,补新的测资跟场景。
这里的前提是无副作用,有副作用的就都得透过 mock 先把副作用的影响处理掉,
大多数的时间会花在研究副作用跟子类。(其实就是读code)
就这样而已,没什么 magic,
你想改哪,就只缩哪。
另外原本的 v1 虽然看起来是重复代码,但是过一两个周期就可以回头删除他了,
而且因为他照理说会除了 test 以外没被连结又是新增程式,
对实际的程式基本上影响为0。
然后你会问我,那你原本想干的方式1,2,3,4,5能不能整并,
施主这要看你的需求。
通常我的习惯是上面的方式3Response,我会试着带进去看看,
方式3方式2能不能共用回传值。
如果可以,那才有谈的空间,没有的话,该分歧就分歧,不要乱合。
然后至于假设在方式1,2,3,4,5 还有 token验证之类的上游问题,
那就是另外写 test ,我这个 test 只涵盖这个下游情境。
用“限缩环境”来减少你一次要看的范围,
强化你对局部开发的速度跟掌握力,也是可测试性中很重要的一环。
作者: ian90911 (xopowo)   2024-05-02 16:27:00
感谢分享
作者: now99 (陈在天)   2024-05-02 16:56:00
最重要需求不要三心二意xdddd
楼主: TonyQ (自立而后立人。)   2024-05-02 16:57:00
需求三心二意就是跟着最后一次的需求走,你有写TEST也比较理解哪些是破坏性的需求,哪些是延伸性(不破坏既有条件)y的需求因为不管需求如何变化,你都得确认最后的那个版本你写的是对的然后可测试性高的程式码,你会有比较多端点可以抽换。
作者: viper9709 (阿达)   2024-05-02 21:14:00
推这篇实际
作者: gmoz ( This can't do that. )   2024-05-03 11:25:00
请公司多聘SDET $$$$$ QAQ
作者: new122851 (未若柳絮因风起)   2024-05-03 19:54:00
我的认知是整个project除了main method这个进入点不用写到UT,其他的所有method都要被至少一支UT程式跑到过,包含static的method
作者: yc86209 (yc86209)   2024-05-03 20:34:00
推一个
作者: wulouise (在线上!=在电脑前)   2024-05-03 22:52:00
当你一开始才0%coverage开始的时候,最重要的先做

Links booklink

Contact Us: admin [ a t ] ucptt.com