通过JUnit源码分析学习编程的奇技淫巧

打印 上一主题 下一主题

主题 893|帖子 893|积分 2679

打开 Maven仓库,左边选项栏排在第一的就是测试框架与工具,今天的文章,V 哥要来聊一聊步伐员必备的测试框架JUnit 的源码实现,整理的学习笔记,分享给各人。

有人说,不就一个测试框架嘛,有必要去了解它的源码吗?确实,在平时的工作中,我们只要把握如何利用 JUnit 框架来帮我们测试代码即可,搞什么源码,信赖我,只有看了 JUnit 框架的源码,你才会赞叹,真是不愧是一款优秀的框架,它的源码设计思路与技巧,真的值得你好好研读一下,学习优秀框架的实现头脑,不就是优秀步伐员要干的事变吗。
JUnit 是一个广泛利用的 Java 单位测试框架,其源码实现分析可以资助开发者更好地理解其工作原理和内部机制,并学习优秀的编码头脑。
JUnit 框架的源码实现过程中体现了多种优秀的设计头脑和编程技巧,这些不但使得 JUnit 成为一个强大且灵活的测试框架,也值得步伐员在日常开发中学习和借鉴。V 哥通过研读源码后,总结了以下是一些关键点:

  • 面向对象设计:JUnit 充分运用了面向对象的封装、继承和多态特性。例如,TestCase 类作为基类提供了共享的测试方法和断言工具,而具体的测试类继承自 TestCase 来实现具体的测试逻辑。
  • 模板方法模式:JUnit 的 TestCase 类利用了模板方法设计模式,定义了一系列模板方法如 setUp()、runTest() 和 tearDown(),允许子类重写这些方法来插入特定的测试逻辑。
  • 制作者模式:JUnit 在构造测试套件时利用了制作者模式,允许渐渐构建复杂的测试结构。例如,JUnitCore 类提供了方法来渐渐添加测试类和监听器。
  • 计谋模式:JUnit 允许通过不同的 Runner 类来改变测试实行的计谋,如 BlockJUnit4ClassRunner 和 Suite。这种设计使得 JUnit 可以灵活地顺应不同的测试需求。
  • 装饰者模式:在处置处罚测试前置和后置操作时,JUnit 利用了装饰者模式。例如,@RunWith 注解允许开发者指定一个 Runner 来装饰测试类,从而添加额外的测试行为。
  • 观察者模式:JUnit 的测试结果监听器利用了观察者模式。多个监听器可以订阅测试变乱,如测试开始、测试失败等,从而实现对测试过程的监控和结果的收集。
  • 依赖注入:JUnit 支持利用注解如 @Mock 和 @InjectMocks 来举行依赖注入,这有助于解耦测试代码,提高测试的可读性和可维护性。
  • 反射机制:JUnit 广泛利用 Java 反射 API 来动态发现和实行测试方法,这提供了极大的灵活性,允许在运行时动态地构建和实行测试。
  • 非常处置处罚:JUnit 在实行测试时,对非常举行了精细的处置处罚。它可以或许区分测试中预期的非常和意外的非常,从而提供更准确的测试结果反馈。
  • 解耦合:JUnit 的设计注重组件之间的解耦,例如,测试实行器(Runner)、测试监听器(RunListener)和测试结果(Result)之间的职责清晰分离。
  • 可扩展性:JUnit 提供了丰富的扩展点,如自定义的 Runner、TestRule 和 Assertion 方法,允许开发者根据需要扩展框架的功能。
  • 参数化测试:JUnit 支持参数化测试,允许开发者为单个测试方法提供多种输入参数,这有助于用一个测试方法覆盖多种测试场景。
  • 代码的模块化:JUnit 的源码结构清晰,模块化的设计使得各个部分之间的依赖关系最小化,便于理解和维护。
通过学习和理解 JUnit 框架的这些设计头脑和技巧,步伐员可以在自己的项目中实现更高质量的代码和更有效的测试计谋。
1. 面向对象设计

JUnit 框架的 TestCase 是一个核心类,它体现了面向对象设计的多个方面。以下是 TestCase 实现过程中的一些关键点,以及源码示例和分析:

  • 封装:TestCase 类封装了测试用例的全部逻辑和相关数据。它提供了公共的方法来实行测试前的预备 (setUp) 和测试后的清理 (tearDown),以及其他测试逻辑。
  1. public class TestCase extends Assert implements Test {
  2.     // 测试前的准备
  3.     protected void setUp() throws Exception {
  4.     }
  5.     // 测试后的清理
  6.     protected void tearDown() throws Exception {
  7.     }
  8.     // 运行单个测试方法
  9.     public void runBare() throws Throwable {
  10.         // 调用测试方法
  11.         method.invoke(this);
  12.     }
  13. }
复制代码

  • 继承:TestCase 允许其他测试类继承它。子类可以重写 setUp 和 tearDown 方法来实行特定的初始化和清理任务。这种继承关系使得测试逻辑可以复用,并且可以构建出条理化的测试结构。
  1. public class MyTest extends TestCase {
  2.     @Override
  3.     protected void setUp() throws Exception {
  4.         // 子类特有的初始化逻辑
  5.     }
  6.     @Override
  7.     protected void tearDown() throws Exception {
  8.         // 子类特有的清理逻辑
  9.     }
  10.     // 具体的测试方法
  11.     public void testSomething() {
  12.         // 使用断言来验证结果
  13.         assertTrue("预期为真", someCondition());
  14.     }
  15. }
复制代码

  • 多态:TestCase 类中的断言方法 (assertEquals, assertTrue 等) 允许以不同的方式利用,这是多态性的体现。开发者可以针对不同的测试场景利用雷同的断言方法,但传入不同的参数和消息。
  1. public class Assert {
  2.     public static void assertEquals(String message, int expected, int actual) {
  3.         // 实现断言逻辑
  4.     }
  5.     public static void assertTrue(String message, boolean condition) {
  6.         // 实现断言逻辑
  7.     }
  8. }
复制代码

  • 抽象类:虽然 TestCase 不是一个抽象类,但它定义了一些抽象概念,如测试方法 (runBare),这个方法可以在子类中以不同的方式实现。这种抽象允许 TestCase 类顺应不同的测试场景。
  1. public class TestCase {
  2.     // 抽象的测试方法执行逻辑
  3.     protected void runBare() throws Throwable {
  4.         // 默认实现可能包括异常处理和断言调用
  5.     }
  6. }
