JUnit5 + JMockit 知识整理

JUnit5 + JMockit 知识整理JUnit5https junit org junit5 基本概念 JUnit5 JUnitPlatfor JUnitJupiter JUnitVintage JUnit5 框架 复杂启动并加载运行 TestCases JUnitJupiter JUnit5TestCa 开发模型与接口 JUnitVintage

1 JUnit5

1.1 基本概念

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

依赖关系图:在这里插入图片描述

  • JUnit Platform
    • junit-platform-commons
    • junit-platform-console
    • junit-platform-console-standalone
    • junit-platform-engine
    • junit-platform-launcher
  • JUnit Jupiter:
    • junit-jupiter-api
    • junit-jupiter-engine:默认的测试引擎
    • junit-jupiter-params:支持JUnit Jupiter中的 参数化测试。
  • Junit Vintage
    • junit-vintage-engine

简而言之,JUnit Platform 提供了 JUnit5 框架相关的组件,包括框架启动,还有 TaskEngine 服务接口,谁会用到它呢?引擎开发人员以及构建工具和IDE提供商的开发人员!比如 JMockit 为了集成 JUnit5 开发了 JMockitTestEngine,Maven 测试相关插件 maven-surefire-plugin, 2.22.0 及以上版本原生支持 JUnit 5。

JUnit Jupiter 是面向测试开发人员和扩展开发人员,。

JUnit Vintage 提供了一个TestEngine,用于运行基于JUnit 3和JUnit 4的测试。

  • Test Method: 添加 @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate 注解的方法;
  • Test Class:包含 Test Method 的 Class;
  • @DisplayName: 定义 Test method 或 Test Class 的显示名称,运行或报告中使用;

1.2 Annotations

@Test, @ParameterizedTest, @RepeatedTest, @TestFactory, @TestInstance, @TestTemplate, @DisplayName, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll, @Nested, @Tag, @Disabled, @ExtendWith

@DisplayName("A special test case") class StandardTests { 
    @BeforeAll static void initAll() { 
    } @BeforeEach void init() { 
    } @Test @Tag("model") @DisplayName("first case") @EnabledOnJre(JAVA_8) @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") void succeedingTest() { 
    } @Test void failingTest() { 
    fail("a failing test"); } @Test @Disabled("for demonstration purposes") void skippedTest() { 
    // not executed } @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("Repeat!") void customDisplayName(TestInfo testInfo) { 
    assertEquals(testInfo.getDisplayName(), "Repeat! 1/1"); } @ParameterizedTest @ValueSource(strings = { 
    "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate) { 
    assertTrue(isPalindrome(candidate)); } @TestFactory Iterable<DynamicTest> dynamicTestsFromIterable() { 
    return Arrays.asList( dynamicTest("3rd dynamic test", () -> assertTrue(true)), dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2)) ); } @Nested class NestedTest{ 
    @Test void isEmpty() { 
    assertTrue(true); } } @AfterEach void tearDown() { 
    } @AfterAll static void tearDownAll() { 
    } } 
  • @BeforeAll, @AfterAll 作用在静态方法上;
  • @Disabled: 禁止执行;
  • @EnabledOnJre, @EnabledOnOs, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, @EnabledIf: 配置条件执行属性;
  • @Tag: 测试类和测试方法可以被@Tag注解标记。那些标记可以在后面被用来过滤测试发现和执行;
  • @Nested: 嵌套测试让测试编写者能够表示出几组测试用例之间的关系;
  • TestInfo, RepetitionInfo, TestReporter: 允许给测试类的构造函数和方法传入参数;
  • @RepeatedTest: 注解并指定重复运行一个测试方法的次数;
  • @ParameterizedTest: 参数化测试可以用不同的参数多次运行试; 使用 @ValueSource, @CsvSource, @CsvFileSource, @EnumSource, @MethodSource, @ArgumentsSource 来指定数据源;
  • @TestFactory: 方法本身不是测试用例,而是测试用例的工厂; DynamicTest是运行时生成的测试用例。它由一个显示名称 和Executable组成;

1.3 Maven

