浅解 JUnit 4 第九篇:JUnitCore (下)

背景

我在 浅解 JUnit 4 这个专栏陆续添加了一些文章。现有的文章初步探讨了 Test/Runner/RunnerBuilder 这些核心类的逻辑。但是 JUnit 4 的入口在哪里呢?本文会继续对这个问题进行探讨(由于 JUnitCore 这个类涉及的内容较多,一篇文章写不完,本文是 下篇 ,上篇的链接是 浅解 JUnit 4 第八篇:JUnitCore (上))。

要点

正文

我创建了一个小项目来讨论本文的问题

项目结构

这个项目的结构和 浅解 JUnit 4 第七篇:AllDefaultPossibilitiesBuilder 一文中的 项目结构 那一小节所描述的相同。为了避免在两篇文章之间跳来跳去,我还是把项目结构在本文中复述一遍 ⬇️

这个项目中包含以下目录/文件

  • src/main/java/org/example 目录下有以下文件
    • SimpleAdder.java
  • src/test/java/org/study 目录下有以下文件
    • SimpleAdderTest.java
  • pom.xml (在项目顶层)

其中 SimpleAdder.java 的内容如下 ⬇️

java 复制代码
package org.example;

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

SimpleAdderTest.java 的内容如下 ⬇️

java 复制代码
package org.study;

import org.example.SimpleAdder;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.JUnitCore;

public class SimpleAdderTest {
    private final SimpleAdder simpleAdder = new SimpleAdder();

    @Test
    public void testAdd() {
        Assert.assertEquals(2, simpleAdder.add(1, 1));
    }

    public static void main(String[] args) {
        JUnitCore.runClasses(SimpleAdderTest.class);
    }
}

pom.xml 的内容如下 ⬇️

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>junit-study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

JUnitCore 里的 runClasses(Class<?>...) 方法

根据 JUnitCorejavadoc 的描述(如下图所示 ⬇️),我们可以调用 runClasses(Class<?>...) 方法

这里涉及如下 6 个方法,我们逐个来看

  1. runClasses(Class<?>... classes)
  2. runClasses(Computer computer, Class<?>... classes)
  3. run(Computer computer, Class<?>... classes)
  4. run(Request request)
  5. run(Runner runner)
  6. defaultComputer()

1 个方法: runClasses(Class<?>... classes)

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
/**
 * Run the tests contained in <code>classes</code>. Write feedback while the tests
 * are running and write stack traces for all failed tests after all tests complete. This is
 * similar to {@link #main(String[])}, but intended to be used programmatically.
 *
 * @param classes Classes in which to find tests
 * @return a {@link Result} describing the details of the test run and the failed tests.
 */
public static Result runClasses(Class<?>... classes) {
    return runClasses(defaultComputer(), classes);
}

