爱陪小樱桃

导航

 

标签(空格分隔): 掌握代码级测试的基本理念与方法


掌握代码级测试的基本理念与方法

通常情况下,代码级测试的工作都是由开发人员完成,但是测试框架选型、覆盖率统计工具选型、测试用例设计原则等都需要资深的测试工程师或者测试架构师参与。所以,代码级测试这个系列,我会和你分享测试人员应该具备的代码级测试基础知识,为你呈现一幅包括代码级测试技术入门、方法论、用例设计,以及覆盖率衡量、典型难点、解决思路的全景技术视图。为了能更好地协助开发人员做好代码级测试,所以我今天的这次分享是根据实际工程项目中的实践,总结了五种常见的代码错误,以及对应的四大类代码级测试方法。掌握了这些错误类型、测试方法,相信你就可以搞定代码级测试了,即使自己不用去完成测试工作,也可以让开发人员对你另眼相看,可以更高效地互相配合完成整个项目。

  • 代码级测试的测试方法一定是一套测试方法的集合,而不是一个测试方法。 因为单靠一种测试方法不可能发现所有潜在的错误,一定是一种方法解决一部分或者一类问题,然后综合运用多种方法解决全部问题。
  • 本着先发现问题,然后解决问题的思路,我在正式介绍代码级测试方法之前,先来概括一下常见的代码错误类型,然后我们再一起讨论代码级测试有哪些方法。这样,我们就可以清晰地看出,每一种代码级测试方法都能覆盖哪些类型的代码错误。
  • 根据过往的经验来看,代码错误,可以分为“有特征”的错误和“无特征”的错误两大类。“有特征”的错误,可进一步分为语法特征错误、边界行为错误和经验特征错误;“无特征”的错误,主要包括算法错误和部分算法错误。
    接下来,我将和你详细说说这五类代码错误的具体含义是什么。

常见代码错误类型

第一,语法特征错误

语法特征错误是指,从编程语法上就能发现的错误。比如,不符合编程语言语法的语句等。如果你使用 IDE 环境进行代码开发,那么 IDE 可以提示你大部分的这类错误,而且只有解决了这类错误,才能编译通过。但是,还会有一些比较隐晦的语法特征错误,IDE 不能及时发现,而且也不会影响编译,只会在运行阶段出错。

void demoMethod(void){ int a[10]; a[10]=88; ...}

比如,这段 C 语言代码就存在数据越界的问题。
很显然,你从语法上很容易就能发现,这段代码初始化了一个长度为 10 的整型数组 a,但数组下标从 0 开始,所以最大可用的数组空间应该是 a[9],而这里却使用了 a[10],造成数组越界,访问了未被初始化的内存空间,代码运行时(Runtime)就会造成意想不到的结果。

第二,边界行为特征错误:

边界行为特征错误是指,代码在执行过程中发生异常,崩溃或者超时。之所以称为“边界”,是由于此类错误通常都是发生在一些边界条件上。

int Division(int a, int b){ return a/b;}

这段 C 语言代码就存在具有边界行为特征的错误。当 b 取值为 0 时,Division 函数就会抛出运行时异常。

第三,经验特征错误

经验特征错误是指,根据过往经验发现代码错误。


void someMethod(void)
{
  ...
  if(i=2)
  {
    // if the value of i equals to 2, call method "operationA"
    operationA();
  }
  else
  {
    // if the value of i doesn't equal to 2, call method "operationB"
    operationB();
  }
}

这段 C 语言代码,就是一个典型的具有经验特征错误的代码片段。代码想要表达的意思是:如果变量 i 的值等于 2,就调用函数 operationA;否则,调用函数 operationB。但是,代码中将“if(i==2)”错误地写成了“if(i=2)”,就会使原本的逻辑判断操作变成了变量赋值操作,而且这个赋值操作的返回结果永远是 true,即这段代码永远只会调用 operationA 的分支。显然,“if(i=2)”在语法上没有错误,但是从过往经验来看,这就很可能是个错误了。也就是说,当你发现一个原本应该出现逻辑判断语句的地方,现在却出现了赋值语句,那就很有可能是代码写错了。

第四,算法错误

算法错误是指,代码完成的计算(或者功能)和之前预先设计的计算结果(或者功能)不一致。这类错误直接关系到代码需要实现的业务逻辑,在整个代码级测试中所占比重最大,也是最重要的。但是,完全的算法错误并不常见,因为不能准确完成基本功能需求的代码,是一定不会被递交的。所以,在实际工程项目中,最常见的是部分算法错误。

第五,部分算法错误

部分算法错误是指,在一些特定的条件或者输入情况下,算法不能准确完成业务要求实现的功能。这类错误,是整个代码级测试过程中最常见的类型