复制代码

  • 接口实现:TestCase 实现了 Test 接口,这表明它具有测试用例的根本特征和行为。通过实现接口,TestCase 保证了全部测试类都遵照雷同的规范。
  1. public interface Test {
  2.     void run(TestResult result);
  3. }
  4. public class TestCase extends Assert implements Test {
  5.     // 实现 Test 接口的 run 方法
  6.     public void run(TestResult result) {
  7.         // 运行测试逻辑
  8.     }
  9. }
复制代码
我们可以看到 TestCase 类的设计充分利用了面向对象编程的优势,提供了一种灵活且强大的方式来组织和实行单位测试。这种设计不但使得测试代码易于编写和维护,而且也易于扩展和顺应不同的测试需求,你get 到了吗。
2. 模板方法模式

模板方法模式是一种行为设计模式,它在父类中定义了算法的框架,同时允许子类在不改变算法结构的环境下重新定义算法的某些步调。在 JUnit 中,TestCase 类就是利用模板方法模式的典型例子。
以下是 TestCase 类利用模板方法模式的实现过程和源码分析:

  • 定义算法框架:TestCase 类定义了测试方法实行的算法框架。这个框架包括测试前的预备 (setUp)、调用实际的测试方法 (runBare) 以及测试后的清理 (tearDown)。
  1. public abstract class TestCase implements Test {
  2.     // 模板方法,定义了测试执行的框架
  3.     public void run(TestResult result) {
  4.         // 测试前的准备
  5.         setUp();
  6.         try {
  7.             // 调用实际的测试方法
  8.             runBare();
  9.         } catch (Throwable e) {
  10.             // 异常处理,可以被子类覆盖
  11.             result.addError(this, e);
  12.         } finally {
  13.             // 清理资源,确保在任何情况下都执行
  14.             tearDown();
  15.         }
  16.     }
  17.     // 测试前的准备,可以被子类覆盖
  18.     protected void setUp() throws Exception {
  19.     }
  20.     // 测试方法的执行,可以被子类覆盖
  21.     protected void runBare() throws Throwable {
  22.         for (int i = 0; i < fCount; i++) {
  23.             runTest();
  24.         }
  25.     }
  26.     // 测试后的清理,可以被子类覆盖
  27.     protected void tearDown() throws Exception {
  28.     }
  29.     // 执行单个测试方法,通常由 runBare 调用
  30.     public void runTest() throws Throwable {
  31.         // 实际的测试逻辑
  32.     }
  33. }
复制代码

  • 允许子类扩展:TestCase 类中的 setUp、runBare 和 tearDown 方法都是 protected,这意味着子类可以覆盖这些方法来插入自己的逻辑。
  1. public class MyTestCase extends TestCase {
  2.     @Override
  3.     protected void setUp() throws Exception {
  4.         // 子类的初始化逻辑
  5.     }
  6.     @Override
  7.     protected void runBare() throws Throwable {
  8.         // 子类可以自定义测试执行逻辑
  9.         super.runBare();
  10.     }
  11.     @Override
  12.     protected void tearDown() throws Exception {
  13.         // 子类的清理逻辑
  14.     }
  15.     // 实际的测试方法
  16.     public void testMyMethod() {
  17.         // 使用断言来验证结果
  18.         assertTrue("测试条件", condition);
  19.     }
  20. }
复制代码

  • 实行测试方法:runTest 方法是实际实行测试的地方,通常在 runBare 方法中被调用。TestCase 类维护了一个测试方法数组 fTests,runTest 方法会遍历这个数组并实行每个测试方法。
  1. public class TestCase {
  2.     // 测试方法数组
  3.     protected final Vector tests = new Vector();
  4.     // 添加测试方法到数组
  5.     public TestCase(String name) {
  6.         tests.addElement(name);
  7.     }
  8.     // 执行单个测试方法
  9.     public void runTest() throws Throwable {
  10.         // 获取测试方法
  11.         Method runMethod = null;
  12.         try {
  13.             runMethod = this.getClass().getMethod((String) tests.elementAt(testNumber), (Class[]) null);
  14.         } catch (NoSuchMethodException e) {
  15.             fail("Missing test method: " + tests.elementAt(testNumber));
  16.         }
  17.         // 调用测试方法
  18.         runMethod.invoke(this, (Object[]) null);
  19.     }
  20. }
复制代码
通过模板方法模式,TestCase 类为全部测试用例提供了一个统一的实行模板,确保了测试的划一性和可维护性。同时,它也允许开发者通过覆盖特定的方法来定制测试的特定步调,提供了灵活性。这种设计模式在 JUnit 中的成功应用,展示了它在构建大型测试框架中的价值。
3. 制作者模式

在JUnit中,制作者模式主要体如今JUnitCore类的利用上,它允许以一种渐渐构建的方式运行测试。JUnitCore类提供了一系列的静态方法,允许开发者渐渐添加测试类和配置选项,最终构建成一个完整的测试运行实例。以下是JUnitCore利用制作者模式的实现过程和源码分析:

  • 构建测试运行器:JUnitCore类提供了一个运行测试的入口点。通过main方法或run方法,可以启动测试。
  1. public class JUnitCore {
  2.     // 运行测试的main方法
  3.     public static void main(String[] args) {
  4.         runMain(new JUnitCore(), args);
  5.     }
  6.     // 运行测试的方法,可以添加测试类和监听器
  7.     public Result run(Class<?>... classes) {
  8.         return run(Request.classes(Arrays.asList(classes)));
  9.     }
  10.     // 接受请求对象的方法
  11.     public Result run(Request request) {
  12.         // 实际的测试运行逻辑
  13.         return run(request.getRunner());
  14.     }
  15.     // 私有方法,执行测试并返回结果
  16.     private Result run(Runner runner) {
  17.         Result result = new Result();
  18.         RunListener listener = result.createListener();
  19.         notifier.addFirstListener(listener);
  20.         try {
  21.             notifier.fireTestRunStarted(runner.getDescription());
  22.             runner.run(notifier);
  23.             notifier.fireTestRunFinished(result);
  24.         } finally {
  25.             removeListener(listener);
  26.         }
  27.         return result;
  28.     }
  29. }
