目录
前言
1 安装
1.1 下载解压
1.2 配置环境变量
1.3 入门示例脚本
2 Mutator Plugin 编译器插件
2.1 如何使用插件?
2.2 插件配置选项
2.3 Major 支持的变异算子
2.4 突变体的日志记录
2.5 生成突变体的源代码
3 构建系统集成
4 Major 突变语言 (MML)
4.1 语句作用域
4.2 覆盖和扩展定义
4.3 自定义突变算子组
4.4 示例 MML 文件
5 Major 的程序分析器
5.1 程序分析器配置选项
5.2 设置分析目标
5.3 性能优化
6 兼容性问题和注意事项
本文出处链接:[https://blog.csdn.net/qq_59075481/article/details/141371759]
前言
Major (主要变异框架, The Major Mutation Framework)是一个高效、灵活的突变分析框架,支持:
- 在编译期间生成和嵌入突变体;
- 导出源代码突变体;
- 抑制等效突变体;
- 对于给定的一组测试,计算突变覆盖率和存活突变体集;
- 针对给定的一组测试计算完整的突变测试检测矩阵。
目前最新版本是 2024.5 的 v.2.2.0 版本,程序运行在 JDK 8 环境中,并且最高支持 语言级别 8 的代码。
主要突变框架提供了三个主要组件:
- Major 变异器:Java 编译器 (javac) 的编译器插件;
- Mml 配置文件:一种用于定制突变生成的 DSL(领域特定语言);
- 分析器:一种可有效确定活突变体、突变体检测率或完整突变体测试矩阵的测试运行器;
变异器(Mutator) 是打包到 major.jar 包里面的,而现代分析器是集成到 Apache Ant 构建系统中的。除了基于 Ant 的增强分析器,Major 还保留了早期版本的经典分析器(Classic analyzer)。
1 安装
1.1 下载解压
Major 的安装配置是很简单的,首先进入 官网 (https://mutation-testing.org) 的下载页下载最新的发布版本:
下载下来是一个 zip 压缩包,使用解压缩软件如 7zip, WinRAR 等即可解压缩。
解压缩后你将得到下面的目录结构(在 major 目录中):
- bin (Major 组件的可执行文件,是一些 Shell 脚本)
- ant
- major
- mmlc
- doc (当前版本的说明文档)
- major.html
- example (如何使用 Major 的示例)
- ant (Ant 中编译和测试)
- …
- run.sh (执行脚本)
- standalone (经典模式中编译和测试)
- …
- run.sh (执行脚本)
- runAll.sh (合并执行脚本)
- ant (Ant 中编译和测试)
- lib (Major 使用的库)
- mml (mml 文件示例)
1.2 配置环境变量
major 需要添加到系统环境变量中,添加变量 MAJOR_HOME 并指定变量值为 major 根路径。
然后,重新打开 Command Terminal (CMD) 终端输入 major 辅助参数进行测试运行:
java -jar %MAJOR_HOME%\lib\major.jar -h
结果如下:
1.3 入门示例脚本
在 bin 和 example 目录下面有一些 shell 脚本供 Linux 用户使用,这些脚本通过在 java 和 javac 下调用 major.jar 来生成代码的变异体或者变异 mml 文件或者测试运行程序等。但是作者并没有提供 Windows 下面的可执行脚本,所以有些文献就误以为只能在 Linux 下运行,实际上并不是这样。(在 Windows 下面使用 Git Bash 执行这里的 Shell 脚本兼容性并不好)
下面我给出根据原脚本原理,修改的运行在 Windows 下的命令行脚本:
注意,完整的 2.2.0 版本和修改后的版本可以在这里获取:
https://pan.baidu.com/s/1RouKYi2GL7GbVgZVtYeFkA?pwd=6666
提取码:6666
major/bin/ant.bat >
@echo off
set BASE=%~dp0..
java -XX:ReservedCodeCacheSize=256M "-Xbootclasspath/a:%BASE%\lib\major-rt.jar" -jar "%BASE%\lib\ant\ant-launcher.jar" %*
echo "java -XX:ReservedCodeCacheSize=256M "-Xbootclasspath/a:%BASE%\lib\major-rt.jar" -jar "%BASE%\lib\ant\ant-launcher.jar" %*"
major/bin/major.bat >
@echo off
setlocal enabledelayedexpansion
:: Get the base directory relative to the location of the batch script
set "BASE=%~dp0.."
set "CLASSPATH=%BASE%\lib\major.jar"
set "MML=%MML%"
set "LOGGING=%LOGGING%"
set "EXPORT=%EXPORT%"
set "REFACTOR=%REFACTOR%"
:: Process the command line arguments
:parse_args
if "%1"=="" goto :prepare_plugin_arg
if "%1"=="-classpath" set "CLASSPATH=%CLASSPATH%;%~2" & shift & shift & goto :parse_args
if "%1"=="-cp" set "CLASSPATH=%CLASSPATH%;%~2" & shift & shift & goto :parse_args
if "%1"=="--logging" (
if "!LOGGING!"=="" (
set "LOGGING=logging:%~2"
) else (
set "LOGGING=!LOGGING!,%~2"
)
shift & shift
goto :parse_args
)
if "%1"=="--mml" (
set "MML=mml:%~2"
shift & shift
goto :parse_args
)
if "%1"=="--export" (
set "EXPORT=%~2"
shift & shift
goto :parse_args
)
if "%1"=="--refactor" (
set "REFACTOR=%~2"
shift & shift
goto :parse_args
)
:: Collect additional Java arguments
set "JAVA_ARGS=!JAVA_ARGS! %1"
shift
goto :parse_args
:prepare_plugin_arg
set "PLUGIN_ARG=-Xplugin:MajorPlugin"
if not "!LOGGING!"=="" set "PLUGIN_ARG=!PLUGIN_ARG! !LOGGING!"
if not "!MML!"=="" set "PLUGIN_ARG=!PLUGIN_ARG! !MML!"
if not "!EXPORT!"=="" set "PLUGIN_ARG=!PLUGIN_ARG! !EXPORT!"
if not "!REFACTOR!"=="" set "PLUGIN_ARG=!PLUGIN_ARG! !REFACTOR!"
:: Execute Java command
javac -cp "%CLASSPATH%" -encoding utf8 "%PLUGIN_ARG%" %JAVA_ARGS%
endlocal
major/bin/mmlc.bat >
@echo off
:: 获取脚本所在目录的上一级目录
set BASE=%~dp0..
set MML=%1
:: 检查是否有输入文件
if "%MML%"=="" (
echo usage: mmlc.bat file.mml
exit /b 1
)
:: 运行Java程序
java -jar "%BASE%\lib\major.jar" --mmlc "%MML%"
major/example/runAll.bat >
@echo off
echo "Using Major standalone"
cd standalone
cmd/c .\runWinG.bat
cd ..
echo.
echo "Using Major with Ant"
cd ant
cmd/c .\runWinG.bat
cd ..
pause
major/example/ant/run.bat >
@echo off
set MAJOR_HOME=..\..
echo.
echo Compiling and mutating project
echo (ant -Dmutator="=mml:%MAJOR_HOME%/mml/tutorial.mml.bin" clean compile)
echo.
cmd/c %MAJOR_HOME%\bin\ant.bat -Dmutator="mml:%MAJOR_HOME%/mml/tutorial.mml.bin" clean compile
echo.
echo Compiling tests
echo (ant compile.tests)
echo.
cmd/c %MAJOR_HOME%\bin\ant.bat compile.tests
echo.
echo Run tests without mutation analysis
echo (ant test)
cmd /c %MAJOR_HOME%\bin\ant.bat test
echo.
echo Run tests with mutation analysis
echo (ant mutation.test)
cmd/c %MAJOR_HOME%\bin\ant.bat mutation.test
pause
major/example/ant/clean.bat >
@echo off
set MAJOR_HOME=..\..
echo.
echo Clean project folder
echo (%MAJOR_HOME%\bin\ant.bat clean)
echo.
cmd/c %MAJOR_HOME%\bin\ant.bat clean
echo.
echo (del /f preprocessing.ser)
del /f preprocessing.ser 2>nul
major/example/standalone/run.bat >
警告:此脚本的部分功能正在使用 Windows PowerShell 的 Tee-Object cmdlet 重定向输出流,请确保脚本在部署了至少 PowerShell 3.0 的系统上运行,并允许执行 PowerShell 命令。
@echo off
set MAJOR_HOME = ../../
echo "- Running javac without the mutation plugin"
echo " (javac triangle/Triangle.java)"
javac triangle/Triangle.java
echo.
echo "- Running javac with the major mutation plugin enabled"
echo " (%MAJOR_HOME%/bin/major --mml %MAJOR_HOME%/mml/tutorial.mml.bin triangle/Triangle.java)"
cmd /c "%MAJOR_HOME%\bin\major.bat" --mml %MAJOR_HOME%\mml\tutorial.mml.bin triangle\Triangle.java
echo.
echo "- Compiling test case (major-rt.jar has to be on the classpath!)"
echo " (javac -cp .;%MAJOR_HOME%/lib/major-rt.jar TriangleTest.java)"
javac -cp .;%MAJOR_HOME%/lib/major-rt.jar TriangleTest.java
echo.
echo "- Executing test case (major-rt.jar has to be on the classpath!)"
echo " (java -cp .;%MAJOR_HOME%/lib/major-rt.jar -Dfile.encoding=utf-8 TriangleTest)"
echo.
REM java -cp .;%MAJOR_HOME%/lib/major-rt.jar -Dfile.encoding=utf-8 TriangleTest
set TIMESTAMP=%DATE%%TIME%
set TIMESTAMP=%TIMESTAMP: =_%
set TIMESTAMP=%TIMESTAMP:/=_%
set TIMESTAMP=%TIMESTAMP::=_%
powershell -command "java -cp '.;%MAJOR_HOME%/lib/major-rt.jar' '-Dfile.encoding=utf-8' TriangleTest | Tee-Object -FilePath 'test_%TIMESTAMP%.txt'"
pause
major/example/standalone/clean.bat >
@echo off
set MAJOR_HOME=..\..
echo.
echo Clean project folder
del /f /s /q *.log 2>nul
del /f /s /q test_*.txt 2>nul
:: 获取当前批处理文件的目录
pushd %~dp0
:: 删除所有 .class 文件
echo Deleting .class files in the current directory and subdirectories...
del /S /Q "*.class"
示例脚本的运行:
运行独立模式的脚本:
将生成如下日志文件:
其中 test_时刻.txt 文件是运行时命令行输出结果,其他的为 major 原生的日志。
命令行中运行 Ant 集成模式:
测试结果:
将在目录下面生成 csv 数据文件记录测试运行过程中的变异体覆盖情况的数据:
注意:使用 Ant 集成模式需要配置 build.xml 文件,具体设置方式看后文说明。
bin 目录下面的三个文件其实是 .sh 文件。ant 负责生成变异和执行 Ant 分析模式,包括 complie 和 clean & complie 两个模式;major 通过生成变异体和用经典模式运行程序;mmlc 用于编译 mml 配置文件为 bin 文件,设置运行时配置时候必须使用编译后的 bin 文件。
最后,我还根据命令编写了一个 GUI 程序,由于版权审核限制就不发了:
其实,major 根本没有图形界面,只能通过命令执行;并且 major 的这些可执行脚本只通过一些支持的 major 命令提供基本功能,以供入门者使用。如果需要更多扩展,则需要引入下面的各项命令和参数。
2 Mutator Plugin 编译器插件
Major 的 mutator 是一个 javac 编译器插件,用于遍历和转换抽象语法树 (AST)。所有 mutants 都嵌入到 AST 中并编译为字节码。可以在运行时启用单个 mutant,而无需重新编译。
2.1 如何使用插件?
mutator 其实就被打包在 lib/major.jar 包里面,通过 javac 运行时参数 -Xplugin:MajorPlugin 即可调用此插件。具体来说,在安装完成后,每次运行 major 都得通过控制台命令行(命令提示符终端)来执行 mutator 插件的配置选项,这些命令我将在下面的 2.2 小节完全介绍。
一般地,用户可以通过在控制台执行 javac 命令来编译 Java 源文件。通过 javac -version 命令查看编译器版本,如果正确显示了版本,且版本号为 1.8 则说明 JDK 环境配置正确;否则,请参照 [ ] 的说明予以修复。
在命令行输入 javac -help 查看基本命令:
然后再输入 javac -X 查看非标准选项的提示:
1)生成变异体
使用 major 时需要通过 -cp 选项将 major.jar 的本地路径添加到运行时的类搜索路径。如果你想使得此路径长期生效,可以通过添加系统环境变量中的 classpath 类路径来完成。
通过 -Xplugin 选项来启用插件的功能,JVM 会尝试加载 major.jar 中的插件。
例如:
javac -cp ".;D:\软件测试\major\lib\major.jar" -encoding gbk "-Xplugin:MajorPlugin mml:D:\软件测试\major\mml\tutorial.mml.bin" triangle\Triangle.java
该命令设置了类搜索路径为当前目录和 "D:\软件测试\major\lib\major.jar" (本地计算机中 major.jar 的路径) ,然后指定了编码格式为 GBK,以及要编译的源代码类文件 ".\triangle\Triangle.java"。在编译时使用插件 MajorPlugin,并选择为插件传递特定选项 mml,提供了编译后的 .mml.bin 配置文件来设置突变生成器的自定义变异算子。
注意:在使用 -Xplugin 选项时,针对具体插件的选项需要跟随 -Xplugin 选项一起传入,方法是在 -Xplugin 选项前的位置和最后一个插件特定选项书写结束的位置各加上一个引号 ( " ) 。如:
- "-Xplugin:MajorPlugin mml:D:\软件测试\major\mml\tutorial.mml.bin"
- "-Xplugin:MajorPlugin mml:D:\软件测试\major\mml\tutorial.mml.bin mutants.log:./log1.log"
不同插件选项之间使用空格间隔。
如果执行成功,你将得到类似这样的提示信息:
Generated 170 mutants (90 ms)
其中,数字 170 表示生成的变异体总数,90 ms 代表执行生成所消耗的总时间。
然后,你将得到下面的三个日志:
其中,mutants.log 就是默认情况下生成的日志文件(当使用 mutants.log:<FILE> 修改路径和文件名时以具体情况为准)。
日志文件大致包含 7~8 个字段(旧版是 7 个字段),包含了生成的变异体信息:
需要注意的是,为了节约磁盘资源,默认情况下 major 是禁用变异体源文件生成的,但可以通过选项开启。并且 major 的分析功能仅通过日志和未修改的源文件完成,不依赖于实体变异体的代码。
而 major.log 则包含了在运行过程中所产生的所有调试信息:
suppression.log 是抑制等价变异体的信息记录,一般地排除生成的互斥突变:
注意:major 仅通过简单地检查是否冲突来排除等价变异体,但不能自动剔除大多数的等价变异体。
2)编译 MML 文件
类似地通过 java.exe 直接运行 major.jar 包,并通过 -mmlc 命令即可编译 (序列化) 指定路径下的 MML 配置文件为 BIN 二进制文件。
java -jar "./lib/major.jar" --mmlc "./mml/all.mml"
通过 -jar 选项执行 major.jar 文件,并传递 -mmlc 命令和 mml 文件路径来执行 MML2BIN 序列。
成功后将没有报错并在 mml 所在路径生成一个 .bin 格式的二进制序列化文件。
注意:
(1)必须通过编译后的 bin 传递配置给运行时的 mutator 插件;当不传递任何 bin 格式的配置时,程序将按照内置的默认处理模式实现处理。
(2)需要注意的是 Ant 集成中也可以使用 major 的变异生成器和分析器,但参数和配置有所不同。
2.2 插件配置选项
mutator 插件支持以下配置选项,通过命令行执行 -Xplugin 命令并附加这些可选参数:
- mml:<FILE>:运行已编译的 mml 文件中指定的突变配置
- mutants.log:<FILE>:mutants.log 文件的位置(默认位置:./mutants.log)
- export.mutants:如果设置,Major 将每个生成的突变体导出为源代码文件
- mutants.directory:<DIR>:突变体源代码文件的导出目录(默认位置:./mutants)
- export.context:如果设置,Major 将导出每个生成的突变体的上下文信息
- context.file:<FILE>:突变上下文文件的位置(默认位置:./mutants.context)
- strict.checks:如果设置,Major 会丢弃在源代码级别创建时不可编译的突变体。
- enable.decl.refactor:如果设置,Major 会尝试重构大型静态声明,认为这些声明会因包含突变而触发“代码太大”错误
- decl.refactor.params:MAX_TOTAL_ELEMS,MAX_INDIVIDUAL_ELEMS:用逗号分隔的重构参数列表,用于覆盖默认值
- MAX_TOTAL_ELEMS:任何给定方法中所有声明的变异常量的阈值总数(默认值为 1000)
- MAX_INDIVIDUAL_ELEMS:任何给定声明的变异常量的阈值数量(默认值为 50)
- enable.method.refactor:如果设置,Major 会尝试重构大型方法,因为这些方法可能会因包含突变而触发“代码过大”错误
- method.refactor.params:MUTANT_THRESHOLD,MUTANTS_PER_METHOD:用逗号分隔的重构参数列表,用于覆盖默认值
- MUTANT_THRESHOLD:触发重构的方法中突变体总数的阈值(默认值为 1000)
- MUTANTS_PER_METHOD:大型方法重构版本中允许的突变体数量(默认 250)
- logging:ARG1,ARG2,ARG3...:以分号分隔的参数列表
- file:FILE:指定保存的运行日志文件的文件名
- file-level:LEVEL:指定写入文件的最低日志记录级别
- console-level:LEVEL:指定记录到控制台的最低日志记录级别
- console-format:LEVEL:指定控制台日志的格式化程序
- file-format:LEVEL:指定文件记录的格式化程序
日志记录级别:all, finest, finer, fine, warning, severe, 以及 none
日志格式化程序:xml, color, verbose-color, oneline, verbose, simple
Mml 文件定义要启用哪些变异运算符以及要变异哪些程序元素。这允许对变异运算符进行细粒度定义和灵活应用。程序包的 major/mml 中提供了示例 mml 文件。请注意,Major 的变异器会解释预编译的 mml 文件。使用 mml 编译器 mmlc 来验证和编译 mml 文件。
2.3 Major 支持的变异算子
Major 支持以下变异算子组,每组包含多个相关的变异算子:
AOR:算术运算符替换
使用兼容的方法替换二进制算术运算符。
例子:
- a + b==>a - b
- a % b==>a * b
COR:条件运算符替换
使用兼容的方法替换条件运算符。major 还会将原子布尔条件替换为 true 和 false (例如,if(flag) 或 if(isSet() )。
例子:
- a || b==>a && b
- if(flag)==>if(true)
LOR:逻辑运算符替换
使用兼容的方法替换二元逻辑运算符。
例子:
- a ^ b==>a | b
ROR:关系运算符替换
使用兼容的方法替换关系运算符。
例子:
- a == b==>a >= b
SOR:移位运算符替换
使用兼容的方法替换位移位运算符。
例子:
- a >> b==>a << b
ORU:一元运算符替换
使用兼容的方法替换一元运算符;Major 目前支持基本一元运算符的替换(不支持增量运算符替换),但不支持一元运算符插入和删除(包括增量运算符插入和删除)。
例子:
- -a==>~a
- -a==>a
- ~a==>a
- ~a==>-a
- a==>~a
- a==>-a
LVR:字面量替换
用默认值替换字面量。
- 数字字面量被替换为正数、负数和零:
- val = 0 ==> 1 和 -1
- val < 0 ==> 0 和 -val
- val > 0 ==> 0 和 -val
- 指定范围的布尔值被其逻辑补码替换。
- 指定范围的字符串变量被替换为空字符串。
EVR:表达式值替换
用默认值替换表达式 (在 LVR 未改变的其他语句中)。
例子:
- return a==>return 0
- int a = b==>int a = 0
STD:语句删除
删除(省略)以下类型的单个语句:
- return
- break
- continue
- 方法调用 (Method call)
- 赋值 (Assignment)
- 前缀/后缀 自增运算符 (Pre/post increment)
- 前缀/后缀 自减运算符 (Pre/post decrement)
例子:
- return a
- break
- continue
- foo(a,b)
- a = b
- ++a
- a--
值得注意的是,Major 支持的变异算子基本上是运算符替换类变异算子,对比其他变异测试工具(如 mujava、PITest),它缺少很多运算符插入和运算符删除类变异算子。
注:
1)Major 变异算子中的一部分需要额外通过 mml 选项启用才能够生成。
2)后文中除特别指出外,"替换" 以及 "替换操作/模式" 一般指的是 major 所启用的突变操作算子(组)。原因是因为 major 目前基本只支持运算符的替换类型的突变操作,"替换" 是根据原文翻译所得,这里保留原义。
2.4 突变体的日志记录
Major 的突变器会生成一个日志文件 mutants.log,该文件提供有关生成的突变体的详细信息,并使用冒号 (':') 作为分隔符。日志文件为每个生成的突变体包含一行;每行包含 8 列,包含以下信息:
- 突变体的唯一编号(id)
- 所应用变异运算符的名称
- 原始运算符号
- 替换运算符号
- 变异方法的完全限定名称
- 原始源文件中的行号
- 替换在源文件中的开始字符位置
- 应用变换的总结 ( <from> |==> <to>)
下面是一个 ROR 突变的日志条目示例,该突变具有突变 ID 23,在 Triangle 类的一个名为 classify 的方法中产生。具体来说是在第 19 行(相对文本开头 350 字符处)生成的,将 a == c 变异为 a >= c,即符号 == 替换为 >= :
23:ROR:==(int,int):>=(int,int):triangle.Triangle@classify(int,int,int):19:350:a == c |==> a >= c
除此之外,Major 在运行过程中还可能会产生以下日志:
- major.log:程序执行过程中的操作信息日志;
- suppression.log:抑制等价变异体生成的信息日志;
- mmlc.log:编译 mml 过程中的异常和错误信息日志;
2.5 生成突变体的源代码
突变体可以导出为单独的源代码文件。默认情况下,此功能处于禁用状态;要启用此功能,请设置 export.mutants 选项。如果启用,Major 会复制每个突变体的原始源文件,将突变体注入副本,并导出生成的错误副本(./mutants 默认情况下)。使用 mutants.directory:<DIR> 选项控制输出目录。Major 会在必要时自动创建导出目录和父目录。
请注意,对大型代码库进行变异并将所有变异体导出到单独的源文件会增加变异/编译时间,并且需要比日志文件多得多的磁盘空间。
生成的变异体按照日志中的变异体编号 (ID) 生成到独立的文件夹中,如图所示:
3 构建系统集成
Major 的编译器插件可以单独使用或在构建系统中使用。
Gradle
待办事项
Apache Maven
待办事项
Apache Ant
考虑 Apache Ant 的 build.xml 文件中的以下 compile 目标:
<target name="compile" depends="init" description="Compile"> <javac srcdir="src" destdir="bin" debug="yes"></javac> </target>
添加以下选项以启用 Major 的 mutator 插件:
<property name="major.jar" value="<path to major.jar>"/> <property name="mml" value="<path to compiled mml file>"/> <target name="compile" depends="init" description="Compile"> <javac srcdir="src" destdir="bin" debug="yes"> <classpath location="${major.jar}"/> <compilerarg value="-Xplugin:MajorPlugin mml:${mml}"/> </javac> </target>
关于运行时设置
突变类文件引用 Major 的运行时配置类来访问突变标识符(_M_NO)和/或监视突变覆盖(COVERED)。默认实现的存档文件和源文件在 lib 目录中由 major-rt.jar 提供(或早期版本中由 config 目录提供)。可以对其进行扩展以执行其他分析。注意,在突变体生成期间,配置类不需要在类路径上可用(突变体不会尝试在编译时解析该类,而是根据该类定义的接口为突变体标识符和覆盖调用创建 AST 节点)。
Config 的示例代码内容如下:
// 这个包名是 Major! package major.mutation; import java.util.*; /** * 一个简单的驱动程序类 —— * 这个类名是 Major 所需要的! */ public class Config { /* * 变异体标识符: * * __M_NO < 0 -> 运行原始版本 * * __M_NO == 0 -> 运行原始版本并收集覆盖信息 * * __M_NO > 0 -> 执行具有相应 id 的突变体 */ public static int __M_NO = -1; // 设置为存储覆盖的突变体的 id public static BitSet covSet = new BitSet(); // 覆盖方法当且仅当 // 突变体标识符设置为 0 时! public static boolean COVERED(int from, int to) { synchronized (covSet) { covSet.set(from, to + 1); } // 总是按照条件突变的要求返回 false! return false; } /* * 用于变异分析 back-end 阶段的附加方法 */ // 重置覆盖信息 public static void reset() { synchronized (covSet) { covSet.clear(); } } // 获得所有被覆盖的变异体列表 public static List<Integer> getCoverageList() { synchronized (covSet) { List<Integer> covList = new ArrayList<>(covSet.cardinality()); for (int i = covSet.nextSetBit(0); i >= 0; i = covSet.nextSetBit(i+1)) { covList.add(i); } return covList; } } }
关于这里的 config 的使用方法以及如何集成到 Ant 中均可以参考:
https://blog.csdn.net/qq_52883908/article/details/127721055。
4 Major 突变语言 (MML)
Major 的领域特定语言 mml 支持突变生成过程的细粒度配置。mml 文件包含一系列语句,其中每个语句可以属于以下语句之一:
- 变量定义,例如作用域或替换;
- 突变操作符的替换定义;
- 定义 STD 突变操作符的语句类型;
- 定义 LVR 变异操作符的文字类型;
- 调用预定义的或自定义的突变操作符(组);
- 自定义突变操作符的定义;
- 单行注释;
前五种语句类型以分号 ( ; ) 结束,操作符组定义由花括号 ( { } ) 封装,单行注释 ( // ) 以行尾结束。
4.1 语句作用域
Mml 为替换操作的定义和操作符调用提供语句作用域,以支持对待测程序中某个包、类或方法的突变。下图显示了语句作用域的定义:
作用域对应于包、类或方法。作用域被定义为一个完全限定名——称为 flatname。flatname 既可以作为带引号的字符串提供,也可以作为变量提供(注:变异体日志中作为不加引号的字符串提供)。注意,语句作用域是可选的。如果没有提供语句作用域,则相应的定义或调用将默认应用于根包(全局)。
下图显示了 flatname 的语法规则:
假设 flatname 标识程序元素,则有效标识符(IDENT) 的命名约定遵循 Java 编程语言的命名约定(定义为 "包名.类名[@,或 ::]方法名或有其他效标识符",中间使用句号 "." 作为分隔符)。下面四个例子展示了包、类、一组重载方法和一个特定方法的有效 flatname:
- "java.lang"
- "java.lang.String"
- "java.lang.String@<init>"
- "java.lang.String@substring"
- "java.lang.String::substring(int,int)"
注意:major 的变异体日志中 flatname 字段默认采取 "@" 间隔类名和方法标识符,而不是使用 "::",尽管两种用法在 mml 中都是支持并且等效的。
flatname 语法还支持内部类和构造函数的标识,这与 Java 的命名约定一致。例如,定义中的后缀部分涉及对内部类、构造函数和静态类初始化项的标识:
- "foo.Bar$InnerClass"
- "foo.Bar@<init>"
- "foo.Bar@<clinit>"
4.2 覆盖和扩展定义
对于给定的范围(作用域),可以启用 ( + ,省略标志时默认为 +) 或者禁用 ( - ) 突变操作符。在下面的例子中,AOR 突变操作符通常对包 org 是启用的,但对包中的类 Foo 是禁用的:
+AOR<"org">;
-AOR<"org.Foo">;
注意,启用/禁用操作符的标志(前缀)是可选的。启用操作符的默认标志 ( + ) 提高了可读性,但可以省略。即 +AOR<"org">; 和 AOR <"org">; 是等价语句。
对于替换操作的定义,有两种可能性:可以将单个替换定义添加 ( + ) 到现有列表中(称为变异操作列表的扩展),或者可以覆盖整个替换列表 ( ! ,称为变异操作列表的重写)—— 如果忽略此可选标志,则默认为重写列表。在下面的示例中,包 org 的替换的一般定义被类 Foo 扩展,但被类 Bar 重写(在注释中给出了有效应用于包和类的替换列表)。
BIN(*)<"org"> -> {+,/}; // * -> {+,/}
+BIN(*)<"org.Foo"> -> {%}; // * -> {+,/,%}
!BIN(*)<"org.Bar"> -> {-}; // * -> {-}
4.3 自定义突变算子组
Mml 允许定义自定义操作符组以最小化代码重复(例如,可以对多个作用域使用相同的替换操作的定义)。自定义操作符组可以包含对 mml 文件有效的任何语句,但不支持调用另一个自定义操作符组(指:自定义操作符组的嵌套和任何外部调用是未定义的行为,不会解释为要合并组,并且会引起编译错误)。自定义操作符组具有唯一标识符,其语句用花括号括起来:
myOp {
BIN(*) -> {+,/};
AOR;
}
4.4 示例 MML 文件
下面的示例 mml 文件执行三个任务:
- 定义 AOR 和 ROR 的特定替换模式。
- 使用定义的替换模式调用 AOR 和 ROR。
- 不加限制地调用 LVR 操作符。
// 为 AOR 声明自定义替换列表
BIN(*) -> {/,%};
BIN(/) -> {*,%};
BIN(%) -> {*,/};
// 为 ROR 声明自定义替换列表
BIN(>) -> {<=,!=,==};
BIN(==) -> {<,!=,>};
// 定义应该由 LVR 操作符改变的字面值类型。
// 文字类型是 {BOOLEAN, NUMBER, STRING} 之一,分别是 布尔值,数值,字符(串)。
LIT(NUMBER);
LIT(BOOLEAN);
// 启用和调用上面定义的自定义突变操作符
AOR;
ROR;
LVR;
下一个示例在第 9 行和第 15-21 行使用了作用域特性,并在第 12 行定义了一个变量,以避免在随后的作用域声明中出现代码重复。如果只有某个包、类或方法需要改变,这两个特性都是有用的。
1. // 设置根节点的替换模式(全局)
2. BIN(>=)->{TRUE,>,==};
3. BIN(<=)->{TRUE,<,==};
4. BIN(!=)->{TRUE,<,> };
5. LIT(NUMBER);
6. LVR;
7.
8. // 对包 org 应用的特定的替换模式
9. ROR<"org">;
10.
11. // 对类 Foo 的变量的定义和赋值
12. foo="org.x.y.z.Foo";
13.
14. // 限制替换列表的作用域(作用范围),这里相当于是只针对 && 和 || 条件运算符实施指定的突变
15. BIN(&&)<foo>->{LHS,RHS,==,FALSE};
16. BIN(||)<foo>->{LHS,RHS,!=,TRUE };
17.
18. // 实施变异操作符的作用域,对不同作用域应用不同的操作符,或者开关某些操作符
19. -LVR<foo>;
20. ROR<foo>;
21. COR<foo>;
下一个示例演示了自定义操作符特性,该特性主要功能是同一组操作(定义或调用)可以应用于多个作用域,这是非常有用的。
myOp{
// 操作符组的定义
BIN(>=)->{TRUE,>,==};
BIN(<=)->{TRUE,<,==};
BIN(!=)->{TRUE,<,> };
BIN(&&)->{LHS,RHS,==,FALSE};
BIN(||)->{LHS,RHS,!=,TRUE };
// 在该组中启用指定的突变操作符
ROR;
COR;
}
// 对定义的操作符组的调用
myOp<"org">;
myOp<"de">;
myOp<"com">;
下一个示例来自对 example 目录下面的 Trangle 程序的配置,配置文件位于 mml/tutorial.mml,此示例演示为目标方法调用已定义的操作符组,如针对 triangle 包中的 Triangle 类的 classify 方法对进行调用已定义的替换模式。
targetOp{
// 为 AOR 声明自定义替换模式
BIN(>)->{>=,!=,FALSE};
BIN(<)->{<=,!=,FALSE};
BIN(>=)->{>,==,TRUE};
BIN(<=)->{<,==,TRUE};
BIN(==)->{<=,>=,FALSE,LHS,RHS};
BIN(!=)->{<,>,TRUE,LHS,RHS};
// 为 COR 声明自定义替换模式
BIN(&&)->{==,LHS,RHS,FALSE};
BIN(||)->{!=,LHS,RHS,TRUE};
// 定义STD应该删除的语句类型
DEL(RETURN);
// 启用自定义的 STD、COR 和 ROR 变异操作符
STD;
COR;
ROR;
}
// 为目标方法调用已定义的操作符组
targetOp<"triangle.Triangle::classify(int,int,int)">;
最后一个示例演示了启用所有变异类型的配置,此设置所产生的突变个数一般比默认模式要多,但包含的变异有所区别:
// 生成所有变异体的简单 mml 文件
// 定义一些操作符列表
list_aor={+,-,*,/,%};
list_lor={&,|,^};
list_sor={<<,>>,>>>};
list_oru={+,-,~};
// 使用足够的 AOR 替换操作列表
BIN(+)->list_aor;
BIN(-)->list_aor;
BIN(*)->list_aor;
BIN(/)->list_aor;
BIN(%)->list_aor;
// 使用足够的 SOR 替换操作列表
BIN(>>)->list_sor;
BIN(<<)->list_sor;
BIN(>>>)->list_sor;
// 使用足够的 LOR 替换操作列表
BIN(&)->list_lor;
BIN(|)->list_lor;
BIN(^)->list_lor;
// 使用足够的 ORU 替换操作列表
UNR(+)->list_oru;
UNR(-)->list_oru;
UNR(~)->list_oru;
// 使用足够的 ROR 替换操作列表
BIN(>)->{>=,!=,FALSE};
BIN(<)->{<=,!=,FALSE};
BIN(>=)->{>,==,TRUE};
BIN(<=)->{<,==,TRUE};
BIN(==)->{<=,>=,FALSE,LHS,RHS};
BIN(!=)->{<,>,TRUE,LHS,RHS};
// 使用足够的 COR 替换操作列表
BIN(&&)->{==,LHS,RHS,FALSE};
BIN(||)->{!=,LHS,RHS,TRUE};
// 定义应该由 LVR 操作符改变的字面值类型。
// 文字类型是 {BOOLEAN, NUMBER, STRING} 之一,分别是 布尔值,数值,字符(串)。
LIT(NUMBER);
LIT(BOOLEAN);
LIT(STRING);
// 尝试删除所有支持的语句类型
DEL(CALL);
DEL(INC);
DEL(DEC);
DEL(ASSIGN);
DEL(CONT);
DEL(BREAK);
DEL(RETURN);
// 启用所有操作符
AOR;
LOR;
SOR;
COR;
ROR;
LVR;
EVR;
ORU;
STD;
5 Major 的程序分析器
Major 提供了一个默认分析器,它扩展了 Apache Ant 的 junit 任务。该分析器支持 JUnit 3 和 4 测试。请注意,该分析器目前在执行 JUnit 测试时不支持对 JVM 进行分叉,这意味着必须将 fork 选项设置为 false。
5.1 程序分析器配置选项
该表总结了 Major 的默认分析器的配置选项。(请参考 Junit 任务的官方文档了解所有其他选项。)
选项 | 描述 | 可选数值 | 默认值 |
mutationAnalysis | 启用突变分析。如果设置为false,则忽略所有选项 | [true|false] | false |
analysisType | 运行预处理和突变分析,或只运行两者中的一个 | [preproc_mutation| preproc| mutation] | preproc_mutation |
serializedMapsFile | 存储序列化预处理结果时使用的文件名 | <String> | preprocessing.ser |
mutantsLogFile | 使用自定义变异算子处理生成的 mutants.log 日志文件的保存路径 | <String> | mutants.log |
testOrder | 测试变异体集的先后顺序和程度 | [original|random| sort_classes| sort_methods] | original |
debug | 打开调试输出开关 | [true|false] | false |
timeoutFactor | 在(原)测试运行时基础上的乘法因子,用于计算测试超时 | <int> | 8 |
timeoutOffset | 以毫秒为单位添加到计算的测试超时的偏移量 | <int> | 0 |
excludeFailingTests | 排除所有失败的测试(haltonfailure 必须为 false) | [true|false] | true |
excludeMutantsFile | 指定一个文本文件中所列出的全部突变 (每行设置 1 个突变 id,这些 id 对应的变异体将被过滤) | <String> | null |
includeMutantsFile | 仅包含此文本文件中列出的突变体(每行 1 个突变体 id) | <String> | null |
excludeTestsFile | 排除此文本文件中列出的所有测试 (每行 1 个测试 id )。 | <String> | null |
includeTestsFile | 仅包括此文本文件中列出的测试 (每行 1 个测试 id) | <String> | null |
summaryFile | 将结果摘要导出到此文件路径 (csv) | <String> | summary.csv |
executionDetailsFile | 将详细的执行信息导出到此文件路径 (csv)。 | <String> | null |
mutantDetailsFile | 将每个突变体的分类详细信息导出到此文件路径 (csv) | <String> | null |
covMapFile | 突变覆盖矩阵的文件名 (csv) | <String> | covMap.csv |
testMapFile | 测试 id 到测试名映射的文件名(csv) | <String> | testMap.csv |
exportKillMap | 导出突变查杀矩阵(对每个被覆盖的突变体执行每个测试!) | [true|false] | false |
killMapFile | 保存突变查杀矩阵的文件名 (csv) | <String> | killMap.csv |
备注:
- analysisType 参数:默认允许运行分析的两个步骤(预处理和突变),或者只运行两个步骤中的一个。对于应该并行化突变分析本身的大型代码库,只运行预处理一次并缓存结果(serializedMapsFile)将大大加快分析时间。如果设置为 mutation,则必须提供序列化映射。
- testOrder 参数:
- sort_methods:以测试方法细粒度报告结果。
- sort_classes:以测试类细粒度报告结果。
- original 和 random:报告结果的细粒度由 build.xml 文件定义。
5.2 设置分析目标
大多数 Apache Ant 构建文件提供了一个 test 目标,该目标执行相应的单元测试。即使不存在这样的目标,也可以很容易地将其设置为执行一组给定的单元测试。下面的代码片段显示了一个示例 test 目标(请参阅 junit.html 了解该任务的详细描述):
<target name="test" description="Run all unit test cases">
<junit printsummary="true"
showoutput="true"
haltonfailure="true">
<formatter type="plain" usefile="true"/>
<classpath path="bin"/>
<batchtest fork="no">
<fileset dir="test">
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>
要启用突变分析,请将 mutationAnalysis 选项设置为 true。为了清晰起见,最好复制并调整现有的 test 目标。
<target name="mutation.test" description="Run mutation analysis">
<junit printsummary="false"
showoutput="false"
haltonfailure="true"
mutationAnalysis="true"
summaryFile="summary.csv"
mutantDetailsFile="details.csv">
<classpath path="bin"/>
<batchtest fork="no">
<fileset dir="test">
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>
5.3 性能优化
变异分析重复地在变异的代码上执行测试。出于性能考虑,在设置突变分析目标时需要考虑以下几点:
- 关闭日志输出(选项 showsummary、showoutput 等);
- 不要使用结果格式化器(嵌套任务格式化器(formatter),尤其是 usefile 选项);
由于频繁的类加载和线程执行,建议使用以下JVM选项,特别是对于大型项目:
- -XX:ReservedCodeCacheSize=256M
- -XX:MaxPermSize=1G
6 兼容性问题和注意事项
Major 已知的兼容性问题并不多。典型的比如 Major 在处理一些代码时可能存在错误,比如 major 使用不够精确的浮点数处理方法,导致浮点数的第 16 个小数位会出现舍入精度损失的问题,使得生成的代码和原本的代码不同。
Major 会对源代码进行自动代码格式化,从日志就可以看出,但是不能够通过原生接口生成格式化后的源代码,这导致我们在代码格式化方面面临一些挑战。我通过一些技术实现了代码格式化并确保了兼容处理 Major 的舍入精度损失问题,但此工具系统受到保护原因,所以暂不可透露实现细节。
Major 支持的变异算子较少,一般只支持运算符替换和删除算子,不支持运算符插入算子。
Major 仅支持 JAVA 8 和语言级别不高于 8 的代码分析,且缺乏一些可视化和便捷操作的功能。
Major 支持解析带有依赖的源文件,但一次只能处理和分析单个类,不能分析整个项目。
所以理论上只能作为研究学习使用,而不能用于实际的软件测试。
本文出处链接:https://blog.csdn.net/qq_59075481/article/details/141371759。
本文发布于:2024.08.21;更新于:2024.08.21。