圈复杂度和可测试性

  1. 最好用的C++圈复杂度分析工具:pip install lizard,没想到它解析C++的函数块超级快,可以用来作为建立进一步的代码片分析的基础,完胜其他所有工具。
  2. 我花了1天多将一个C++模块测试覆盖率做到100%,对于代码如何才具有良好可测试性有了直观的经验,从测试的角度看代码的设计是否简洁是一个非常合理的角度。
  3. 如果代码的可测试性很好,那么AI也能非常容易的处理它,可见未来的代码,不是要写的复杂,就是写的直接简洁,实际上就是要保持线性。

如何将一个难以测试的类改进为易于测试的类?

例子1,切分辅助的纯函数

让这个类的构造依赖的参数简单,简单不一定是参数个数少,而是这些参数的构造本身的依赖不要有太多深度。

以前有一个痛点问题是,一个类的内部有一些成员变量不好修改,于是不好再测试的时候控制函数内部的分支行为。解决方式是:将控制复杂行为的函数实现拆分,拆分为调用几个辅助函数,这几个辅助函数是纯函数,依赖的参数都通过函数参数传递(即使这个参数是类成员变量就有的),这样这个辅助函数就容易被测试,因为是一个纯函数,因为都通过函数参数传递状态。
例子:

class A{
public:
  void run(){
      if(config.enable_xxx){
         do_something();
      }
  }
  void dosomthing(){
    status.value = ...
  }
private:
  Config config;
  Status status;
}

这个run就不好测试,因为测试方不好控制Config的状态。修改如下:

class A{
public:
  void run(){
    do_something(config, status);
  }

  bool do_somthting(const Config& config, const Status& status){
    if(!config.enable_xxx){
       return false;
    } 
    status.value = ...
  }
private:
  Config config;
  Status status;
}

这个时候就可以对 do_something 做测试了,因为do_somthting依赖的对象,和修改的对象,都是通过函数参数传递的,是一个纯函数。那么测试函数和A::run 对于do_somthting来说都是同等的访问能力。

例子2: 处理内部的返回值问题

下面的类,内部对于某个API请求对结果做处理,但是测试方不易于控制API请求的结果,不好测试到对应分支。

class A{
public:
   void run(){
       xxx v;
       bool ret = receiver.pop(v);
       if(ret){
         ....
       }
   }
}

解决方式是把获取数据和处理数据完全的分开成两个部分:

class A{
public:
   void run(){
     xxx v;
     bool ret = receiver.pop(v);
     on_receive_v(ret, v);
   }
   bool on_receive_v(bool ret, const xxx& v){
       if(!ret){
         return false;
       }
       ...
   } 
}

这样分支跑到 on_receive_v 里面去,而 on_receive_v 是一个易于通过参数控制分支测试的函数。

这个问题的本质是:代码如何做依赖倒置(Dependency Inverse),依赖注入(Dependency Inject)。很多教代码依赖倒置,依赖注入的教程从这个角度来说都教错了,应该从测试的角度来教。从测试的角度来说,如果不做依赖倒置和依赖注入设计,那么这个代码的可测试性就很差,因为它依赖的决定了代码内部分支走向的状态,外部无法便利的动态“配置”。

面向对象方面:如果一个类 A 内部直接创建了 B的实例子,那么你要在测试的时候动态调整A内部的B实例的状态,就非常困难。所以,首先要让B是通过 A的构造函数,或者A的set接口传入的,这样测试的时候B的实例你才有机会动态传入。其次,A内部用到的B的接口,你就应该设计成 virtual 方法,否则的话 B 类的这些方法的行为可能很难按需调整,如果是 virtual 方法,你可以轻易做一个mock的子类B实现,测试的时候使用你的MockB的实例。

函数式方面:策略依赖的函数,可以把函数指针作为参数。

posted @ 2024-03-30 15:49  ffl  阅读(127)  评论(0编辑  收藏  举报