复制代码

  • 创建请求对象:Request类是制作者模式中的制作者类,它提供了方法来渐渐添加测试类和其他配置。
  1. public class Request {
  2.     // 静态方法,用于创建包含测试类的请求
  3.     public static Request classes(Class<?>... classes) {
  4.         return new Request().classes(Arrays.asList(classes));
  5.     }
  6.     // 向请求中添加测试类
  7.     public Request classes(Collection<Class<?>> classes) {
  8.         // 添加测试类逻辑
  9.         return this; // 返回自身,支持链式调用
  10.     }
  11.     // 获取构建好的Runner
  12.     public Runner getRunner() {
  13.         // 创建并返回Runner逻辑
  14.     }
  15. }
复制代码

  • 链式调用:Request类的方法设计支持链式调用,这是制作者模式的一个典型特征。每个方法返回Request对象的引用,允许继续添加更多的配置。
  1. // 示例使用
  2. Request request = JUnitCore.request()
  3.                           .classes(MyTest.class, AnotherTest.class)
  4.                           // 可以继续添加其他配置
  5.                           ;
  6. Runner runner = request.getRunner();
  7. Result result = new JUnitCore().run(runner);
复制代码

  • 实行测试:一旦通过Request对象构建好了测试配置,就可以通过JUnitCore的run方法来实行测试,并获取结果。
  1. // 执行测试并获取结果
  2. Result result = JUnitCore.run(request);
复制代码
靓仔们,我们可以看到JUnitCore和Request的联合利用体现了制作者模式的精髓。这种模式允许开发者以一种非常灵活和表达性强的方式来构建测试配置,然后再运行它们。制作者模式的利用提高了代码的可读性和可维护性,并且使得扩展新的配置选项变得更加轻易。
4. 计谋模式

计谋模式允许在运行时选择算法的行为,这在JUnit中体现为不同的Runner实现。每种Runner都定义了实行测试的特定计谋,例如,BlockJUnit4ClassRunner是JUnit 4的默认Runner,而JUnitCore允许通过传递不同的Runner来改变测试实行的行为。
以下是Runner接口和几种实现的源码分析:

  • 定义计谋接口:Runner接口定义了全部测试运行器必须实现的计谋方法。run方法接受一个RunNotifier参数,它是JUnit中的一个观察者,用于通知测试变乱。
  1. public interface Runner {
  2.     void run(RunNotifier notifier);
  3.     Description getDescription();
  4. }
复制代码

  • 实现具体计谋:JUnit 提供了多种Runner实现,每种实现都有其特定的测试实行逻辑。


  • BlockJUnit4ClassRunner是JUnit 4 的默认运行器,它利用注解来识别测试方法,并按次序实行它们。
  1. public class BlockJUnit4ClassRunner extends ParentRunner<TestResult> {
  2.     @Override
  3.     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
  4.         runLeaf(methodBlock(method), description, notifier);
  5.     }
  6.     protected Statement methodBlock(FrameworkMethod method) {
  7.         // 创建一个Statement,可能包含@Before, @After等注解的处理
  8.     }
  9. }
复制代码

  • Suite是一个Runner实现,它允许将多个测试类组合成一个测试套件。
  1. public class Suite extends ParentRunner<Runner> {
  2.     @Override
  3.     protected void runChild(Runner runner, RunNotifier notifier) {
  4.         runner.run(notifier);
  5.     }
  6. }
复制代码

  • 上下文配置:JUnitCore作为上下文,它根据传入的Runner实行测试。
  1. public class JUnitCore {
  2.     public Result run(Request request) {
  3.         Runner runner = request.getRunner();
  4.         return run(runner);
  5.     }
  6.     private Result run(Runner runner) {
  7.         Result result = new Result();
  8.         RunNotifier notifier = new RunNotifier();
  9.         runner.run(notifier);
  10.         return result;
  11.     }
  12. }
复制代码

  • 利用@RunWith注解:开发者可以利用@RunWith注解来指定测试类应该利用的Runner。
  1. @RunWith(Suite.class)
  2. public class MyTestSuite {
  3.     // 测试类组合
  4. }
复制代码

  • 自定义Runner:开发者也可以通过实现自己的Runner来改变测试实行的行为。
  1. public class MyCustomRunner extends BlockJUnit4ClassRunner {
  2.     public MyCustomRunner(Class<?> klass) throws InitializationError {
  3.         super(klass);
  4.     }
  5.     @Override
  6.     protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
  7.         // 自定义@Before注解的处理
  8.     }
  9. }
复制代码

  • 运行自定义Runner
  1. JUnitCore.runClasses(MyCustomRunner.class, MyTest.class);
复制代码
通过计谋模式,JUnit 允许开发者根据不同的测试需求选择不同的实行计谋,或者通过自定义Runner来扩展测试框架的功能。这种设计提供了高度的灵活性和可扩展性,使得JUnit可以或许顺应各种复杂的测试场景。
5. 装饰者模式

装饰者模式是一种结构型设计模式,它允许用户在不修改对象自身的基础上,向一个对象添加新的功能。在JUnit中,装饰者模式被用于加强测试类的行为,比如通过@RunWith注解来指定利用特定的Runner类来运行测试。
以下是@RunWith注解利用装饰者模式的实现过程和源码分析:

  • 定义组件接口:Runner接口是JUnit中全部测试运行器的组件接口,它定义了运行测试的根本方法。
  1. public interface Runner extends Describable {
  2.     void run(RunNotifier notifier);
  3.     Description getDescription();
  4. }
复制代码

  • 创建具体组件:BlockJUnit4ClassRunner是JUnit中一个具体的Runner实现,它提供了实行JUnit 4测试的根本逻辑。
  1. public class BlockJUnit4ClassRunner extends ParentRunner<T> {
  2.     protected BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError {
  3.         super(klass);
  4.     }
  5.     // 实现具体的测试执行逻辑
  6. }
复制代码

  • 定义装饰者抽象类:ParentRunner类是一个装饰者抽象类,它提供了装饰Runner的根本结构和默认实现。
  1. public abstract class ParentRunner<T> implements Runner {
  2.     protected Class<?> fTestClass;
  3.     protected Statement classBlock;
  4.     public void run(RunNotifier notifier) {
  5.         // 装饰并执行测试
  6.     }
  7.     // 其他公共方法和装饰逻辑
  8. }
