
引用
Dirk Beyer, Thomas Lemberger, TESTCOV: Robust Test-Suite Execution and Coverage Measurement, 34th IEEE/ACM International Conference on Automated Software Engineering (ASE), 2019, 1074-1077
摘要
在本文中我们介绍了 TESTCOV,这是一种用于在 C 程序上进行可靠的测试套件执行和测试覆盖率测量的工具。 TESTCOV 在隔离的容器中执行程序测试,以确保系统完整性和可靠的资源控制。 该工具提供每个测试以及整个测试套件的覆盖率统计信息。 TESTCOV 使用基于 XML 的简单交换格式并以 Test-Comp 创建的标准规范约束测试套件。TESTCOV 已成功用于 Test-Comp’19,对 1720 个不同的程序执行近 900 万次测试。 TESTCOV 的源代码在开放源代码许可 Apache 2.0 下发布,可从https://gitlab.com/sosy-lab/software/ test-suite-validator 获得。包括演示视频在内的完整内容,请访问https://doi.org/10.5281/zenodo.3418726。
关键词:测试执行,覆盖率,测试套件规约
1.介绍
现代的测试用例生成器能够生成系统测试用例,以前所未有的方式揭示程序中的错误,但是执行这些测试可能会导致系统故障,修改,信息泄漏或资源耗尽。 因此,程序测试通常在虚拟机或容器(例如 Docker)中执行。 TESTCOV 为此提供了一个轻量级的解决方案:它基于现有的基准测试工具 BENCHEXEC,使用覆盖文件系统和 Linux 控制组来保护文件系统免受修改,并防止在测试执行期间意外使用资源。 与其他容器化技术相比,BENCHEXEC 在使用过程中不需要安装任何其他软件或超级用户特权,因为它仅依赖于 Linux 内核中内置的功能。 TESTCOV 提供每个测试以及整个测试套件的行覆盖,分支覆盖和条件覆盖的覆盖率统计信息,并创建图以可视化所测得的数据。
TESTCOV 已在第一次软件测试国际竞赛(Test-Comp’19)中使用,验证所有 9 位参与者创建的测试套件。 TESTCOV 使用简单的基于 XML 的交换格式进行测试套件规范,该格式由 Test-Comp 建立标准。 所有 9 位参与者都支持交换格式。 过去,测试用例生成器使用专有格式来输出其生成的测试,这导致了两个问题:根据格式的不同,测试套件通常只能使用辅助程序执行或根本不执行,并且由不同测试用例生成器生成的测试套件不能直接进行比较或组合。 基于 XML 的交换格式解决了这些问题。
可获得:TESTCOV 以压缩包的形式在 gitlab 上公开。
相关工作:TESTCOV 旨在统一和执行由测试用例生成器为 C 程序创建的测试套件。 KLEE 提供了一个重放库,该重放库可用于从被测程序创建测试工具,利用该库可以执行 KLEE 专有的测试用例格式的单个测试。 由 AFL-FUZZ2 创建的测试用例可以直接输入程序。 现有的测试执行器都不支持其他测试用例生成器创建的测试,也不支持完整的测试套件的执行。TESTCOV 基于 BENCHEXEC; 其他用于容器化的工具是 Docker3,LXC4 和 Snap5。 BugZoo6 以及 ManyBugs 和 IntroClass 基准测试是与软件 Bug 隔离和可靠执行有关的其他项目。
2.TESTCOV 的结构
图 1 显示了 TESTCOV 的输入和输出。 TESTCOV 获得测试中的 C 程序,要检查的覆盖率标准和测试套件的输入,并创建一个可执行程序,该可执行程序可用于向测试中的程序提供测试,有关测试套件的覆盖率统计信息以及与原始测试套件相同的覆盖范围(就覆盖标准而言)的规约后的测试套件。

图 1 TESTCOV 的输入与输出
A. 测试套件交换格式
TESTCOV 以基于 XML 的测试套件交换格式读写测试套件,该测试套件包括两部分:元数据文件和一组测试用例文件,每个定义一个测试用例。元数据文件是描述测试套件的 XML 文件,并且始终命名为 metadata.xml。图 2 显示了具有所有可用字段的示例元数据文件。一些值得注意的字段是:被测程序的编程语言(),为测试套件创建的覆盖标准(),被测程序的 SHA-256 哈希(),测试套件测试的程序功能(),以及为其创建程序测试的系统结构()。如果测试套件是另一个测试套件的结果,例如,由于测试套件减少,则还可以记录此输入测试套件的文件名()及其 SHA256 哈希()。一个测试用例文件(图 3)包含描述输入值序列的一系列标签。测试套件的目录结构是任意的,它们由 TESTCOV 作为 zip 文件提供并由其创建,以实现高效存储和便捷处理。由于在 Test-Comp 中使用了交换格式,因此许多测试用例生成器都支持以下格式:COVERITEST,CPA-TIGER7,ESBMC,FAIRFUZZ,KLEE,PRTEST,SYMBIOTIC 和 VERIFUZZ。

