从 Run 到 Report:现有框架引入 Allure 的实践

调试阶段,我们通常直接在 IDEA 里 Run 测试,控制台输出的信息零散、易被刷屏,断言失败时只能看到一行堆栈,难以快速定位是哪个用例、哪一步、哪组数据出了问题;即使测试全部通过,也看不出用例的覆盖范围与执行趋势。相比之下,一份结构化、可视化的测试报告能直观呈现每个用例的执行结果、步骤明细、请求响应和失败截图,让问题一目了然,也便于团队共享与回溯。因此,在现有框架中引入 Allure 来生成 Report,把"跑完看控制台"升级为"跑完看报告"。
注:本项目需要下载allure到本地

一、相关文件清单与对应职责

文件 职责
pom.xml 依赖、surefire 配置(argLine + systemPropertyVariables)、clean 配置
testng.xml 注册 AllureReportListener
AllureReportListener.java suite 结束时调 allure.bat 生成时间戳报告
AbstractCaseTest.java Allure.getLifecycle().updateTestCase 改报告显示名
ResponseAsserter.java @Step 注解,靠 aspectjweaver 生效
target/allure-results/ 运行时产物,不是源码
report/<时间戳>/ 最终 HTML 报告,不是源码

二、具体代码和详细说明

1. Pom.xml - 配置层

位置 作用
<properties> 里 allure.results.directory 结果目录,target/allure-results
依赖 io.qameta.allure:allure-testng 提供 TestNG 集成,运行时自动生成结果文件
依赖 org.aspectj:aspectjweaver 关键,Allure 的 @Step、@Attachment 靠它做字节码织入,没它注解不生效
maven-clean-plugin clean 时删掉 target/allure-results
maven-surefire-plugin 指定 testng.xml、控制 fork、挂 aspectjweaver javaagent、传 allure.results.directory 系统属性
xml 复制代码
<properties>
    <aspectj.version>1.9.21</aspectj.version>
    <!-- allure 结果目录 -->
    <allure.results.directory>${project.build.directory}/allure-results</allure.results.directory>