复制代码

  • 实现具体装饰者:通过@RunWith注解,JUnit允许开发者指定一个装饰者Runner来加强测试类的行为。例如,Suite类是一个装饰者,它可以运行多个测试类。
  1. @RunWith(Suite.class)
  2. @Suite.SuiteClasses({Test1.class, Test2.class})
  3. public class AllTests {
  4.     // 这个类使用SuiteRunner来运行包含的测试类
  5. }
复制代码

  • 利用@RunWith注解:开发者通过在测试类上利用@RunWith注解来指定一个装饰者Runner。
  1. @RunWith(CustomRunner.class)
  2. public class MyTest {
  3.     // 这个测试类将使用CustomRunner来运行
  4. }
复制代码

  • 自定义Runner:开发者可以实现自己的Runner来提供额外的功能,如下所示:
  1. public class CustomRunner extends BlockJUnit4ClassRunner {
  2.     public CustomRunner(Class<?> klass) throws InitializationError {
  3.         super(klass);
  4.     }
  5.     @Override
  6.     protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
  7.         // 添加@Before注解的处理
  8.         return super.withBefores(method, target, statement);
  9.     }
  10.     @Override
  11.     protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
  12.         // 添加@After注解的处理
  13.         return super.withAfters(method, target, statement);
  14.     }
  15. }
复制代码

  • 运行时创建装饰者:在JUnit的运行时,根据@RunWith注解的值,利用反射来实例化对应的Runner装饰者。
  1. public static Runner getRunner(Class<?> testClass) throws InitializationError {
  2.     RunWith runWith = testClass.getAnnotation(RunWith.class);
  3.     if (runWith == null) {
  4.         return new BlockJUnit4ClassRunner(testClass);
  5.     } else {
  6.         try {
  7.             // 使用反射创建指定的Runner装饰者
  8.             return (Runner) runWith.value().getConstructor(Class.class).newInstance(testClass);
  9.         } catch (Exception e) {
  10.             throw new InitializationError("Couldn't create runner for class " + testClass, e);
  11.         }
  12.     }
  13. }
复制代码
通过利用装饰者模式,JUnit 允许开发者通过@RunWith注解来灵活地为测试类添加额外的行为,而无需修改测试类自己。这种设计提高了代码的可扩展性和可维护性,同时也允许开发者通过自定义Runner来实现复杂的测试逻辑。
6. 观察者模式

观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,全部依赖于它的对象都会得到通知并主动更新。在JUnit中,观察者模式主要应用于测试结果监听器,以通知测试过程中的各个变乱,如测试开始、测试失败、测试完成等。
以下是JUnit中观察者模式的实现过程和源码分析:

  • 定义观察者接口:TestListener接口定义了测试过程中需要通知的变乱的方法。
  1. public interface TestListener {
  2.     void testAborted(Test test, Throwable t);
  3.     void testAssumptionFailed(Test test, AssumptionViolatedException e);
  4.     void testFailed(Test test, AssertionFailedError e);
  5.     void testFinished(Test test);
  6.     void testIgnored(Test test);
  7.     void testStarted(Test test);
  8. }
复制代码

  • 创建主题:RunNotifier类作为主题,维护了一组观察者列表,并提供了添加、移除观察者以及通知观察者的方法。
  1. public class RunNotifier {
  2.     private final List<TestListener> listeners = new ArrayList<TestListener>();
  3.     public void addListener(TestListener listener) {
  4.         listeners.add(listener);
  5.     }
  6.     public void removeListener(TestListener listener) {
  7.         listeners.remove(listener);
  8.     }
  9.     protected void fireTestRunStarted(Description description) {
  10.         for (TestListener listener : listeners) {
  11.             listener.testStarted(null);
  12.         }
  13.     }
  14.     // 其他类似fireTestXXXStarted/Finished等方法
  15. }
复制代码

  • 实现具体观察者:具体的测试结果监听器实现TestListener接口,根据测试变乱实行相应的逻辑。
  1. public class MyTestListener implements TestListener {
  2.     @Override
  3.     public void testStarted(Test test) {
  4.         // 测试开始时的逻辑
  5.     }
  6.     @Override
  7.     public void testFinished(Test test) {
  8.         // 测试结束时的逻辑
  9.     }
  10.     // 实现其他TestListener方法
  11. }
复制代码

  • 注册观察者:在测试运行前,通过RunNotifier将具体的监听器添加到观察者列表中。
  1. RunNotifier notifier = new RunNotifier();
  2. notifier.addListener(new MyTestListener());
复制代码

  • 通知观察者:在测试实行过程中,RunNotifier会调用相应的方法来通知全部注册的观察者关于测试变乱的信息。
  1. protected void run(Runner runner) {
  2.     // ...
  3.     runner.run(notifier);
  4.     // ...
  5. }
复制代码

  • 利用JUnitCore运行测试:JUnitCore类利用RunNotifier来运行测试,并通知注册的监听器。
  1. public class JUnitCore {
  2.     public Result run(Request request) {
  3.         Runner runner = request.getRunner();
  4.         return run(runner);
  5.     }
  6.     private Result run(Runner runner) {
  7.         Result result = new Result();
  8.         RunNotifier notifier = new RunNotifier();
  9.         notifier.addListener(result.createListener());
  10.         runner.run(notifier);
  11.         return result;
  12.     }
  13. }
复制代码

  • 结果监听器:Result类自己也是一个观察者,它实现了TestListener接口,用于收集测试结果。
  1. public class Result implements TestListener {
  2.     public void testRunStarted(Description description) {
  3.         // 测试运行开始时的逻辑
  4.     }
  5.     public void testRunFinished(long elapsedTime) {
  6.         // 测试运行结束时的逻辑
  7.     }
  8.     // 实现其他TestListener方法
  9. }
复制代码
通过观察者模式,JUnit 允许开发者自定义测试结果监听器,以获取测试过程中的各种变乱通知。这种模式提高了测试框架的灵活性和可扩展性,使得开发者可以根据自己的需求来监控和响应测试变乱。
7. 依赖注入

依赖注入是一种常见的设计模式,它允许将组件的依赖关系从组件自己中解耦出来,通常通过构造函数、工厂方法或 setter 方法注入。在 JUnit 中,依赖注入主要用于测试领域,特别是与 Mockito 这样的模拟框架联合利用时,可以方便地注入模拟对象。
以下是 @Mock 和 @InjectMocks 注解利用依赖注入的实现过程和源码分析:

  • Mockito 依赖注入注解

    • @Mock 注解用于创建模拟对象。
    • @InjectMocks 注解用于将模拟对象注入到测试类中。

  • 利用 @Mock 创建模拟对象

    • 在测试类中,利用 @Mock 注解的字段将主动被 Mockito 框架在测试实行前初始化为模拟对象。

  1. public class MyTest {
  2.     @Mock
  3.     private Collaborator mockCollaborator;
  4.    
  5.     // 其他测试方法...
  6. }