int add(int a, int b)
{
  return a+b;
}

这段 C 语言代码,完成了两个 int 类型整数的加法运算。在大多数情况下,这段代码的功能逻辑都是正确的,能够准确地返回两个整数的加法之和。但是,在某些情况下,可能存在两个很大的整数相加后和越界的情况,也就是说两个很大的 int 数相加的结果超过了 int 的范围。这就是典型的部分算法错误。

代码级测试常用方法:

介绍完了语法特征错误、边界行为特征错误、经验特征错误、算法错误、部分算法错误这五类代码错误后,我们再回过头来看看代码级测试的方法有哪些,这些测试方法又是如何揭露这五类代码错误的。

在我看来,代码级测试方法主要分为两大类,分别是静态方法和动态方法

  • 静态方法,顾名思义就是在不实际执行代码的基础上发现代码缺陷的方法,又可以进一步细分为人工静态方法和自动静态方法;
  • 动态方法是指,通过实际执行代码发现代码中潜在缺陷的方法,同样可以进一步细分为人工动态方法和自动动态方法。
    这里需要注意到的是,我在这篇文章中只会和你分享这四种方法具体是什么,各有何局限性和优势,分别可以覆盖哪些错误类型。而对于,具体如何用这四种方法完成代码级测试,测试用例如何设计、常用的测试工具如何使用,我会在后面介绍

第一,人工静态方法

人工静态方法是指,通过人工阅读代码查找代码中潜在错误的方法,通常采用的手段包括,开发人员代码走查、结对编程、同行评审等。

理论上,人工静态方法可以发现上述五类代码错误,但实际效果却并不理想。 这个方法的局限性,主要体现在以下三个方面:

1.过度依赖于代码评审者的个人能力,同样的评审流程,发现的问题却相差悬殊;
2.如果开发人员自行走查自己的代码,往往会存在“思维惯性”,开发过程中没有能考虑的输入和边界值,代码走查时也一样会被遗漏;
3.由于完全依赖人工,效率普遍较低。

第二,自动静态方法

自动静态方法是指,在不运行代码的方式下,通过词法分析、语法分析、控制流分析等技术,并结合各种预定义和自定义的代码规则,对程序代码进行静态扫描发现语法错误、潜在语义错误,以及部分动态错误的一种代码分析技术。

自动静态方法可以发现语法特征错误、边界行为特征错误和经验特征错误这三类“有特征”的错误,但对于算法错误和部分算法错误这两种“无特征”的错误却无能为力。根本原因在于,自动静态方法并不清楚代码的具体业务逻辑。

  • 目前,自动静态方法无论是在传统软件企业,还是在互联网软件企业都已经被广泛采用,往往会结合企业或项目的编码规范一起使用,并与持续集成过程紧密绑定。
    你需要根据不同的开发语言,选择不同的工具。目前有很多工具都可以支持多种语言,比如 Sonar、Coverity 等,你可以根据实际需求来选择。

第三,人工动态方法

  • 在代码级测试中,人工动态方法是最主要的测试手段,可以真正检测代码的逻辑功能,其关注点是“什么样的输入,执行了什么代码,产生了什么样的输出”,所以最善于发现算法错误和部分算法错误。
  • 目前,不同的编程语言对应有不同的单元测试框架,比如,对 Java 语言最典型的是 Junit 和 TestNG,对于 C 语言比较常用的是 Google Test 等。

第四,自动动态方法

自动动态方法,又称自动边界测试方法,指的是基于代码自动生成边界测试用例并执行,以捕捉潜在的异常、崩溃和超时的方法。

自动动态方法,可以覆盖边界行为特征错误, 通常能够发现“忘记处理某些输入”引起的错误(因为容易忘记处理的输入,往往是“边界”输入)。但是它对于发现算法错误无能为力,毕竟工具不可能了解代码所要实现的功能逻辑。

今天,我将和你详细讨论人工静态测试方法和自动静态测试方法,来帮你理解研发流程上是如何保证代码质量的,以及如何搭建自己的自动静态代码扫描方案,并且应用到项目的日常开发工作中去。

  • 人工静态方法本质上属于流程上的实践,实际能够发现问题的数量很大程度依赖于个人的能力,所以从技术上来讲这部分内容可以讨论的点并不多。但是,这种方法已经在目前的企业级测试项目中被广泛地应用了,所以我们还是需要理解这其中的流程,才能更好地参与到人工静态测试中。
  • 而自动静态方法,可以通过自动化的手段,以很低的成本发现并报告各种潜在的代码质量问题,目前已经被很多企业和项目广泛采用,并且已经集成到 CI/CD 流水线了。作为测试工程师,我们需要完成代码静态扫描环境的搭建。接下来我会重点和你分享这一部分内容。

