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有如下核心组件
- JVM Attach
JMockit使用了JDK6动态添加代理功能。目的是为了运行JMockit启动程序做准备。 JMockit提供了不同OS的hotSpot JVM的Attach支持: BsdVirtualMachine, LinuxVirtualMachine,SolarisVirtualMachine,WindowsVirtualMachine。
JMockit启动程序:主要功能是集成测试框架(JUnit/TestNG),完成对JMockit类转换器织入。
- 测试框架集成
提供了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)修饰的测试属性&测试参数的类做相关字节码的织入。
- 字节码处理
通过ASM,在类的某个方法中加入某段逻辑以达到Mock的目的;生成某个类的子类以支持抽象类的Mock;生成某个接口的实例类以支持接口的Mock。通过ASM, 这些都变得不那么复杂了。
- 类转换器
类转换器是JMockit的核心。Mock的核心就是JMockit不同的类转换器在起作用。
- Mock API
@Mocked, @Tested ,@Injectable, @Capturing, MockUp, @Mock ,Expectations, Verifications这些API,通过前面基础知识,常见用法等的学习,这些API已经耳熟能详了吧。 基本能满足大部分的Mock场景了。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224968.html原文链接:https://javaforall.net