复制代码

  • 利用 @InjectMocks 举行依赖注入

    • 当测试类中的对象需要依赖其他模拟对象时,利用 @InjectMocks 注解可以主动注入这些模拟对象。

  1. @RunWith(MockitoJUnitRunner.class)
  2. public class MyTest {
  3.     @Mock
  4.     private Collaborator mockCollaborator;
  5.     @InjectMocks
  6.     private MyClass testClass;
  7.    
  8.     // 测试方法...
  9. }
复制代码

  • MockitoJUnitRunner

    • @RunWith(MockitoJUnitRunner.class) 指定了利用 Mockito 的测试运行器,它负责设置测试环境,包括初始化模拟对象和注入依赖。

  • Mockito 框架初始化过程

    • 在测试运行前,Mockito 框架会查找全部利用 @Mock 注解的字段,并创建相应的模拟对象。
    • 接着,对于利用 @InjectMocks 注解的字段,Mockito 会举行反射查抄其构造函数和成员变量,利用创建的模拟对象举行依赖注入。

  • Mockito 注解处置处罚器

    • Mockito 框架内部利用注解处置处罚器来处置处罚 @Mock 和 @InjectMocks 注解。这些处置处罚器在测试实行前初始化模拟对象,并在必要时注入它们。

  1. public class MockitoAnnotations {
  2.     public static void initMocks(Object testClass) {
  3.         // 查找并初始化 @Mock 注解的字段
  4.         for (Field field : Reflections.fieldsAnnotatedWith(testClass.getClass(), Mock.class)) {
  5.             field.setAccessible(true);
  6.             try {
  7.                 field.set(testClass, MockUtil.createMock(field.getType()));
  8.             } catch (IllegalAccessException e) {
  9.                 throw new RuntimeException("Unable to inject @Mock for " + field, e);
  10.             }
  11.         }
  12.         // 查找并处理 @InjectMocks 注解的字段
  13.         for (Field field : Reflections.fieldsAnnotatedWith(testClass.getClass(), InjectMocks.class)) {
  14.             // 注入逻辑...
  15.         }
  16.     }
  17. }
复制代码

  • 测试方法实行

    • 在测试方法实行期间,如果测试类中的实例调用了被 @Mock 注解的对象的方法,实际上是调用了模拟对象的方法,可以举行行为验证或返回预设的值。

  • Mockito 模拟行为

    • 开发者可以利用 Mockito 提供的 API 来定义模拟对象的行为,例如利用 when().thenReturn() 或 doThrow() 等方法。

  1. when(mockCollaborator.someMethod()).thenReturn("expected value");
复制代码
通过依赖注入,JUnit 和 Mockito 的联合利用极大地简化了测试过程中的依赖管理,使得测试代码更加简洁和专注于测试逻辑自己。同时,这也提高了测试的可读性和可维护性。
8. 反射机制

在JUnit中,反射机制是实现动态测试发现和实行的关键技术之一。反射允许在运行时查抄类的信息、创建对象、调用方法和访问字段,这使得JUnit可以或许在不直接引用测试方法的环境下实行它们。以下是利用Java反射API来动态发现和实行测试方法的实现过程和源码分析:

  • 获取类对象:首先,利用Class.forName()方法获取测试类的Class对象。
  1. Class<?> testClass = Class.forName("com.example.MyTest");
复制代码

  • 获取测试方法列表:通过Class对象,利用Java反射API获取类中全部声明的方法。
  1. Method[] methods = testClass.getDeclaredMethods();
复制代码

  • 筛选测试方法:遍历方法列表,筛选出标记为测试方法的Method对象。在JUnit中,这通常是通过@Test注解来标识的。
  1. List<FrameworkMethod> testMethods = new ArrayList<>();
  2. for (Method method : methods) {
  3.     if (method.isAnnotationPresent(Test.class)) {
  4.         testMethods.add(new FrameworkMethod(method));
  5.     }
  6. }
复制代码

  • 创建测试方法的封装对象:JUnit利用FrameworkMethod类来封装Method对象,提供额外的功能,如处置处罚@Before、@After注解。
  1. public class FrameworkMethod {
  2.     private final Method method;
  3.     public FrameworkMethod(Method method) {
  4.         this.method = method;
  5.     }
  6.     public Object invokeExplosively(Object target, Object... params) throws Throwable {
  7.         try {
  8.             return method.invoke(target, params);
  9.         } catch (IllegalAccessException | InvocationTargetException e) {
  10.             throw new Exception("Failed to invoke " + method, e.getCause());
  11.         }
  12.     }
  13. }
复制代码

  • 调用测试方法:利用FrameworkMethod的invokeExplosively()方法,在指定的测试实例上调用测试方法。
  1. public class BlockJUnit4ClassRunner extends ParentRunner<MyClass> {
  2.     @Override
  3.     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
  4.         runLeaf(new Statement() {
  5.             @Override
  6.             public void evaluate() throws Throwable {
  7.                 Object target = new MyClass();
  8.                 method.invokeExplosively(target);
  9.             }
  10.         }, methodBlock(method), notifier);
  11.     }
  12. }
复制代码

  • 处置处罚测试方法的实行:在invokeExplosively()方法中,利用Method对象的invoke()方法来实行测试方法。这个方法可以或许处置处罚方法的访问权限,并调用实际的测试逻辑。
  • 非常处置处罚:在实行测试方法时,可能会抛出非常。JUnit需要捕捉这些非常,并适当地处置处罚它们,例如将测试失败通知给RunNotifier。
  • 整合到测试运行器:将上述过程整合到JUnit的测试运行器中,如BlockJUnit4ClassRunner,它负责创建测试实例、调用测试方法,并处置处罚测试结果。
通过利用Java反射API,JUnit可以或许以一种非常灵活和动态的方式来实行测试方法。这种机制不但提高了JUnit框架的通用性和可扩展性,而且允许开发者在不修改测试类代码的环境下,通过配置和注解来控制测试的行为。反射机制是JUnit强大功能的一个紧张支柱。
9. 非常处置处罚