图 2 测试套件元数据文件示例

图 3 测试套件测试用例示例
B.测试执行
为了执行测试,被测程序是根据测试工具编译的并包含以下两个部分:(1)用于接收测试值的方法 getinput,以及(2)针对测试程序中委派给 getinput 的每种输入方法的新定义。
方法 get_input 从标准输入中以 C 格式字符串读取测试输入,并将其解析为 C 类型。 它支持所有原始类型(最大为 long double)的十六进制(例如 0x4a),整数(例如 74),浮点数(例如 74.5)和字符表示形式(例如'J')以及单行字符串输入。 使用 C 标准库中的方法进行解析。
对于每种输入方法,都会引入一个新定义,该定义使用输入方法返回类型的相应格式类型调用 get_input。 例如:

给定一个测试套件,TESTCOV 首先解析元数据文件,以检查与输入文件和覆盖标准的一致性。 如果其中之一不一致,则会通知用户。 然后,TESTCOV 遍历所有测试用例文件,读取每个测试的测试输入,执行编译的程序,然后通过标准输入顺序传入测试输入并执行。
为确保测试执行不会因非终止测试而停滞,对每个测试执行都设置了时间限制。 另外,如果一个测试用例包含的输入值比必要的少,一旦所有输入值被消耗并且请求一个新的输入值,TESTCOV 将终止执行。
为了确保测试执行不会改变用户的系统,执行恶意操作或相互影响,TESTCOV 使用 RUNEXEC8(BENCHEXEC 的一部分提供了此工具)将每个测试执行隔离在单独的容器和控制组中。 我们对其进行配置,使容器中的测试执行无法访问网络,无法查看或修改其他系统进程,并且可以在可防止原始系统中文件修改的覆盖文件系统上工作。 写入容器内的文件保留在内存中,而不写入磁盘。 在存在文件操作的情况下,将所有文件修改保存在内存中还可以加快测试的执行速度。 Cgroup 是 Linux 内核的一项功能,它可以限制和衡量一个进程及其所有子进程的资源消耗。 TESTCOV 使用它来将内存使用量限制为用户指定的最大值,将计算限制为指定数量的 CPU 内核,并对测试执行施加时间限制。
图 4 显示了具有副作用的程序。 程序采用单个字符作为输入,这里通过 Test-Comp-specific 方法__VERIFIERnondetchar。 如果输入为“ a”,则会产生一个 fork 隐患,它会产生无数个进程,这些进程最终将填满用户系统的进程表并使其无法使用。 如果输入不为“a”,程序将删除一些文件,并检查删除是否成功。 如果为 TESTCOV 提供了一个定义两个分支的测试用例的测试套件,它将正确执行两个分支,但是进程数受到限制(默认为 5000 个进程),并且文件删除仅在执行的容器中发生,而不是在原始文件系统中,因此,在覆盖范围测量仍然准确的情况下,不会对用户系统造成损害。

图 4 产生副作用的程序示例
C.覆盖统计信息
TESTCOV 提供每个测试和整个测试套件的覆盖率信息,并为覆盖率标准创建图。 TESTCOV 在查询语言 FQL 中读取覆盖率标准。当前,它支持块覆盖,分支覆盖和条件覆盖,以及对错误函数的调用覆盖。为了计算覆盖率,TESTCOV 使用 GCC 和 LCOV。 LCOV 将每个行的覆盖范围和程序条件存储在一个跟踪文件中。 LCOV 声称要存储分支覆盖信息,但将短路布尔运算的每个条件视为一个单独的分支。例如,图 6 中的代码包括两个分支:如果条件 x > 0 || x < 0 为真,则进入 if 分支,否则进入 else 分支。 LCOV 将两个条件 x> 0 和 x <0 的每个判断视为一个单独的分支,因此报告该程序有四个分支。对于该程序,第一个条件(x> 0)的求值始终为 true,因此每个程序执行都使用 if 分支。由于从未评估条件 x <0,因此 LCOV 报告的分支覆盖率仅为 25%,而不是预期的 50%。为了避免这种情况并执行正确的分支覆盖率测量,TESTCOV 在程序的每个程序分支的开头添加了程序标签 BRANCH_i(图 7),并使用 LCOV 的行覆盖率测量来检查添加的哪些程序标签被覆盖了。这样,TESTCOV 可以准确地测量分支覆盖范围。
默认情况下,LCOV 仅将所有程序执行的累积覆盖范围存储在一个跟踪文件中。为了获得累积的覆盖率和每个单独的测试用例的单独覆盖率,TESTCOV 使用 LCOV 管理两个单独的跟踪:一个默认跟踪,它是为每个测试执行新创建的,并且仅存储该执行的覆盖范围,而另一个包含累积所有测试执行的覆盖信息。虽然每个测试的覆盖率信息通常对于执行测试没有太大的帮助,但它可以为测试套件的优化和简化提供启发。 TESTCOV 以图表和 CSV 文件的形式提供覆盖率统计信息,可以轻松地对其进行进一步处理。它提供了一个图(图 5),该图显示:(a)执行测试套件的第 n 个测试(x 轴)后的累积测试覆盖率(y 轴)(图中的实线)。 5),以及(b)每个测试用例的测试范围(图 5 中的条形)。图中测试的顺序始终与执行顺序相同。可以看到,例如,覆盖率达到 75.0%的五个测试包含了覆盖率达到 12.5%的三个测试,因为在执行了任何执行之后,累积覆盖率不会增加到 75.0%和 87.5%以上。此外,很明显,只需执行第 6 个测试即可达到与该测试套件的所有 9 个测试一起获得的相同分支覆盖率,因为它自身提供的覆盖率与整个测试套件的累积覆盖率相同。

