16、测试与报告:单元测试(JUnit5/TestNG)、集成测试配置、测试报告生成(HTML/XML)

说实话,很多开发兄弟对测试的态度就是「能跑就行」。我以前也这么想,直到有一次线上出了个低级bug,查了半天发现是单元测试覆盖率太低。嗯,从那以后我再也不敢轻视测试了。

这一章,咱们就聊聊Gradle里的测试那些事。说白了,就是怎么让你的代码在提交之前就自己把自己检查一遍。

16.1 单元测试:JUnit5 与 TestNG

Gradle对测试框架的支持非常友好。你只需要在依赖里声明一下,剩下的交给它就好。

16.1.1 配置 JUnit5

我个人习惯用JUnit5,因为它比JUnit4更灵活,而且支持Lambda表达式。配置起来也很简单:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

test {
    useJUnitPlatform()
}

你看,就两行。testImplementation表示这个依赖只在测试时使用,不会打包到生产环境。useJUnitPlatform()是告诉Gradle用JUnit5的引擎来跑测试。

写个简单的测试类:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void testDivideByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
    }
}

这里有个小技巧:@DisplayName注解可以给测试起个中文名字,方便看报告时一眼就知道测的是什么。

16.1.2 配置 TestNG

如果你团队里有人喜欢TestNG,也没问题。Gradle同样支持:

dependencies {
    testImplementation 'org.testng:testng:7.8.0'
}

test {
    useTestNG()
}

TestNG有个好处是支持@DataProvider,可以很方便地做参数化测试。我在项目中遇到过需要测试大量边界值的情况,用DataProvider简直不要太爽。

核心区别: JUnit5更轻量,社区活跃;TestNG功能更全,适合复杂测试场景。选哪个?看团队习惯就好。

16.2 集成测试配置

单元测试只能测单个类,但真实场景里类之间是要协作的。这时候就需要集成测试了。

Gradle里怎么做?我建议用SourceSet来隔离集成测试和单元测试:

sourceSets {
    integrationTest {
        java {
            srcDir 'src/integrationTest/java'
        }
        resources {
            srcDir 'src/integrationTest/resources'
        }
        compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath
        runtimeClasspath += output + compileClasspath
    }
}

task integrationTest(type: Test) {
    description = '运行集成测试'
    group = 'verification'
    testClassesDirs = sourceSets.integrationTest.output.classesDirs
    classpath = sourceSets.integrationTest.runtimeClasspath
    shouldRunAfter test
}

check.dependsOn integrationTest

你想想看,这样配置之后,./gradlew check就会先跑单元测试,再跑集成测试。如果单元测试挂了,集成测试根本不会执行——省时间。

我的习惯: 集成测试里尽量用真实的数据库或外部服务,但不要用生产环境。可以用Testcontainers库启动临时容器,用完自动销毁。

16.3 测试报告生成

测试跑完了,怎么给别人看结果?Gradle默认会生成HTML报告,但很多人不知道它还能生成XML格式,方便CI系统解析。

16.3.1 HTML报告

默认情况下,Gradle会在build/reports/tests/test/index.html生成一份漂亮的HTML报告。里面包含了:

  • 测试总数、通过数、失败数
  • 每个测试类的详细结果
  • 失败测试的堆栈信息
  • 测试耗时统计

你什么都不用配置,直接./gradlew test,然后打开那个HTML文件就能看到。我经常在本地开发时用这个来快速定位失败的测试。

16.3.2 XML报告

CI系统(比如Jenkins、GitLab CI)更喜欢XML格式。Gradle默认也会生成XML报告,位置在build/test-results/test/*.xml

如果你想自定义报告格式,可以这样配置:

test {
    reports {
        html.required = true
        xml.required = true
    }
}

说白了,HTML给人看,XML给机器看。两个都开着,没坏处。

注意: 如果测试数量很大(比如上千个),HTML报告生成可能会比较慢。这时候可以考虑只生成XML,然后用CI工具来展示结果。

16.4 知识体系图

下面这张图帮你理清测试与报告的整体结构:

Gradle 测试与报告体系 单元测试 集成测试 报告生成 JUnit5 TestNG SourceSet 隔离 Testcontainers HTML 报告 XML 报告 核心流程 编写测试 → 配置Gradle → 执行测试 → 生成报告 单元测试先行,集成测试随后,报告自动生成

16.5 实战小贴士

最后分享几个我在项目中踩过的坑:

  • 测试命名规范: 我习惯用*Test.java命名单元测试,*IT.java命名集成测试。这样Gradle可以自动识别。
  • 并行执行: 如果测试之间没有依赖,可以设置maxParallelForks = 4来加速。但要注意数据库测试可能会冲突。
  • 失败重试: 有些测试不稳定(比如网络相关的),可以用retry插件自动重试。但别滥用,不稳定测试本身就该修。
避坑指南: 我曾经在集成测试里忘了关闭数据库连接,导致测试跑完后连接池爆满。后来加了个@AfterAll方法统一清理资源,问题就解决了。

好了,这一章的内容就这些。测试这东西,投入越多,回报越大。别等到线上出bug了才后悔没写测试。

公众号:蓝海资料掘金营,微信deep3321