在JUnit中,非常处置处罚是一个精细的过程,确保了测试实行的稳定性和结果的准确性。JUnit区分了预期的非常(如测试中显式查抄的非常)和未预期的非常(如错误或未捕捉的非常),并相应地报告这些非常。以下是JUnit中非常处置处罚的实现过程和源码分析:

  • 测试方法实行:在测试方法实行时,JUnit会捕捉全部抛出的非常。
  1. public void runBare() throws Throwable {
  2.     Throwable exception = null;
  3.     try {
  4.         method.invoke(target);
  5.     } catch (InvocationTargetException e) {
  6.         exception = e.getCause();
  7.     } catch (IllegalAccessException e) {
  8.         exception = e;
  9.     } catch (IllegalArgumentException e) {
  10.         exception = e;
  11.     } catch (SecurityException e) {
  12.         exception = e;
  13.     }
  14.     if (exception != null) {
  15.         runAfters();
  16.         throw exception;
  17.     }
  18. }
复制代码

  • 预期非常的处置处罚:利用@Test(expected = Exception.class)注解可以指定测试方法预期抛出的非常类型。如果实际抛出的非常与预期不符,JUnit会报告测试失败。
  1. @Test(expected = SpecificException.class)
  2. public void testMethod() {
  3.     // 测试逻辑,预期抛出 SpecificException
  4. }
复制代码

  • 断言非常:Assert类提供了assertThrows方法,允许在测试中显式查抄方法是否抛出了预期的非常。
  1. public static <T extends Throwable> T assertThrows(
  2.     Class<T> expectedThrowable, Executable executable, String message) {
  3.     try {
  4.         executable.execute();
  5.         fail(message);
  6.     } catch (Throwable actualException) {
  7.         if (!expectedThrowable.isInstance(actualException)) {
  8.             throw new AssertionFailedError(
  9.                 "Expected " + expectedThrowable.getName() + " but got " + actualException.getClass().getName());
  10.         }
  11.         @SuppressWarnings("unchecked")
  12.         T result = (T) actualException;
  13.         return result;
  14.     }
  15. }
复制代码

  • 非常的分类:JUnit将非常分为两种类型:AssertionError和Throwable。AssertionError通常表示测试失败,而Throwable可能表示测试中的严峻错误。
  • 非常的报告:在捕捉非常后,JUnit会将非常信息报告给RunNotifier,以便举行适当的处置处罚。
  1. protected void runChild(FrameworkMethod method, RunNotifier notifier) {
  2.     runLeaf(new Statement() {
  3.         @Override
  4.         public void evaluate() throws Throwable {
  5.             try {
  6.                 method.invokeExplosively(testInstance);
  7.             } catch (Throwable e) {
  8.                 notifier.fireTestFailure(new Failure(method, e));
  9.             }
  10.         }
  11.     }, describeChild(method), notifier);
  12. }
复制代码

  • 非常的监听:RunNotifier监听器可以捕捉并处置处罚测试过程中抛出的非常,例如记载失败或向用户报告错误。
  1. public void addListener(TestListener listener) {
  2.     listeners.add(listener);
  3. }
  4. // 在测试执行过程中调用
  5. notifier.fireTestFailure(new Failure(method, e));
复制代码

  • 自定义非常处置处罚:开发者可以通过实现自定义的TestListener来捕捉和处置处罚测试过程中的非常。
  • 非常的传播:在某些环境下,JUnit允许非常向上传播,使得测试框架或IDE可以或许捕捉并显示给用户。
通过精细的非常处置处罚,JUnit确保了测试的准确性和可靠性,同时提供了灵活的错误报告机制。这使得开发者可以或许快速定位息争决问题,提高了开发和测试的服从。
10. 解耦合

在JUnit中,解耦合是通过将测试实行的不同方面分离成独立的组件来实现的,从而提高了代码的可维护性和可扩展性。以下是解耦合实现过程的具体分析:

  • 测试实行器(Runner):Runner接口定义了实行测试的方法,每个具体的Runner实现负责运行测试用例的逻辑。
  1. public interface Runner {
  2.     void run(RunNotifier notifier);
  3.     Description getDescription();
  4. }
复制代码

  • 测试监听器(RunListener):RunListener接口定义了测试过程中的变乱回调方法,用于监听测试的开始、成功、失败和结束等变乱。
  1. public interface RunListener {
  2.     void testRunStarted(Description description);
  3.     void testRunFinished(Result result);
  4.     void testStarted(Description description);
  5.     void testFinished(Description description);
  6.     // 其他事件回调...
  7. }
复制代码

  • 测试结果(Result):Result类实现了RunListener接口,用于收集和存储测试实行的结果。
  1. public class Result implements RunListener {
  2.     private List<Failure> failures = new ArrayList<>();
  3.     @Override
  4.     public void testRunFinished(Result result) {
  5.         // 收集测试运行结果
  6.     }
  7.     @Override
  8.     public void testFailure(Failure failure) {
  9.         // 收集测试失败信息
  10.         failures.add(failure);
  11.     }
  12.     // 其他RunListener方法实现...
  13. }
复制代码

  • 职责分离:Runner负责实行测试逻辑,RunListener负责监听测试变乱,而Result负责收集测试结果。这三者通过接口和回调机制相互协作,但各自独立实现。
  • 利用RunNotifier协调:RunNotifier类作为协调者,维护了RunListener的注册和变乱分发。
  1. public class RunNotifier {
  2.     private final List<RunListener> listeners = new ArrayList<>();
  3.     public void addListener(RunListener listener) {
  4.         listeners.add(listener);
  5.     }
  6.     public void fireTestRunStarted(Description description) {
  7.         for (RunListener listener : listeners) {
  8.             listener.testRunStarted(description);
  9.         }
  10.     }
  11.     // 其他事件分发方法...
  12. }
复制代码

  • 测试实行流程:在测试实行时,Runner会创建一个RunNotifier实例,然后实行测试,并在适当的时候调用RunNotifier的变乱分发方法。
  1. public class BlockJUnit4ClassRunner extends ParentRunner {
  2.     @Override
  3.     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
  4.         RunBefores runBefores = new RunBefores(noTestsYet, method, null);
  5.         Statement statement = new RunAfters(runBefores, method, null);
  6.         statement.evaluate();
  7.     }
  8.     @Override
  9.     public void run(RunNotifier notifier) {
  10.         // 初始化测试运行
  11.         Description description = getDescription();
  12.         notifier.fireTestRunStarted(description);
  13.         try {
  14.             // 执行测试
  15.             runChildren(makeTestRunNotifier(notifier, description));
  16.         } finally {
  17.             // 测试运行结束
  18.             notifier.fireTestRunFinished(result);
  19.         }
  20.     }
  21. }
