既然重构不能改变系统外部行为,那就需要自动化测试用例的保护,于是重构前首先要补充自动化测试用例。但是由于遗留代码依赖较深,错综复杂,很多代码都是一个个大泥球,且过大类、过大函数、发散式变换、散弹式修改比比皆是,很多场景混杂揉合,用例很难构造,甚至过大耦合导致很多类都不能实例化出来,必须依赖上下文才能构造,这种情况很难补充用例,补充用例越难,越是不敢重构;要解决设计测试用例问题,必须先把代码进行重构,把功能拆分、迁移、规整、内聚,整理出一个个职责单一、功能内聚小功能代码模块,这么大改动又必须需要自动化测试用例的保护。这就陷入了一个死循环,补充用例需要先重构,重构又需要先补充用例,掉入鸡生蛋还是蛋生鸡的陷阱。
为了解决这个困境,这里推荐一个折中的方法,采用金鱼缸
的方式,先在尽可能有把握保证功能安全的情况下,对代码进行微调,使之逐步适于编写测试用例,或进行一定的解耦分割,使内容变得单一、内聚,从而适于编写用例。
下面,老乐着重介绍下几种常见的很难编写测试用例的遗留代码和微调手法。
一、硬代码依赖
其中,RdKbd()函数是键盘驱动函数,这种情况下如果不对代码做微调,想做自动化测试的话,只能以下做法:
2、做UT,使用mock工具
使用mock工具可以不修改代码构造成用例。但是,问题是此时用例要清楚知道mock的位置,也就是说要知道代码的细节实现逻辑,这就造成测试用例和代码细节实现逻辑的深度耦合,用例变得非常晦涩难懂,且细节实现逻辑稍微变化后,就会造成用例不稳定,造成用例维护工作量过大。
微调手法一、依赖隔离
这样微调后,从正确性和性能角度看,风险是可控的。即可以认为copy1=copy2,测试用例就可以针对copy3开发,这样copy的业务逻辑就和键盘和键盘驱动硬件隔离开了,测试用例设计的简单性、测试环境稳定性、测试问题定位的易识别性、测试性能都会提升。
class A{
public:
void processPORequest(PORequest *request){
log.logEvent(request);
…
}
}
////////////////////////修改为
class B:A{
public:
void processPORequest(PORequest *request){
logEvent(request);
…
}
virtual void logEvent(PORequest *request) {
log.logEvent(request);
}
}
这样,对processPORequest函数进行单元测试时,就可以通过类C继承类B,然后覆盖掉B中的logEvent函数即可隔离对第三方log的依赖。
二、新增功能
微调手法二、发芽
具体做法建议独立成一个接口或类,把新增功能独立出来变成单独函数或类C,这样对这个独立出来的C做单元测试,而不是把新功能代码散落到老代码中,当然,这里也要实现依赖隔离。
当接受一坨大泥球式的代码时,特别是很难读懂来龙去脉的代码是,更是感觉有点碰到一只刺猬,
哪里都是刺,无从下口。这时候微调的手法就是分离关注点,这种方法是一种相对比较粗放的方式,适合对大泥球进行粗分,帮助我们更好的理解原代码,并为下一步精细化重构做好铺垫。
以发散式变换的过大类A为例,具体手法如下:
1、首先尝试选取部分关联紧密的成员变量,将这些成员变量移植到一个新的类C中,并且设置成public封装类型,并给这个新类一个合适的名字。
2、A中通过C.变量的形式引用这些成员变量,编译通过。
3、A中和C.变量关联紧密的代码移植到C中,并变成C的成员函数。
4、A中引用C的新的成员函数
5、C中的成员变量变成private。
这样A就被分割成A、C两个类,然后再选取A中其他成员变量,重复上述5个步骤,渐渐的,A就被拆分为一系列的功能内聚、职责单一的小类,从而就可以补充测试用例并进行下一步精细化重构。
对于非面向对象的代码,比如过长函数,则可以选择其中的作用域大的局部变量或全局变量为起点,抽取到struct中开始做起。
声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/213224.html