人工静态方法

通过我上一次的分析,我们知道了人工静态方法检查代码错误,主要有代码走查、结对编程,以及同行评审这三种手段。那么我们接下来就看一下这三种方法是如何执行的。

  • 代码走查(Code Review),是由开发人员检查自己的代码,尽可能多地发现各类潜在错误。但是,由于个人能力的差异,以及开发人员的“思维惯性”,很多错误并不能在这个阶段被及时发现。
  • 结对编程(Pair Programming),是一种敏捷软件开发的方法,一般是由两个开发人员结成对子在一台计算机上共同完成开发任务。其中,一个开发人员实现代码,通过被称为“驾驶员”;另一个开发人员审查输入的每一行代码,通常被称为“观察员”。当“观察员”对代码有任何疑问时,会立即要求“驾驶员”给出解释。解释过程中,“驾驶员”会意识到问题所在,进而修正代码设计和实现。实际执行过程中,这两个开发人员的角色会定期更换。
  • 同行评审(Peer Review),是指把代码递交到代码仓库,或者合并代码分支(Branch)到主干(Master)前,需要和你同技术级别或者更高技术级别的一个或多个同事对你的代码进行评审,只有通过所有评审后,你的代码才会被真正递交。如果你所在的项目使用 GitHub 管理代码,并采用 GitFlow 的分支管理策略,那么在递交代码或者分支合并时,需要先递交 Pull Request(PR),只有这个 PR 经过了所有评审者的审核,才能被合并。这也是同行评审的具体实践。目前,只要你采用 GitFlow 的分支管理策略,基本都会采用这个方式。
  • 对于以上三种方式,使用最普遍的是同行评审。因为同行评审既能较好地保证代码质量,又不需要过多的人工成本投入,而且递交的代码出现问题后责任明确,另外代码的可追溯性也很好。

自动静态方法

相比于编译器,可以做到对代码更加严格、个性化的检查;
不真正检测代码的逻辑功能,只是站在代码本身的视角,基于规则,尽可能多地去发现代码错误;
由于静态分析算法并不实际执行代码,完全是基于代码的词法分析、语法分析、控制流分析等技术,由于分析技术的局限性以及代码写法的多样性,所以会存在一定的误报率。

  • 基于这些特点,自动静态方法通常能够以极低的成本发现以下问题:
  • 使用未初始化的变量;
    变量在使用前未定义;
    变量声明了但未使用;
    变量类型不匹配;
    部分的内存泄漏问题;
    空指针引用;
    缓冲区溢出;
    数组越界;
    不可达的僵尸代码;
    过高的代码复杂度;
    死循环;
    大量的重复代码块;

正是由于自动静态方法具有自动化程度高,检查发现问题的成本低以及能够发现的代码问题广等特点,所以该方法被很多企业和项目广泛应用于前期代码质量控制和代码质量度量。

代码本地开发阶段,IDE 环境就可以自动对代码实现自动静态检查;当代码递交到代码仓库后,CI/CD 流水线也会自动触发代码静态检查,如果检测到潜在错误,就会自动邮件通知代码递交者。

自动静态方法的实际例子

第一个例子,自动静态方法检查语法特征错误。

图 1 右侧,就是通过 C 语言的自动静态扫描工具 splint 发现的这个问题,并给出的分析结果。
image.png-153.4kB

第二个例子,自动静态方法检查内存空间被释放后继续被赋值的错误。

如图 2 左侧所示的 C 语言代码,我们用 malloc 函数申请了一个内存空间,并用指针 a 指向了这个空间,然后新建了一个指针 b 也指向这个空间,也就是指针 a 和指针 b 实际上指向了同一个内存空间。之后,我们把指针 a 指向的空间释放掉了,意味着指针 b 指向的空间也被释放了。但是,此时代码却试图去对指针 b 指向的空间赋值,显然这会导致不可预料的后果。幸运的是,C 语言的自动静态扫描工具 splint 发现了这个问题,并给出了详细解释。
image.png-247.6kB

实际案例:Sonar 实战

  • 现在,我们已经了解了自动静态代码扫描的基本概念,那怎么把这些知识落地到你的实际项目中呢?我们就从目前主流的自动静态工具 Sonar 的使用开始吧。
    考虑到你可能以前并没有接触过 Sonar,所以我会按照 step by step 的节奏展开。如果你已经用过 Sonar 了,你可以跳过在 Mac 电脑上建立 Sonar 的步骤,从完成你的 Maven 项目的自动静态分析开始。