复制代码

  • 结果收集和报告:测试完成后,Result对象会包含全部测试的结果,可以被用来生成测试报告或举行其他后续处置处罚。
  • 解耦合的优势:通过将测试实行、监听和结果收集分离,JUnit允许开发者自定义测试实行流程(通过自定义Runner)、添加自定义监听器(通过实现RunListener接口)以及处置处罚测试结果(通过操作Result对象)。
这种解耦合的设计使得JUnit非常灵活,易于扩展,同时也使得测试代码更加清晰和易于理解。开发者可以根据需要替换或扩展框架的任何部分,而不影响其他部分的功能。
11. 可扩展性

JUnit的可扩展性体如今多个方面,包括自定义Runner、TestRule和断言(Assertion)方法。以下是这些可扩展性点的实现过程和源码分析:
自定义 Runner

自定义Runner允许开发者定义自己的测试运行逻辑。以下是创建自定义Runner的步调:

  • 实现Runner接口:创建一个类实现Runner接口,并实现run方法和getDescription方法。
  1. public class CustomRunner extends Runner {
  2.     private final Class<?> testClass;
  3.     public CustomRunner(Class<?> testClass) throws InitializationError {
  4.         this.testClass = testClass;
  5.     }
  6.     @Override
  7.     public Description getDescription() {
  8.         // 返回测试描述
  9.     }
  10.     @Override
  11.     public void run(RunNotifier notifier) {
  12.         // 自定义测试运行逻辑
  13.     }
  14. }
复制代码

  • 利用@RunWith注解:在测试类上利用@RunWith注解来指定利用自定义的Runner。
  1. @RunWith(CustomRunner.class)
  2. public class MyTests {
  3.     // 测试方法...
  4. }
复制代码
自定义 TestRule

TestRule接口允许开发者插入测试方法实行前后的逻辑。以下是创建自定义TestRule的步调:

  • 实现TestRule接口:创建一个类实现TestRule接口。
  1. public class CustomTestRule implements TestRule {
  2.     @Override
  3.     public Statement apply(Statement base, FrameworkMethod method, Object target) {
  4.         // 返回一个Statement,包装原始的测试逻辑
  5.     }
  6. }
复制代码

  • 利用@Rule注解:在测试类或方法上利用@Rule注解来指定利用自定义的TestRule。
  1. public class MyTests {
  2.     @Rule
  3.     public CustomTestRule customTestRule = new CustomTestRule();
  4.     // 测试方法...
  5. }
复制代码
自定义 Assertion 方法

JUnit提供了一个Assert类,包含许多断言方法。开发者也可以添加自己的断言方法:

  • 扩展Assert类:创建一个工具类,添加自定义的静态方法。
  1. public class CustomAssertions {
  2.     public static void assertEquals(String message, int expected, int actual) {
  3.         if (expected != actual) {
  4.             throw new AssertionFailedError(message);
  5.         }
  6.     }
  7. }
复制代码

  • 利用自定义断言:在测试方法中调用自定义的断言方法。
  1. public void testCustomAssertion() {
  2.     CustomAssertions.assertEquals("Values should be equal", 1, 2);
  3. }
复制代码
源码分析

以下是利用自定义Runner、TestRule和断言方法的示例:
  1. // 自定义Runner
  2. public class CustomRunner extends Runner {
  3.     public CustomRunner(Class<?> klass) throws InitializationError {
  4.         // 初始化逻辑
  5.     }
  6.     @Override
  7.     public Description getDescription() {
  8.         // 返回测试的描述信息
  9.     }
  10.     @Override
  11.     public void run(RunNotifier notifier) {
  12.         // 自定义测试执行逻辑,包括调用测试方法和处理测试结果
  13.     }
  14. }
  15. // 自定义TestRule
  16. public class CustomTestRule implements TestRule {
  17.     @Override
  18.     public Statement apply(Statement base, FrameworkMethod method, Object target) {
  19.         // 包装原始的测试逻辑,可以在测试前后执行额外的操作
  20.         return new Statement() {
  21.             @Override
  22.             public void evaluate() throws Throwable {
  23.                 // 测试前的逻辑
  24.                 base.evaluate();
  25.                 // 测试后的逻辑
  26.             }
  27.         };
  28.     }
  29. }
  30. // 使用自定义Runner和TestRule的测试类
  31. @RunWith(CustomRunner.class)
  32. public class MyTests {
  33.     @Rule
  34.     public CustomTestRule customTestRule = new CustomTestRule();
  35.     @Test
  36.     public void myTest() {
  37.         // 测试逻辑,使用自定义断言
  38.         CustomAssertions.assertEquals("Expected and actual values should match", 1, 1);
  39.     }
  40. }
复制代码
通过这些自定义扩展,JUnit允许开发者根据特定需求调整测试行为,加强测试框架的功能,实现高度定制化的测试流程。这种可扩展性是JUnit强大顺应性的关键因素之一。
12. 参数化测试

参数化测试是JUnit提供的一项功能,它允许为单个测试方法提供多种输入参数,从而用一个测试方法覆盖多种测试场景。以下是参数化测试的实现过程和源码分析:

  • 利用@Parameterized注解:首先,在测试类上利用@RunWith(Parameterized.class)来指定利用参数化测试的Runner。
  1. @RunWith(Parameterized.class)
  2. public class MyParameterizedTests {
  3.     // 测试方法的参数
  4.     private final int input;
  5.     private final int expectedResult;
  6.     // 构造函数,用于接收参数
  7.     public MyParameterizedTests(int input, int expectedResult) {
  8.         this.input = input;
  9.         this.expectedResult = expectedResult;
  10.     }
  11.     // 测试方法
  12.     @Test
  13.     public void testWithParameters() {
  14.         // 使用参数进行测试
  15.         assertEquals(expectedResult, someMethod(input));
  16.     }
  17.     // 获取参数来源
  18.     @Parameters
  19.     public static Collection<Object[]> data() {
  20.         return Arrays.asList(new Object[][] {
  21.             { 1, 2 },
  22.             { 2, 4 },
  23.             { 3, 6 }
  24.         });
  25.     }
  26. }