图 5 独立和累积测试覆盖率


图 6 短路分支代码 图 7 用于计算分支覆盖率的代码
D. 测试套件规约
TESTCOV 通过策略模式实现了测试套件规约,因此可以在现有基础架构中添加不同的算法以减少给定的测试套件。默认情况下,TESTCOV 提供以下测试套件规约技术:如果覆盖标准是要涵盖对错误函数的调用,则 TESTCOV 会创建一个新的测试套件,其中包括覆盖该错误功能的原始测试套件中的一个测试用例。如果覆盖范围的标准是覆盖行,分支或条件,则 TESTCOV 创建一个新的测试套件,该套件可能小于原始测试套件,并且可以实现相同的覆盖范围。为此,它会在每次测试执行后读取记录的累积覆盖率,并且只有在其相应的测试执行增加了累积覆盖率的情况下,才将测试添加到规约测试套件中。 TESTCOV 以任意顺序执行测试,因此此方法不一定会产生最小的测试套件,但是不需要其他附加测试执行或计算,使得该规约方法是简单而有效的。
3.使用
安装:TESTCOV 需要 Python 3.6 或更高版本。 以下命令行将安装 TESTCOV 及其依赖项(从 TESTCOV 源代码的基本目录执行):
> python3 setup.py install
执行:TESTCOV 通过命令行启动,带有三个必需的参数:(1)–test-suite 指定要执行的测试套件,(2)–goal 指定覆盖标准,以及(3)程序文件。 测试套件以 zip 文件的形式提供,覆盖标准以 FQL 语法的文本文件的形式提供。 以下示例命令行在测试套件 suite.zip,覆盖标准 criteria.prp 和程序 prog.c 上运行 TESTCOV:
> testcov –test-suite suite.zip –goal criterion.prp prog.c
输出目录中将包含所有输出文件,即可执行测试工具,简化的测试套件,覆盖率统计信息和图表(以 SVG 格式)。
由于要执行额外的文件系统操作,因此为每个测试执行和覆盖率度量创建单独的容器会增加执行开销。 TESTCOV 提供了可选参数,以在不需要这些功能时将其关闭。 以下命令行显示所有此类参数:
> testcov –help
测试格式自适应:为了使测试用例生成器易于适应基于 XML 的测试格式,我们提供了一个名为 tsbuilder9 的小型 Python 库。 它可用于以已建立的测试套件交换格式创建测试套件元数据和测试用例。
4.应用
TESTCOV 已用于 Test-Comp’19,它在 9 个不同的测试用例生成器上针对 1720 个不同的程序和 2 个不同的覆盖率标准运行了将近 900 万个测试。 TESTCOV 在比赛期间用于执行和覆盖率度量。 竞赛的所有结果均可在线获得。显示几个测试用例生成器或元类别(例如 Cover-Branches)的结果的表格仅列出了 TESTCOV 计算的覆盖率。 单个测试生成器和子类别的表格(例如 coverage-branches.ReachSafety-Arrays-VERIFUZZ11)提供了完整数据,包括累积测试覆盖率的精简版图表。
5.结论
TESTCOV 是用于在 C 程序上执行测试套件的工具,它以 Test-Comp 的简单和标准交换格式读取测试套件,并执行可靠的测试执行。 TESTCOV 使用 BENCHEXEC 作为基础,用于隔离执行的容器和操作系统内核提供的资源控制的控制组。当前版本提供了四个重要覆盖标准的独立和累积覆盖统计信息。 TESTCOV 已成功用于执行 Test-Comp’19。 尽管 TESTCOV 是针对 C 程序实现的,但是所使用的概念可以轻松地转换为其他语言。
致谢
本文由南京大学软件学院 2020 级硕士生曹振飞翻译转述。
感谢国家重点研发计划(2018YFB1003900)和国家自然科学基金(61832009,61932012)支持!