<build> <plugins> <plugin> <artifactId>maven-surefire-plugin 
     artifactId> <version>2.22.0 
      version> <configuration> <includes> <include>Sample.java 
       include> <include>%regex[.*(Cat|Dog).*Test.*] 
        include>  
         includes> <excludes> <exclude>/TestCircle.java 
          exclude> <exclude>/TestSquare.java 
           exclude>  
            excludes> <groups>acceptance | !feature-a 
             groups> <excludedGroups>integration, regression 
              excludedGroups> <properties> <configurationParameters> junit.jupiter.conditions.deactivate = * junit.jupiter.extensions.autodetection.enabled = true junit.jupiter.testinstance.lifecycle.default = per_class  
               configurationParameters>  
                properties>  
                 configuration>  
                  plugin>  
                   plugins>  
                    build> <dependencies> <dependency> <groupId>org.junit.jupiter 
                     groupId> <artifactId>junit-jupiter-api 
                      artifactId> <version>5.3.0 
                       version> <scope>test 
                        scope>  
                         dependency> <dependency> <groupId>org.junit.jupiter 
                          groupId> <artifactId>junit-jupiter-engine  
                           artifactId> <version>5.3.0 
                            version> <scope>test 
                             scope>  
                              dependency>  
                               dependencies> 

https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html

  • 按Tag过滤
  • 按测试类名过滤
  • 配置参数

2 JMockit

JMockit 中文网 http://jmockit.cn/index.htm

JMockit是一款Java类/接口/对象的Mock工具,目前广泛应用于Java应用程序的单元测试中。

2.1 基本概念