复制代码

  • 定义测试参数:利用@Parameters注解的方法来定义测试参数。这个方法需要返回一个Collection,其中包含参数数组的列表。
  1. @Parameters
  2. public static Collection<Object[]> parameters() {
  3.     return Arrays.asList(new Object[][] {
  4.         // 参数列表
  5.     });
  6. }
复制代码

  • 构造函数注入:参数化测试框架会通过构造函数将参数注入到测试实例中。
  1. public MyParameterizedTests(int param1, String param2) {
  2.     // 使用参数初始化测试用例
  3. }
复制代码

  • 参数化测试的实行:JUnit框架会为@Parameters方法中定义的每一组参数创建测试类的实例,并实行测试方法。
  • 自定义参数源:除了利用@Parameters注解的方法外,还可以利用Parameterized.ParametersRunnerFactory注解来指定自定义的参数源。
  1. @RunWith(value = Parameterized.class, runnerFactory = MyParametersRunnerFactory.class)
  2. public class MyParameterizedTests {
  3.     // 测试方法和参数...
  4. }
  5. public class MyParametersRunnerFactory implements ParametersRunnerFactory {
  6.     @Override
  7.     public Runner createRunnerForTestWithParameters(TestWithParameters test) {
  8.         // 返回自定义的参数化运行器
  9.     }
  10. }
复制代码

  • 利用Arguments辅助类:在JUnit 4.12中,可以利用Arguments类来简化参数的创建。
  1. @Parameters
  2. public static Collection<Object[]> data() {
  3.     return Arrays.asList(
  4.         Arguments.arguments(1, 2),
  5.         Arguments.arguments(2, 4),
  6.         Arguments.arguments(3, 6)
  7.     );
  8. }
复制代码

  • 源码分析:Parameterized类是实现参数化测试的核心。它利用ParametersRunnerFactory来创建Runner,然后为每组参数实行测试方法。
  1. public class Parameterized {
  2.     public static class ParametersRunnerFactory implements RunnerFactory {
  3.         @Override
  4.         public Runner create(Description description) {
  5.             return new BlockJUnit4ClassRunner(description.getTestClass()) {
  6.                 @Override
  7.                 protected List<Runner> getChildren() {
  8.                     // 获取参数并为每组参数创建Runner
  9.                 }
  10.             };
  11.         }
  12.     }
  13.     // 其他实现...
  14. }
复制代码
通过参数化测试,JUnit允许开发者编写更灵活、更全面的测试用例,同时保持测试代码的简洁性。这种方法特别适合于需要多种输入组合来验证逻辑精确性的场景。
13. 代码的模块化

代码的模块化是软件设计中的一种紧张实践,它将步伐分解为独立的、可重用的模块,每个模块负责一部分特定的功能。在JUnit框架中,模块化设计体如今其清晰的包结构和类的设计上。以下是JUnit中模块化实现的过程和源码分析:

  • 包结构:JUnit的源码按照功能划分为不同的包(packages),每个包包含一组相关的类。
  1. // 核心包,包含JUnit的基础类和接口
  2. org.junit
  3. // 断言包,提供断言方法
  4. org.junit.Assert
  5. // 运行器包,负责测试套件的运行和管理
  6. org.junit.runner
  7. // 规则包,提供测试规则,如测试隔离和初始化
  8. org.junit.rules
复制代码

  • 接口定义:JUnit利用接口(如Test、Runner、TestRule)定义模块的契约,确保模块间的松耦合。
  1. public interface Test {    void run(TestResult result);}public interface Runner {
  2.     void run(RunNotifier notifier);
  3.     Description getDescription();
  4. }
复制代码

  • 抽象类:利用抽象类(如Assert、Runner、TestWatcher)为模块提供共享的实现,同时保留扩展的灵活性。
  1. public abstract class Assert {
  2.     // 断言方法的默认实现
  3. }
  4. public abstract class Runner implements Describable {
  5.     // 测试运行器的默认实现
  6. }
复制代码

  • 具体实现:为每个抽象类或接口提供具体的实现,这些实现类可以在不同的测试场景中重用。
  1. public class TestCase extends Assert implements Test {
  2.     // 测试用例的具体实现
  3. }
  4. public class BlockJUnit4ClassRunner extends ParentRunner {
  5.     // 测试类的运行器实现
  6. }
复制代码

  • 依赖倒置:通过依赖接口而非具体实现,JUnit的模块可以在不修改其他模块的环境下举行扩展或替换。
  • 服务提供者接口(SPI):JUnit利用服务提供者接口来发现和加载扩展模块,如测试规则(TestRule)。
  1. public interface TestRule {
  2.     Statement apply(Statement base, Description description);
  3. }
复制代码

  • 模块化测试实行:JUnit允许开发者通过@RunWith注解指定自定义的Runner,这允许对测试实行过程举行模块化定制。
  1. @RunWith(CustomRunner.class)
  2. public class MyTests {
  3.     // ...
  4. }
复制代码

  • 参数化测试模块:参数化测试通过@Parameters注解和Parameterized类实现模块化,允许为测试方法提供不同的输入参数集。
  1. @RunWith(Parameterized.class)
  2. public class MyParameterizedTests {
  3.     @Parameters
  4.     public static Collection<Object[]> data() {
  5.         // 提供参数集
  6.     }
  7. }
复制代码

  • 解耦的变乱监听:RunNotifier和RunListener接口的利用使得测试变乱的监听和处置处罚可以独立于测试实行逻辑。
  1. public class RunNotifier {
  2.     public void addListener(RunListener listener);
  3.     // ...
  4. }
复制代码

  • 测试结果的模块化处置处罚:Result类实现了RunListener接口,负责收集和报告测试结果,与测试实行逻辑解耦。
通过这种模块化设计,JUnit提供了一个灵活、可扩展的测试框架,允许开发者根据自己的需求添加自定义的行为和扩展功能。这种设计不但提高了代码的可维护性,也方便了重用和测试过程的定制。
末了

以上就是V哥在 JUnit 框架源码学习时总结的13个非常值得学习的点,希望也可以资助到你提升编码的功力,欢迎关注威哥爱编程,一起学习框架源码,提升编程技巧,我是 V哥,爱 编程,一辈子。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

吴旭华

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表