</properties>

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>

    <dependency>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-testng</artifactId>
        <version>${allure.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- clean 时只清 allure 结果目录(report/<时间戳> 由 Listener 生成,不在这里清) -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-clean-plugin</artifactId>
            <version>${maven.clean.plugin.version}</version>
            <configuration>
                <filesets>
                    <fileset>
                        <directory>${allure.results.directory}</directory>
                    </fileset>
                </filesets>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${maven.surefire.plugin.version}</version>
            <configuration>
                <suiteXmlFiles>
                    <suiteXmlFile>testng.xml</suiteXmlFile>
                </suiteXmlFiles>
                <useFile>false</useFile>
                <reportFormat>plain</reportFormat>
                <forkCount>1</forkCount>
                <reuseForks>false</reuseForks>

                <!-- 关键:给 JVM 加 -Dfile.encoding=UTF-8,避免 ??? 乱码 -->
                <argLine>
                    -Dfile.encoding=UTF-8
                    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                </argLine>

                <systemPropertyVariables>
                    <allure.results.directory>${allure.results.directory}</allure.results.directory>
                </systemPropertyVariables>
            </configuration>
        </plugin>
    </plugins>
</build>

2. AbstractCaseTest - 通用执行流程中调用Allure的API将report中显示名设置成yaml中case属性的值

java 复制代码
public abstract class AbstractCaseTest extends ApiBase {

    /** 通用执行流程 */
    @SuppressWarnings("unchecked")
    protected void execute(Map<String, Object> caseData) {
        String caseName = (String) caseData.get("case");
        String method   = (String) caseData.get("method");

        // 把 YAML 里的 case 名设成报告里的显示名
        Allure.getLifecycle().updateTestCase(testResult ->
                testResult.setName(caseName)
        );
     }
 }

3. ResponseAsserter

  • @Step 是 Allure 注解,运行时由 AspectJ 织入拦截。
  • 方法被调用时,Allure 会在报告里生成一个"步骤"节点,名字里的 {path}、{expectedValue} 会被参数值替换。
  • 前提 :JVM 挂了 aspectjweaver javaagent(surefire 里那行),否则 @Step 不生效。
java 复制代码
@Step("校验 {path} 非空")
public static void assertNotNull(Response resp, String path) {
}

@Step("校验 {path} = {expectedValue}")
public static void assertEquals(Response resp, String path, Object expectedValue){
}

4. AllureReportListener - suite 结束时调 allure.bat 生成时间戳文件夹+ /index.html 报告

java 复制代码
package engine;

import org.testng.ISuite;
import org.testng.ISuiteListener;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class AllureReportListener implements ISuiteListener {

    // ⚠️ 改成你自己机器上的 allure.bat 绝对路径
    private static final String ALLURE_CMD =
            "D:\\Tools\\allure-2.29.0\\bin\\allure.bat";

    @Override
    public void onFinish(ISuite suite) {
        System.out.println("===== AllureReportListener.onFinish 触发了 =====");

        Path resultsDir = Path.of("target", "allure-results");
        System.out.println("resultsDir = " + resultsDir.toAbsolutePath());
        System.out.println("exists     = " + Files.exists(resultsDir));

        if (!Files.exists(resultsDir)) {
            System.out.println("[Allure] results 目录不存在,跳过报告生成");
            return;
        }

        String ts = LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        Path reportDir = Path.of("report", ts);

        try {
            ProcessBuilder pb = new ProcessBuilder(
                    ALLURE_CMD,
                    "generate", resultsDir.toString(),
                    "--single-file",
                    "--clean", "-o", reportDir.toString());
            pb.redirectErrorStream(true);
            pb.inheritIO();

            Process p = pb.start();
            int code = p.waitFor();

            if (code == 0) {
                System.out.println("[Allure] 报告已生成: " + reportDir.toAbsolutePath());
            } else {
                System.out.println("[Allure] 生成失败,exit code = " + code);
            }
        } catch (IOException | InterruptedException e) {
            System.out.println("[Allure] 调用 allure 命令失败:");
            e.printStackTrace();
        }
    }
}

5. testng.xml ------ 注册 Listener

xml 复制代码
<listeners>
    <listener class-name="engine.AllureReportListener"/>
</listeners>

6. 设置运行配置

新加一个Maven运行配置,运行命令写上clean verify,工作目录选择项目根目录,后续用这个配置跑。

三、生成的report如下

四、遇到的问题及解决办法

1. 报告里出现三个模块

问题:只跑 product,报告里却出现 admin、brand、order 三个模块。

原因 :target/allure-results 是累积目录,之前跑过的旧结果没清,Allure 读整个目录时把旧结果一起读进去了。

改动 :跑测试时加 clean,让 clean 阶段清掉 target,从而清掉 target/allure-results。

powershell

复制代码
mvn clean verify

并把 maven-clean-plugin 的 filesets 扩展,显式清理结果目录和报告目录:

xml

xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>${maven.clean.plugin.version}</version>
    <configuration>
        <filesets>
            <fileset>
                <directory>${allure.results.directory}</directory>
            </fileset>
            <fileset>
                <directory>${allure.report.directory}</directory>
            </fileset>
        </filesets>
    </configuration>
</plugin>

2. 报告中文乱码 ???

问题 :报告里中文显示成 ???。

原因:跑测试的 JVM 默认编码不是 UTF-8(Windows 中文系统常见 GBK)。

改动 :Surefire 的 argLine 加 -Dfile.encoding=UTF-8:

xml

bash 复制代码
<argLine>
    -Dfile.encoding=UTF-8
    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>

3. Corrupted channel by directly writing to native stream

现象 :构建输出里出现这个警告,附带 dumpstream 文件。

原因 :allure-maven 插件打印「Report successfully generated to ...」时,直接写原生 stdout,绕过了 Surefire 的日志通道,Surefire 记录警告。

结论 :这是警告,不是错误,报告已成功生成。可以忽略。

4. allure添加到了系统变量后IDEA终端找不到

现象 :下载了allure并且添加到了系统变量Path中,在IDEA中打开终端却找不到allure。

原因 :IDEA 的终端用的是 IDEA 启动时复制的环境变量快照,不会实时读后来改的系统 PATH。

结论 :改了项目相关的环境变量,要重启IDEA。(把所有IDEA页面关闭再重新打开,不能只关你要跑的项目窗口)

相关推荐
一帅1 小时前
Muzzle:给 Java Agent 戴上的"安全口罩"
后端
Bazingga1 小时前
从0到1吃透Function Calling:Spring AI完整实战
后端
写了20年代码的老程序员1 小时前
想让 AI 改 Bug 快准狠?先给日志加个业务代码坐标
java·后端·apache log4j
合橱瑰1 小时前
踩坑实录:子进程“假 Ready”导致窗口永远无法唤起?
后端·全栈
咖啡八杯1 小时前
常量与枚举设计规范:HttpStatus 自定义 601 警告码
java·架构·代码规范
imDwAaY1 小时前
如何快速定位线上OOM
后端
一帅1 小时前
大象无形:OTel Java Agent 的隐身哲学
后端
dd聊技术1 小时前
给项目接上动态线程池
后端
hsfxuebao1 小时前
常用开源项目github
后端·github