runClasses(Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️

2 个方法: runClasses(Computer computer, Class<?>... classes)

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
/**
 * Run the tests contained in <code>classes</code>. Write feedback while the tests
 * are running and write stack traces for all failed tests after all tests complete. This is
 * similar to {@link #main(String[])}, but intended to be used programmatically.
 *
 * @param computer Helps construct Runners from classes
 * @param classes  Classes in which to find tests
 * @return a {@link Result} describing the details of the test run and the failed tests.
 */
public static Result runClasses(Computer computer, Class<?>... classes) {
    return new JUnitCore().run(computer, classes);
}

runClasses(Computer computer, Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️

3 个方法: run(Computer computer, Class<?>... classes)

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
/**
 * Run all the tests in <code>classes</code>.
 *
 * @param computer Helps construct Runners from classes
 * @param classes the classes containing tests
 * @return a {@link Result} describing the details of the test run and the failed tests.
 */
public Result run(Computer computer, Class<?>... classes) {
    return run(Request.classes(computer, classes));
}

run(Computer computer, Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️

我们在 Request 类的 classes(Computer computer, Class<?>... classes) 方法里打一个断点,断点的位置如下图所示 ⬇️

然后 debug SimpleAdderTest 里的 main 方法

可以观察到

  • builder 变量是 AllDefaultPossibilitiesBuilder 的一个实例
  • classes 变量是一个数组(其中只有一个元素: org.study.SimpleAdderTest.class)

我们 Step Into Computer 类中的 getSuite(final RunnerBuilder builder, Class<?>[] classes) 方法

Computer 类中的 getSuite(final RunnerBuilder builder, Class<?>[] classes) 方法,会创建 Suite 类的某个匿名子类的实例,因此也会调用 Suite 类中的某个构造函数,我们 Step Into 这个构造函数

Suite 类的这个构造函数中,可以观察到

  • builder 变量是 RunnerBuilder 的一个匿名子类的实例(它可以访问一个 AllDefaultPossibilitiesBuilder 的实例)
  • classes 变量是一个数组(其中只有一个元素: org.study.SimpleAdderTest.class)

基于以上观察,我们可以概括出 Request 类的 classes(Computer computer, Class<?>... classes) 方法所做的事情 ⬇️

  1. 创建一个 AllDefaultPossibilitiesBuilder 类的实例 builder \text{builder} builder
  2. 调用 Computer 类中的 getSuite(final RunnerBuilder builder, Class<?>[] classes) 方法
    • 该方法会返回 Suite 类的某个匿名子类的实例 suite \text{suite} suite
    • suite \text{suite} suite 可以(间接地)通过 builder \text{builder} builder 来为测试类构建对应的 Runner
    • 对测试类 T 1 , T 2 , ⋯ \text{T}_1,\text{T}2,\cdots T1,T2,⋯ 而言, builder \text{builder} builder 可以构建对应的 Runner: Runner T 1 , Runner T 2 , ⋯ \text{Runner}{\text{T}1},\text{Runner}{\text{T}_2},\cdots RunnerT1,RunnerT2,⋯
    • Runner T 1 , Runner T 2 , ⋯ \text{Runner}_{\text{T}1},\text{Runner}{\text{T}_2},\cdots RunnerT1,RunnerT2,⋯ 都是 suite \text{suite} suite 的子节点
  3. suite \text{suite} suite 包装成 Request 对象

4 个方法: run(Request request)

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
/**
 * Run all the tests contained in <code>request</code>.
 *
 * @param request the request describing tests
 * @return a {@link Result} describing the details of the test run and the failed tests.
 */
public Result run(Request request) {
    return run(request.getRunner());
}

run(Request request) 方法所做的事情如下方的思维导图所示 ⬇️

这个方法的 request 参数是通过 Request 类中 runner(final Runner runner) 方法构造的,所以我们可以简单将 Request 视为 Runner 的包装 ⬇️

5 个方法: run(Runner runner)

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
/**
 * Do not use. Testing purposes only.
 */
public Result run(Runner runner) {
    Result result = new Result();
    RunListener listener = result.createListener();
    notifier.addFirstListener(listener);
    try {
        notifier.fireTestRunStarted(runner.getDescription());
        runner.run(notifier);
        notifier.fireTestRunFinished(result);
    } finally {
        removeListener(listener);
    }
    return result;
}

这个方法负责运行 Runner,并通知相关的 RunListener。其中涉及的细节比较多,我们在后续的文章中再展开说。本文不讲这些细节。

6 个方法: defaultComputer()

我把这个方法的代码复制到下方了 ⬇️

java 复制代码
static Computer defaultComputer() {
    return new Computer();
}

defaultComputer() 这个方法会返回 Computer 的一个实例。

其他

画"JUnitCore 类的一个入口: runClasses(Class<?>... classes) 方法"一图所用到的代码

我用 PlantUML 画了那张图,所用到的代码如下 ⬇️

puml 复制代码
@startuml
'https://plantuml.com/class-diagram

title <i>JUnitCore</i> 类的一个入口: <i>runClasses(Class<?>... classes)</i> 方法

class org.junit.runner.JUnitCore {
    +{static} Result runClasses(Class<?>... classes)
    +{static} Result runClasses(Computer computer, Class<?>... classes)
    +Result run(Computer computer, Class<?>... classes)
    +Result run(Request request)
    +Result run(Runner runner)
    ~{static} Computer defaultComputer()
}

note right of org.junit.runner.JUnitCore::"runClasses(Class<?>... classes)"
   <i>return runClasses(defaultComputer(), classes);</i>
end note

note right of org.junit.runner.JUnitCore::"runClasses(Computer computer, Class<?>... classes)"
    <i>return new JUnitCore().run(computer, classes);</i>
end note

note left of org.junit.runner.JUnitCore::"run(Computer computer, Class<?>... classes)"
    <i>return run(Request.classes(computer, classes));</i>
end note

note left of org.junit.runner.JUnitCore::"run(Request request)"
    <i>return run(request.getRunner());</i>
end note

note right of org.junit.runner.JUnitCore::"run(Runner runner)"
    Result result = new Result();
    RunListener listener = result.createListener();
    notifier.addFirstListener(listener);
    try {
        notifier.fireTestRunStarted(runner.getDescription());
        runner.run(notifier);
        notifier.fireTestRunFinished(result);
    } finally {
        removeListener(listener);
    }
    return result;
end note

note left of org.junit.runner.JUnitCore::defaultComputer()
    <i>return new Computer();</i>
end note

@enduml

画"runClasses(Class<?>... classes) 方法做了什么"一图所用到的代码

我用 PlantUML 画了那张图,所用到的代码如下 ⬇️

puml 复制代码
@startmindmap
'https://plantuml.com/mindmap-diagram

title <i>runClasses(Class<?>... classes)</i> 方法做了什么

* <i>runClasses(Class<?>... classes)</i> 方法
**[#Orange] 第 <i>1</i> 步: 调用 <i>defaultComputer()</i> 方法
***:此方法只有一行代码 ⬇️
<i>return new Computer();</i>;
**[#Orange] 第 <i>2</i> 步: 调用 <i>runClasses(Computer computer, Class<?>... classes)</i> 方法

caption 橙色节点展示了 <i>runClasses(Class<?>... classes)</i> 方法 所做的事情
@endmindmap

画"runClasses(Computer computer, Class<?>... classes) 方法做了什么" 一图所用到的代码

我用 PlantUML 画了那张图,所用到的代码如下 ⬇️

puml 复制代码
@startmindmap
'https://plantuml.com/mindmap-diagram

top to bottom direction

title <i>runClasses(Computer computer, Class<?>... classes)</i> 方法做了什么

* <i>runClasses(Computer computer, Class<?>... classes)</i> 方法
**[#Orange] 第 <i>1</i> 步: 创建 <i>JUnitCore</i> 的实例
**[#Orange]:第 <i>2</i> 步: 通过 <i>JUnitCore</i> 的实例,
调用 <i>run(Computer computer, Class<?>... classes)</i> 方法;

@endmindmap

画"run(Computer computer, Class<?>... classes) 方法做了什么"一图所用到的代码

我用 PlantUML 画了那张图,所用到的代码如下 ⬇️

puml 复制代码
@startmindmap
'https://plantuml.com/mindmap-diagram

top to bottom direction

title <i>run(Computer computer, Class<?>... classes)</i> 方法做了什么

* <i>run(Computer computer, Class<?>... classes)</i> 方法
**[#Orange]:第 <i>1</i> 步: 调用 <i>Request</i> 类里的
<i>classes(Computer, Class<?>...)</i> 方法;
**[#Orange] 第 <i>2</i> 步: 调用 <i>run(Request request)</i> 方法

@endmindmap

画 "run(Request request) 方法做了什么" 一图所用到的代码

我用 PlantUML 画了那张图,所用到的代码如下 ⬇️

puml 复制代码
@startmindmap
'https://plantuml.com/mindmap-diagram

top to bottom direction

title <i>run(Request request)</i> 方法做了什么

* <i>run(Request request)</i> 方法
**[#Orange]:第 <i>1</i> 步: 调用 <i>Request</i> 类里的
<i>getRunner()</i> 方法;
**[#Orange] 第 <i>2</i> 步: 调用 <i>run(Runner runner)</i> 方法

@endmindmap

参考资料

相关推荐
HLAIA光子5 小时前
AI Coding框架,打好TDD和SDD这两拳
单元测试·ai编程·代码规范
IT策士6 小时前
Redis 从入门到精通:事务与 Lua 脚本
redis·junit·lua
北极星日淘6 小时前
日淘平台优惠券系统的设计:从规则引擎到防超领
junit
慧都小妮子6 小时前
不想频繁改 PLC?用 DeviceXPlorer Lua 脚本把产线业务逻辑放到 OPC Server 层
java·junit·lua·takebishi·dxpserver·设备数据采集软件·opc server
霸道流氓气质12 小时前
Java 单元测试生成大量 Excel 测试数据实战指南
java·单元测试·excel
川石课堂软件测试12 小时前
UI自动化测试|下拉选择框&弹出框&滚动条操作实践
开发语言·python·jmeter·ui·docker·单元测试·harmonyos
川石课堂软件测试1 天前
UI自动化测试|元素操作&浏览器操作实践
功能测试·测试工具·mysql·ui·docker·容器·单元测试
无聊的老谢1 天前
电信系统中的单元测试策略:构建高可靠性的微服务防线
数据库·微服务·单元测试
wh_xia_jun1 天前
单元测试 + Mockito 开发指南
oracle·单元测试·log4j
闪电悠米2 天前
黑马点评-Redis 消息队列-03_stream_consumer_group
开发语言·数据库·redis·分布式·缓存·junit·lua