//JMockit的程序结构 public class ProgramConstructureTest { 
    // 这是一个测试属性 @Mocked HelloJMockit helloJMockit; @Test public void test1() { 
    // 录制(Record) new Expectations() { 
    { 
    helloJMockit.sayHello(); // 期待上述调用的返回是"hello,david",而不是返回"hello,JMockit" result = "hello,david"; } }; // 重放(Replay) String msg = helloJMockit.sayHello(); Assert.assertTrue(msg.equals("hello,david")); // 验证(Verification) new Verifications() { 
    { 
    helloJMockit.sayHello(); times = 1; } }; } @Test public void testInjectable(@Injectable Locale locale) { 
    new Expectations() { 
    // 这是一个Expectations匿名内部类 { 
    // 这是这个内部类的初始化代码块,我们在这里写录制脚本,脚本的格式要遵循下面的约定 //方法调用(可是类的静态方法调用,也可以是对象的非静态方法调用) //result赋值要紧跟在方法调用后面 //...其它准备录制脚本的代码 //方法调用 //result赋值 } }; // 静态方法不mock Assert.assertTrue(Locale.getDefault() != null); // 非静态方法(返回类型为String)也不起作用了,返回了null,但仅仅限于locale这个对象 Assert.assertTrue(locale.getCountry() == null); // 自已new一个,并不受影响 Locale chinaLocale = new Locale("zh", "CN"); Assert.assertTrue(chinaLocale.getCountry().equals("CN")); } @Test public void testCaputring(@Capturing IPrivilege privilegeManager) { 
    // 加上了JMockit的API @Capturing, // JMockit会帮我们实例化这个对象,它除了具有@Mocked的特点,还能影响它的子类/实现类 new Expectations() { 
    { 
    // 对IPrivilege的所有实现类录制,假设测试用户有权限 privilegeManager.isAllow(testUserId); result = true; } }; // 不管权限校验的实现类是哪个,这个测试用户都有权限 Assert.assertTrue(privilegeManager1.isAllow(testUserId)); Assert.assertTrue(privilegeManager2.isAllow(testUserId)); } @Test public void testMockUp() { 
    // 对Java自带类Calendar的get方法进行定制 // 只需要把Calendar类传入MockUp类的构造函数即可 new MockUp<Calendar>(Calendar.class) { 
    // 想Mock哪个方法,就给哪个方法加上@Mock, 没有@Mock的方法,不受影响 @Mock public int get(int unit) { 
    if (unit == Calendar.YEAR) { 
    return 2017; } if (unit == Calendar.MONDAY) { 
    return 12; } if (unit == Calendar.DAY_OF_MONTH) { 
    return 25; } if (unit == Calendar.HOUR_OF_DAY) { 
    return 7; } return 0; } }; // 从此Calendar的get方法,就沿用你定制过的逻辑,而不是它原先的逻辑。 Calendar cal = Calendar.getInstance(Locale.FRANCE); Assert.assertTrue(cal.get(Calendar.YEAR) == 2017); Assert.assertTrue(cal.get(Calendar.MONDAY) == 12); Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 25); Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7); // Calendar的其它方法,不受影响 Assert.assertTrue((cal.getFirstDayOfWeek() == Calendar.MONDAY)); } @Test public void testClassMockingByExpectation() { 
    AnOrdinaryClass instanceToRecord = new AnOrdinaryClass(); new Expectations(AnOrdinaryClass.class) { 
    { 
    // mock静态方法 AnOrdinaryClass.staticMethod(); result = 10; // mock普通方法 instanceToRecord.ordinaryMethod(); result = 20; // mock final方法 instanceToRecord.finalMethod(); result = 30; // native, private方法无法用Expectations来Mock } }; AnOrdinaryClass instance = new AnOrdinaryClass(); Assert.assertTrue(AnOrdinaryClass.staticMethod() == 10); Assert.assertTrue(instance.ordinaryMethod() == 20); Assert.assertTrue(instance.finalMethod() == 30); // 用Expectations无法mock native方法 Assert.assertTrue(instance.navtiveMethod() == 4); // 用Expectations无法mock private方法 Assert.assertTrue(instance.callPrivateMethod() == 5); } } 

JMockit的程序结构:

  • 测试属性&测试参数:测试属性即测试类的一个属性。它作用于测试类的所有测试方法;测试参数即测试方法的参数。二者皆可以用 @Mocked, @Tested, @Injectable,@Capturing 来标注。测试参数与测试属性的不同,主要是作用域的不同。
  • Record-Replay-Verification: 与JUnit程序的AAA(Arrange-Action-Assert)结构是一样的。Record对应Arrange,先准备一些测试数据,测试依赖。Replay对应Action,即执行测试逻辑。Verification对应Assert,即做测试验证。
  • @Mocked: @Mocked修饰的类/接口,是让 JMockit 生成一个 Mocked 对象,这个对象方法(包含静态方法)返回默认值。如果返回类型是其它引用类型,则返回这个引用类型的Mocked对象。
  • @Tested, @Injectable: @Tested修饰的类,表示是测试对象; @Injectable 也表示一个Mocked对象,相比@Mocked,只不过只影响类的一个实例。
  • @Capturing: 主要用于子类/实现类的Mock;
  • Expectations: 主要是用于录制, 即录制类/对象的调用,返回值是什么。
  • @Mock: 直接 mock 对象的方法;
  • 用Expectations来Mock类与用Expectations来Mock实例的唯一不同就在于,前者影响类的所有实例,而后者只影响某一个实例。

2.2 JMockit 架构

架构图: 在这里插入图片描述

通过上面的架构图,我们可以看到JMockit有如下核心组件

  1. JVM Attach

JMockit使用了JDK6动态添加代理功能。目的是为了运行JMockit启动程序做准备。 JMockit提供了不同OS的hotSpot JVM的Attach支持: BsdVirtualMachine, LinuxVirtualMachine,SolarisVirtualMachine,WindowsVirtualMachine。

JMockit启动程序:主要功能是集成测试框架(JUnit/TestNG),完成对JMockit类转换器织入。

  1. 测试框架集成

提供了JUnit4/5, TestNG的支持。

b) 对JUnit5/TestNG的集成方法: 由于JUnit5/TestNG支持ServiceLoader的扩展体系,JMockit通过配置/META-INF/services/org.junit.platform.engine.TestEngine,/META-INF/services/org.testng.ITestNGListener完成对JUnit5/TestNG的集成。集成的目的同样是为了让测试程序在运行测试方法前,完成Mock 注解API(@Mocked,@Injectable,@Capturing)修饰的测试属性&测试参数的类做相关字节码的织入。

  1. 字节码处理

通过ASM,在类的某个方法中加入某段逻辑以达到Mock的目的;生成某个类的子类以支持抽象类的Mock;生成某个接口的实例类以支持接口的Mock。通过ASM, 这些都变得不那么复杂了。

  1. 类转换器

类转换器是JMockit的核心。Mock的核心就是JMockit不同的类转换器在起作用。

  1. Mock API

@Mocked, @Tested ,@Injectable, @Capturing, MockUp, @Mock ,Expectations, Verifications这些API,通过前面基础知识,常见用法等的学习,这些API已经耳熟能详了吧。 基本能满足大部分的Mock场景了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224968.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午10:27
下一篇 2026年3月17日 上午10:27


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号