通过这个 Sonar 实例,你可以掌握:
  • 1.搭建自己的 SonarQube 服务器;
    2.扫描 Maven 项目,并将结果报告递交到 SonarQube 服务器;
    3.在 IntelliJ IDE 中集成 SonarLint 插件,在 IDE 中实现实时的自动静态分析;

首先,在Sonar 官网下载 LTS(Long-term Support)版本的 SonarQube 6.7.5。这里需要注意的是,我不推荐在实际工程项目中使用最新版的 SonarQube,而是建议使用 LTS 版本以保证稳定性和兼容性。解压后运行其中的 bin/macosx-universal-64 目录下的 sonar.sh,这里需要注意运行 sonar.sh 时要带上“console”参数。如果执行完成的界面如下图 3 所示,那么说明你的 SonarQube 服务已经成功启动。
image.png-849.4kB

此时,你可以尝试访问 localhost:9000,并用默认账号(用户名和密码都是“admin”)登录。

为了简化建立 SonarQube 的步骤,所有的内容我都使用了默认值。比如,我直接使用了 SonarQube 内建的数据库,端口也采用了默认的 9000。但是,在实际工程项目中,为了 Sonar 数据的长期可维护和升级,我们通常会使用自己的数据库,需要执行下面这些步骤:

  • 安装 SonarQube 之前,先安装数据库;建立一个空数据库并赋予 CRUD 权限;修改 SonarQube 的 conf/sonar.properties 中的 JDBC 配置,使其指向我们新建的数据库。我们也可以采用同样的方法,来修改默认的端口。

因为要在 Maven 项目中执行代码静态扫描,为此我们需要先找到 $MAVEN_HOME/conf 下的 settings.xml 文件,在文件中加入 Sonar 相关的全局配置,具体需要加入的内容如下所示:


<settings>
    <pluginGroups>
        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
    </pluginGroups>
    <profiles>
        <profile>
            <id>sonar</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <sonar.host.url>
                  http://myserver:9000
                </sonar.host.url>
            </properties>
        </profile>
     </profiles>
</settings>

最后,我们就可以在 Maven 项目中,执行“mvn clean verify sonar:sonar”命令完成静态代码扫描。如果你是第一次使用这个命令,那么 mvn 会自动下载依赖 maven-sonar-plugin,完成后发起代码的静态扫描,并会自动把扫描结果显示到 SonarQube 中。

  • 图 4 所示的结果
    GUI 测试项目代码的扫描结果。
    image.png-337.9kB

扫描结果是 Passd,但同时也发现了三个 Code Smell 问题,或者说是改进建议,如图 5 所示。Class 建议放在 package 中;导入了 java.io.BufferedInputStream,但没有在实际代码中使用,建议删除;建议变量名字不要包含下划线。
image.png-434.4kB

在日常工作中你可能还想要实时看到 Sonar 分析的结果,这样可以大幅提高修改代码的效率。为此,我们可以在 IDE 中引入 SonarLint 插件。你可以通过 IDE 的 plugin(插件)管理界面安装 SonarLint。
安装完成后重启 IDE,你就可以在 IDE 环境中实时看到 Sonar 的静态分析结果了,如图 6

image.png-181.9kB

另外,在 IDE 中绑定 SonarQube,就可以把 SonarLint 和 SonarQube 集成在一起了,如图 7 所示。集成完成后,IDE 本地的代码扫描就能使用 SonarQube 端的静态代码规则库了,在企业级的项目中,一般要求所有开发人员都使用统一的静态代码规则库,所以一般都会要求本地 IDE 的 SonarLint 与 SonarQube 集成。
image.png-159.3kB

目前,自动静态扫描通常都会和持续集成的流水线做绑定,最常见的应用场景是当你递交代码后,持续集成流水线就会自动触发自动静态扫描,这一功能是通过 Jenkins 以及 Jenkins 上的 SonarQube 插件来完成的,当你在 Jenkins 中安装了 SonarQube Plugin,并且将 SonarQube 服务器相关的配置信息加入 Plugin 之后,你就可以在 Jenkins Job 的配置中增加 Sonar 静态扫描步骤了。

总结

人工静态方法,主要有代码走查、结对编程和同行评审三种常用方法。在工程实践中,同行评审因为可以保证代码质量、效率高、责任明确等特点,已经被广泛采用。
自动静态方法,因为自动化程度高、成本低、发现的代码问题广等特点,是常用的代码级测试方法。

在这里,测试工程师需要完成代码静态扫描环境的搭建,考虑到你以前可能没有接触过 Sonar,我按照 step by step 的思路,带你一起搭建了一套代码静态扫描环境,并分享了一个 Maven 项目代码静态扫描的实例。

posted on 2020-04-14 23:25  cherry小樱桃  阅读(465)  评论(0编辑  收藏  举报