JUnit5 + JMockit 知识整理
文章目录
- 1 JUnit5
- 1.1 基本概念
- 1.2 Annotations
- 1.3 Maven
- 2 JMockit
- 2.1 基本概念
- 2.2 JMockit 架构
参考资料:
https://sjyuan.cc/junit5/user-guide-cn/
http://jmockit.cn/index.htm
1 JUnit5
1.1 基本概念
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
依赖关系图:
TaskEngine:
maven-surefire-plugin:
- 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 的显示名称,运行或报告中使用;
Record-Replay-Verification
Arrange-Action-Assert
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 {@BeforeAllstatic void initAll() {}@BeforeEachvoid init() {}@Test@Tag("model")@DisplayName("first case")@EnabledOnJre(JAVA_8)@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")void succeedingTest() {}@Testvoid 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));}@TestFactoryIterable<DynamicTest> dynamicTestsFromIterable() {return Arrays.asList(dynamicTest("3rd dynamic test", () -> assertTrue(true)),dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2)));}@Nestedclass NestedTest{@Testvoid isEmpty() {assertTrue(true);}}@AfterEachvoid tearDown() {}@AfterAllstatic 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 = truejunit.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 {// 这是一个测试属性@MockedHelloJMockit helloJMockit;@Testpublic 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;}};}@Testpublic void testInjectable(@Injectable Locale locale) {new Expectations() {// 这是一个Expectations匿名内部类{// 这是这个内部类的初始化代码块,我们在这里写录制脚本,脚本的格式要遵循下面的约定//方法调用(可是类的静态方法调用,也可以是对象的非静态方法调用)//result赋值要紧跟在方法调用后面//...其它准备录制脚本的代码//方法调用//result赋值}};// 静态方法不mockAssert.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"));}@Testpublic 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));}@Testpublic void testMockUp() {// 对Java自带类Calendar的get方法进行定制// 只需要把Calendar类传入MockUp类的构造函数即可new MockUp<Calendar>(Calendar.class) {// 想Mock哪个方法,就给哪个方法加上@Mock, 没有@Mock的方法,不受影响@Mockpublic 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));}@Testpublic 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有如下核心组件
- JVM Attach
JMockit使用了JDK6动态添加代理功能。目的是为了运行JMockit启动程序做准备。 JMockit提供了不同OS的hotSpot JVM的Attach支持: BsdVirtualMachine, LinuxVirtualMachine,SolarisVirtualMachine,WindowsVirtualMachine。
JMockit启动程序:主要功能是集成测试框架(JUnit/TestNG),完成对JMockit类转换器织入。
- 测试框架集成
提供了JUnit4/5, TestNG的支持。
a) 对JUnit4的集成方法:改写JUnit4的核心类org.junit.runner.Runner,org.junit.runners.model.FrameworkMethod, org.junit.runners.model.TestRunnerDecorator,org.junit.runners.model.RunNotifier。改写的目的是为了让测试程序在运行测试方法前,完成Mock 注解API(@Mocked,@Injectable,@Capturing)修饰的测试属性&测试参数的类做相关字节码的织入。
详见可以见JMockit源代码中Runner类,FakeFrameworkMethod类,JUnit4TestRunnerDecorator类,RunNotifierDecorator类。
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)修饰的测试属性&测试参数的类做相关字节码的织入。
- 字节码处理
通过ASM,在类的某个方法中加入某段逻辑以达到Mock的目的;生成某个类的子类以支持抽象类的Mock;生成某个接口的实例类以支持接口的Mock。通过ASM, 这些都变得不那么复杂了。
- 类转换器
类转换器是JMockit的核心。Mock的核心就是JMockit不同的类转换器在起作用。
a)录制(ExpectationsTransformer):用于对new Expectations(){{}},new Verifications(){{}},匿名类进行重定义。用于支持测试程序中的录制,重放,校验。
b)伪类(ClassLoadingBridgeFields): 伪类,即new MockUp {}的匿名类或 extends MockUp的子类。用于伪类的@Mock方法提供支持。 通过识别伪类@Mock方法,在对应的方法体中织入一段分支,用于走伪类的@Mock方法逻辑。
c)覆盖率(CodeCoverage):用于支持JMockit Coverage功能。 通过在类的方法体行加埋点。即可以完成行覆盖率,路径覆盖率的计算。
d)类缓存(CachedClassfiles): 这个没有什么好说的,对类进行了重定义,当然要求一个测试方法结束后,能复原类的原有字节码,于是需要一个Cache了。
e)对象捕捉(CaptureTransformer): 用于支持JMockit的withCapture()功能,即捕捉某次测试中,某个类的某个方法的入参是什么,并记录下来。通常用于在验证代码块中,某个方法的入参是否符合期望。
- Mock API
@Mocked, @Tested ,@Injectable, @Capturing, MockUp, @Mock ,Expectations, Verifications这些API,通过前面基础知识,常见用法等的学习,这些API已经耳熟能详了吧。 基本能满足大部分的Mock场景了。
JUnit5 + JMockit 知识整理相关推荐
- 单元测试实践思考(junit5+jmockit+testcontainer)
文章目录 背景 方案设计 单元测试指导思想 单层隔离 内部穿透 技术实现 依赖管理 基础架构 封装Junit5&Jmockit 单元测试配置 TestContainer封装 官方方案 实际方案 ...
- python常用变量名_python基础知识整理
Python Python开发 Python语言 python基础知识整理 序言:本文简单介绍python基础知识的一些重要知识点,用于总结复习,每个知识点的具体用法会在后面的博客中一一补充程序: 一 ...
- Spring AOP 知识整理
为什么80%的码农都做不了架构师?>>> AOP知识整理 面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足.在OOP中模块化的关键单元 ...
- Linux系统基础知识整理
一.说明 本篇文章,我将结合自己的实践以及简介,来对linux系统做一个直观清晰的介绍,使得哪些刚接触Linux的小伙伴可以快速入门,也方便自己以后进行复习查阅. 二.基本知识整理 1.Linux文件 ...
- 计算机二级c语基础知识,计算机二级C语基础知识整理.doc
计算机二级C语基础知识整理 1.1 算法 算法:是一组有穷指令集,是解题方案的准确而完整的描述.通俗地说,算法就是计算机解题的过程.算法不等于程序,也不等于计算方法,程序的编制不可能优于算法的设计. ...
- js事件(Event)知识整理
鼠标事件 鼠标移动到目标元素上的那一刻,首先触发mouseover 之后如果光标继续在元素上移动,则不断触发mousemove 如果按下鼠标上的设备(左键,右键,滚轮--),则触发mousedow ...
- Spring学习篇:IoC知识整理(一)
现在正通过spring的官方文档学习spring,将自己学习时的点点滴滴记录下来. Ioc知识整理(一): IoC (Inversion of Control) 控制反转. 1.bean的别名 我们每 ...
- 使用Aspose.Cells的基础知识整理
使用Aspose.Cells的基础知识整理 转自 http://www.cnblogs.com/kenblove/archive/2009/01/07/1371104.html 这两天用Aspose. ...
- 前端基础知识整理汇总(中)
前端基础知识整理汇总(中) Call, bind, apply实现 // call Function.prototype.myCall = function (context) {context = ...
- 前端基础知识整理汇总(上)
前端基础知识整理汇总(上) HTML页面的生命周期 HTML页面的生命周期有以下三个重要事件: 1.DOMContentLoaded -- 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是 ...
最新文章
- mysql winxp 本地 10061 错误
- 第三十一篇:SOUI布局之相对于特定兄弟窗口
- 深入理解分布式技术 - 消息幂等性如何保障不重复消费
- Hadoop每日一讨论整理版
- Springboot搭建个人博客系列
- LeetCode——排序
- Python(8):模块内置变量
- Yarn application has already ended! It might have been killed or unable to launch application master
- 量子计算机到底多强大?从量子运算看清楚它们的能力
- 如何使用JavaScript检查输入是否为空
- 第七天Python学习记录
- 多种语言签名代码实现
- SM3算法对大文件做摘要
- 人人商城删除后台菜单“小程序”
- DS18B20驱动详解(蓝桥杯单片机)
- 自家主机建云服务器_是用云主机还是自己建服务器好?
- Maven Failsafe 插件
- 学python看小甲鱼还是黑马_为什么我看完小甲鱼的python视频还是不会写呢?
- 预约制成为汽车年检新常态
- 2021不堪回首,2022满路荆棘,但依然乐观努力