干练的稀饭 · 三种不配置tnsnames.ora的另类sq ...· 3 周前 · |
冷静的猴子 · Selenium + Chrome ...· 6 月前 · |
爱跑步的沙滩裤 · UnionWithOperation ...· 8 月前 · |
温文尔雅的钥匙 · python学习之 实现QQ自动发送消息 - 简书· 1 年前 · |
建议:我个人是比较推荐单元测试与具体实现代码同步进行这个方案的。只有对需求有一定的理解后才能知道什么是代码的正确性,才能写出有效的单元测试来验证正确性,而能写出一些功能代码则说明对需求有一定理解了。
单元测试不是越多越好,而是越有效越好!进一步解读就是哪些代码需要有单元测试覆盖:
被测系统(System under test, SUT)表示正在被测试的系统, 目的是测试系统能否正确操作. 根据测试类型的不同, SUT 指代的内容也不同, 例如 SUT 可以是一个类甚至是一整个系统.
被测系统所依赖的组件, 例如进程 UserService 的单元测试时, UserService 会依赖 UserDao, 因此 UserDao 就是 DOC.
一个实际的系统会依赖多个外部对象, 但是在进行单元测试时, 我们会用一些功能较为简单的并且其行为和实际对象类似的假对象来作为 SUT 的依赖对象, 以此来降低单元测试的复杂性和可实现性. 在这里, 这些假对象就被称为 测试替身(Test Double). 测试替身有如下 5 种类型:
所谓 test fixture, 就是运行测试程序所需要的先决条件(precondition). 即对被测对象进行测试时锁需要的一切东西(The test fixture is everything we need to have in place to exercise the SUT). 这个 东西 不单单指的是数据, 同时包括对被测对象的配置, 被测对象所需要的依赖对象等. JUnit4 之前是通过 setUp, TearDown 方法完成, 在 JUnit4这, 我们可以使用@Before 代替 setUp 方法, @After 代替 tearDown 方法.
注意, @Before 在每个测试方法运行前都会被调用, @After 在每个测试方法运行后都会被调用.
因为 @Before 和 @After 会在每个测试方法前后都会被调用, 而有时我们仅仅需要在测试前进行一次初始化, 这样的情况下, 可以使用@BeforeClass 和@AfterClass 注解.
在 JUnit 3中, 测试方法都必须以 test 为前缀, 且必须是 public void 的, JUnit 4之后, 就没有这个限制了, 只要在每个测试方法标注 @Test 注解, 方法签名可以是任意的.
通过 TestSuit 对象将多个测试用例组装成一个测试套件, 测试套件批量运行.
通过@RunWith 和@SuteClass 两个注解, 我们可以创建一个测试套件. 通过@RunWith 指定一个特殊的运行器, Suite.class 套件运行器, 并通过@SuiteClasses 注解, 将需要进行测试的类列表作作为参数传入
JUint是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。
JUnit 是一个开放的资源框架,用于编写和运行测试。
最好的资料依然在Junit官方网站,以下我帮你总结下Junit相关的官方网址。@pdai
在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有的测试类必须继承junit的测试基类。在junit4中,定义一个测试方法变得简单很多,只需要在方法前加上@Test就行了。
注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。
有时候我们想暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。在运行结果中,junit会统计忽略的用例数,来提醒你。但是不建议经常这么做,因为这样的坏处时,容易忘记去更新这些测试方法,导致代码不够干净,用例遗漏。使用此标注的时候不能与其它标注一起使用,如:和@Test 标注一起使用,那就没用了
当我们运行几个有关联的用例时,可能会在数据准备或其它前期准备中执行一些相同的命令,这个时候为了让代码更清晰,更少冗余,可以将公用的部分提取出来,放在一个方法里,并为这个方法注解@BeforeClass。意思是在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。
注意:方法名可以任意,但必须是public static void,即公开、静态、无返回。这个方法只会运行一次。
跟@BeforeClass对应,在测试类里所有用例运行之后,运行一次。用于处理一些测试后续工作,例如清理数据,恢复现场。
注意:同样必须是public static void,即公开、静态、无返回。这个方法只会运行一次。
与@BeforeClass的区别在于,@Before不止运行一次,它会在每个用例运行之前都运行一次。主要用于一些独立于用例之间的准备工作。
比如两个用例都需要读取数据库里的用户A信息,但第一个用例会删除这个用户A,而第二个用例需要修改用户A。那么可以用@BeforeClass创建数据库连接。用@Before来插入一条用户A信息。
注意:必须是public void,不能为static。不止运行一次,根据用例数而定。
<?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>java-junit4</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
测试:Hello World
package tech.pdai.junit4;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
* Hello world test.
* @author pdai
public class HelloWorldTest {
@Test
public void firstTest() {
assertEquals(2, 1 + 1);
@Test注解在方法上标记方法为测试方法,以便构建工具和 IDE 能够识别并执行它们。JUnit 4 需要测试方法为public,这和Junit 5 有差别。
测试:生命周期
测试:生命周期
@BeforeClass注解修饰的方法(该方法要用static修饰)会在所有方法运行前被执行,且只执行一次,通常用来为后面测试方法的准备工作,如加载配置、进行数据库的连接等。父类的@BeforeClass注解方法会在子类的@BeforeClass注解方法执行前执行。
@Before注解修饰的方法会在每个测试方法执行前执行一次,父类@Before修饰的方法会在子类@Before修饰的方法执行前 执行
@After注解修饰的方法会在每个测试方法执行后执行一次,父类@After修饰的方法会在子类@After修饰的方法执行后执行。
@AfterClass注解修饰的方法(该方法要用static修饰)会在所有方法执行结束后执行一次,且也只执行一次,通常用来对资源进行释放,比如数据库连接的关闭等,无论测试用例里的其他方法有没有抛出异常,该方法最终都会被执行。而且父类中的被@AfterClass注解方法修饰的方法会在子类的@AfterClass注解修饰的方法执行之后才会被执行。
package tech.pdai.junit4;
import org.junit.*;
* Standard Test.
public class StandardTest {
@BeforeClass
public static void beforeClass() {
System.out.println("in before class");
@AfterClass
public static void afterClass() {
System.out.println("in after class");
@Before
public void before() {
System.out.println("in before");
@After
public void after() {
System.out.println("in after");
@Test
public void testCase1() {
System.out.println("in test case 1");
@Test
public void testCase2() {
System.out.println("in test case 2");
测试:禁用测试
@Ignore:暂不执行该方法;
package tech.pdai.junit4;
import org.junit.Ignore;
import org.junit.Test;
* Ignore Test.
public class IgnoreTest {
* ignore.
@Ignore
@Test
public void ignoreTest(){
System.out.println("ignore test");
测试:断言测试
断言测试注解有哪些
断言 描述 void assertEquals([String message],expected value,actual value) 断言两个值相等。值类型可能是int,short,long,byte,char,Object,第一个参数是一个可选字符串消息 void assertTrue([String message],boolean condition) 断言一个条件为真 void assertFalse([String message],boolean condition) 断言一个条件为假 void assertNotNull([String message],java.lang.Object object) 断言一个对象不为空(null) void assertNull([String message],java.lang.Object object) 断言一个对象为空(null) void assertSame([String message],java.lang.Object expected,java.lang.Object actual) 断言两个对象引用相同的对象 void assertNotSame([String message],java.lang.Object unexpected,java.lang.Object actual) 断言两个对象不是引用同一个对象 void assertArrayEquals([String message],expectedArray,resultArray) 断言预期数组和结果数组相等,数组类型可能是int,short,long,byte,char,Object
package tech.pdai.junit4;
import org.junit.Assert;
import org.junit.Test;
* Assertion Test.
public class AssertionTest {
@Test
public void test() {
String obj1 = "junit";
String obj2 = "junit";
String obj3 = "test";
String obj4 = "test";
String obj5 = null;
int var1 = 1;
int var2 = 2;
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
Assert.assertEquals(obj1, obj2);
Assert.assertSame(obj3, obj4);
Assert.assertNotSame(obj2, obj4);
Assert.assertNotNull(obj1);
Assert.assertNull(obj5);
Assert.assertTrue(var1 < var2);
Assert.assertFalse(var1 > var2);
Assert.assertArrayEquals(array1, array2);
更多测试,来自官网github.com/junit-team/…
package tech.pdai.junit4;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
import java.util.Arrays;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
* More Assertion Test from Junit-Team.
public class Assertion2Test {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
@Test
public void testAssertEquals() {
assertEquals("failure - strings are not equal", "text", "text");
@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
@Test
public void testAssertNull() {
assertNull("should be null", null);
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
@Test
public void testAssertThatHasItems() {
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
@Test
public void testAssertThatEveryItemContainsString() {
assertThat(Arrays.asList(new String[]{"fun", "ban", "net"}), everyItem(containsString("n")));
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer>either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
@Test
public void testAssertTrue() {
assertTrue("failure - should be true", true);
测试:异常测试
Junit 用代码处理提供了一个追踪异常的选项。你可以测试代码是否它抛出了想要得到的异常。expected 参数和 @Test 注释一起使用。现在让我们看看 @Test(expected):
package tech.pdai.junit4;
import org.junit.Test;
* Exception Test.
public class ExceptionTest {
@Test(expected = ArithmeticException.class)
public void exceptionTest() {
System.out.println("in exception success test");
int a = 0;
int b = 1 / a;
@Test(expected = NullPointerException.class)
public void exceptionFailTest() {
System.out.println("in exception fail test");
int a = 0;
int b = 1 / a;
测试:时间测试
JUnit提供了一个暂停的方便选项,如果一个测试用例比起指定的毫秒数花费了更多的时间,那么JUnit将自动将它标记为失败,timeout参数和@Test注解一起使用,例如@Test(timeout=1000)。
package tech.pdai.junit4;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
* Timeout Test.
public class TimeoutTest {
@Test(timeout = 1000)
public void testCase1() throws InterruptedException {
TimeUnit.SECONDS.sleep(5000);
System.out.println("in timeout exception");
应用到测试类的所有测试用例
package tech.pdai.junit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
* Timeout Rule.
public class HasGlobalTimeoutTest {
public static String log;
private final CountDownLatch latch = new CountDownLatch(1);
@Rule
public Timeout globalTimeout = Timeout.seconds(10); // 10 seconds max per method tested
@Test
public void testSleepForTooLong() throws Exception {
log += "ran1";
TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
@Test
public void testBlockForever() throws Exception {
log += "ran2";
latch.await(); // will block
测试:参数化测试
Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同 一个测试。你将遵循 5 个步骤来创建参数化测试:
为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
为测试类声明几个变量,分别用于存放期望值和测试所用数据。
为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
编写测试方法,使用定义的变量作为参数进行测试
package tech.pdai.junit4;
* PrimeNumberChecker.
public class PrimeNumberChecker {
public Boolean validate(final Integer parimeNumber) {
for (int i = 2; i < (parimeNumber / 2); i++) {
if (parimeNumber % i == 0) {
return false;
return true;
package tech.pdai.junit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
* Parameterized Test.
@RunWith(Parameterized.class) // 步骤一: 指定定参数运行器
public class PrimeNumberCheckerTest {
* 步骤二:声明变量
private Integer inputNumber;
private Boolean expectedResult;
private PrimeNumberChecker primeNumberChecker;
* 步骤三:为测试类声明一个带有参数的公共构造函数,为变量赋值
public PrimeNumberCheckerTest(Integer inputNumber,
Boolean expectedResult) {
this.inputNumber = inputNumber;
this.expectedResult = expectedResult;
* 步骤四:为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为
* java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对
* 1)该方法必须由Parameters注解修饰
2)该方法必须为public static的
3)该方法必须返回Collection类型
4)该方法的名字不做要求
5)该方法没有参数
@Parameterized.Parameters
public static Collection primeNumbers() {
return Arrays.asList(new Object[][]{
{2, true},
{6, false},
{19, true},
{22, false},
{23, true}
@Before
public void initialize() {
primeNumberChecker = new PrimeNumberChecker();
* 步骤五:编写测试方法,使用自定义变量进行测试
@Test
public void testPrimeNumberChecker() {
System.out.println("Parameterized Number is : " + inputNumber);
Assert.assertEquals(expectedResult,
primeNumberChecker.validate(inputNumber));
测试:套件测试
“套件测试”是指捆绑了几个单元测试用例并运行起来。在JUnit中,@RunWith 和 @Suite 这两个注解是用来运行套件测试。先来创建几个测试类
package tech.pdai.junit4.testsuite;
import org.junit.Test;
public class JunitTest1 {
@Test
public void printMessage(){
System.out.println("in JunitTest1");
package tech.pdai.junit4.testsuite;
import org.junit.Test;
public class JunitTest2 {
@Test
public void printMessage(){
System.out.println("in JunitTest2");
package tech.pdai.junit4.testsuite;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
* Test suite.
@RunWith(Suite.class)
@Suite.SuiteClasses({
* 此处类的配置顺序会影响执行顺序
JunitTest1.class,
JunitTest2.class
public class JunitSuiteTest {
测试:测试顺序
自定义测试方法的顺序,比如按照方法的名字顺序:
package tech.pdai.junit4;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
* Order.
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestMethodOrder {
@Test
public void testA() {
System.out.println("first");
@Test
public void testC() {
System.out.println("third");
@Test
public void testB() {
System.out.println("second");
单元测试 - Junit5 详解
JUnit 5是JUnit的下一代。目标是为JVM上的开发人员端测试创建一个最新的基础。这包括专注于Java 8及更高版本,以及启用许多不同风格的测试。
最好的资料依然在Junit官方网站,以下我帮你总结下Junit相关的官方网址。@pdai
官方入门文档
官方github
Junit5的架构
与以前版本的JUnit不同,JUnit 5由三个不同子项目中的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform是基于JVM的运行测试的基础框架在,它定义了开发运行在这个测试框架上的TestEngine API。此外该平台提供了一个控制台启动器,可以从命令行启动平台,可以为Gradle和 Maven构建插件,同时提供基于JUnit 4的Runner。
JUnit Jupiter是在JUnit 5中编写测试和扩展的新编程模型和扩展模型的组合.Jupiter子项目提供了一个TestEngine在平台上运行基于Jupiter的测试。
JUnit Vintage提供了一个TestEngine在平台上运行基于JUnit 3和JUnit 4的测试。
架构图如下:
JUnit Jupiter API 的使用
JUnit Jupiter是在JUnit 5中编写测试和扩展的新编程模型和扩展模型的组合; 所以我们使用Jupiter来学习Junit5。
@Test 表示方法是一种测试方法。 与JUnit 4的@Test注解不同,此注释不会声明任何属性。
@ParameterizedTest 表示方法是参数化测试
@RepeatedTest 表示方法是重复测试模板
@TestFactory 表示方法是动态测试的测试工程
@DisplayName 为测试类或者测试方法自定义一个名称
@BeforeEach 表示方法在每个测试方法运行前都会运行 , @AfterEach 表示方法在每个测试方法运行之后都会运行
@BeforeAll 表示方法在所有测试方法之前运行 , @AfterAll 表示方法在所有测试方法之后运行
@Nested 表示带注解的类是嵌套的非静态测试类, @BeforeAll和 @AfterAll方法不能直接在@Nested测试类中使用,除非修改测试实例生命周期。
@Tag 用于在类或方法级别声明用于过滤测试的标记
@Disabled 用于禁用测试类或测试方法
@ExtendWith 用于注册自定义扩展,该注解可以继承
@FixMethodOrder(MethodSorters.NAME_ASCENDING) ,控制测试类中方法执行的顺序,这种测试方式将按方法名称的进行排序,由于是按字符的字典顺序,所以以这种方式指定执行顺序会始终保持一致;不过这种方式需要对测试方法有一定的命名规则,如 测试方法均以testNNN开头(NNN表示测试方法序列号 001-999)
编写单元测试
接下来,我们开始学习JUnit5单元测试实例:
Maven包引入
最新的包引入,请参考这里:junit.org/junit5/docs…
<?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>pdai.tech</groupId>
<artifactId>java-junit5</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
</project>
测试:Hello World
第一个测试:
package tech.pdai.junit5;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
* Hello world test.
* @author pdai
public class HelloWorldTest {
@Test
void firstTest() {
assertEquals(2, 1 + 1);
@Test注解在方法上标记方法为测试方法,以便构建工具和 IDE 能够识别并执行它们。JUnit 5不再需要手动将测试类与测试方法为public,包可见的访问级别就足够了。
测试:生命周期
首先,需要对比下Junit5和Junit4注解:
Junit4 Junit5 注释 @Test @Test 表示该方法是一个测试方法 @BeforeClass @BeforeAll 表示使用了该注解的方法应该在当前类中所有测试方法之前执行(只执行一次),并且它必须是 static方法(除非@TestInstance指定生命周期为Lifecycle.PER_CLASS) @AfterClass @AfterAll 表示使用了该注解的方法应该在当前类中所有测试方法之后执行(只执行一次),并且它必须是 static方法(除非@TestInstance指定生命周期为Lifecycle.PER_CLASS) @Before @BeforeEach 表示使用了该注解的方法应该在当前类中每一个测试方法之前执行 @After @AfterEach 表示使用了该注解的方法应该在当前类中每一个测试方法之后执行 @Ignore @Disabled 用于禁用(或者说忽略)一个测试类或测试方法 @Category @Tag 用于声明过滤测试的tag标签,该注解可以用在方法或类上
测试用例:
package tech.pdai.junit5;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
* Standard Test.
* @author pdai
public class StandardTest {
@BeforeAll
static void initAll() {
System.out.println("BeforeAll");
@BeforeEach
void init() {
System.out.println("BeforeEach");
@Test
void succeedingTest() {
System.out.println("succeedingTest");
@Test
void failingTest() {
System.out.println("failingTest");
fail("a failing test");
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
@Test
void abortedTest() {
System.out.println("abortedTest");
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
@AfterEach
void tearDown() {
System.out.println("AfterEach");
@AfterAll
static void tearDownAll() {
System.out.println("AfterEach");
测试:禁用测试
这是一个禁用的测试案例:
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled
class DisabledClassTest {
@Test
void testWillBeSkipped() {
这是一个带有禁用测试方法的测试案例:
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class DisabledTest {
@Disabled
@Test
void testWillBeSkipped() {
@Test
void testWillBeExecuted() {
测试:断言测试
准备好测试实例、执行了被测类的方法以后,断言能确保你得到了想要的结果。一般的断言,无非是检查一个实例的属性(比如,判空与判非空等),或者对两个实例进行比较(比如,检查两个实例对象是否相等)等。无论哪种检查,断言方法都可以接受一个字符串作为最后一个可选参数,它会在断言失败时提供必要的描述信息。如果提供出错信息的过程比较复杂,它也可以被包装在一个 lambda 表达式中,这样,只有到真正失败的时候,消息才会真正被构造出来。
常用断言 Assertions
import tech.pdai.junit5.entity.Person;
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.*;
* Assertions Test.
public class AssertionsTest {
Person person = new Person("John", "Doe");
@Test
void standardAssertions() {
assertEquals(2, 2);
assertEquals(4, 4, "The optional assertion message is now the last parameter.");
assertTrue(2 == 2, () -> "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily.");
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and any
// failures will be reported together.
assertAll("person",
() -> assertEquals("John", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
@Test
void dependentAssertions() {
// Within a code block, if an assertion fails the
// subsequent code in the same block will be skipped.
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
// Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("n"))
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName);
// Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
assertEquals("a message", exception.getMessage());
@Test
void timeoutNotExceeded() {
// The following assertion succeeds.
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
@Test
void timeoutNotExceededWithResult() {
// The following assertion succeeds, and returns the supplied object.
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "a result";
assertEquals("a result", actualResult);
@Test
void timeoutNotExceededWithMethod() {
// The following assertion invokes a method reference and returns an object.
String actualGreeting = assertTimeout(ofMinutes(2), AssertionsTest::greeting);
assertEquals("hello world!", actualGreeting);
@Test
void timeoutExceeded() {
// The following assertion fails with an error message similar to:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
@Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
private static String greeting() {
return "hello world!";
这里注意下:assertTimeoutPreemptively() 和 assertTimeout() 的区别为: 两者都是断言超时,前者在指定时间没有完成任务就会立即返回断言失败;后者会在任务执行完毕之后才返回。
执行结果:
测试:异常测试
我们代码中对于带有异常的方法通常都是使用 try-catch 方式捕获处理,针对测试这样带有异常抛出的代码,而 JUnit 5 提供方法 Assertions#assertThrows(Class, Executable) 来进行测试,第一个参数为异常类型,第二个为函数式接口参数,跟 Runnable 接口相似,不需要参数,也没有返回,并且支持 Lambda表达式方式使用,具体使用方式可参考下方代码:
package tech.pdai.junit5;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
* Exception Test.
public class ExceptionTest {
// 标准的测试例子
@Test
@DisplayName("Exception Test Demo")
void assertThrowsException() {
String str = null;
assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
// 注:异常失败例子,当Lambda表达式中代码出现的异常会跟首个参数的异常类型进行比较,如果不属于同一类异常,则失败
@Test
@DisplayName("Exception Test Demo2")
void assertThrowsException2() {
String str = null;
assertThrows(NullPointerException.class, () -> {
Integer.valueOf(str);
测试:嵌套测试
嵌套测试给测试编写者更多的能力,来表达几组测试之间的关系。这里有一个详细的例子。
用于测试stack的嵌套测试套件:
package tech.pdai.junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.EmptyStackException;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
* Stack test for Nest Demo.
@DisplayName("A stack")
public class NestedTest {
Stack stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, () -> stack.pop());
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, () -> stack.peek());
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
测试:重复测试
JUnit Jupiter通过使用@RepeatedTest注解方法并指定所需的重复次数,提供了重复测试指定次数的功能。每次重复测试的调用都像执行常规的@Test方法一样,完全支持相同的生命周期回调和扩展。
以下示例演示了如何声明名为repeatedTest()的测试,该测试将自动重复10次。
@RepeatedTest(10)
void repeatedTest() {
// ...
除了指定重复次数外,还可以通过@RepeatedTest注解的name属性为每次重复配置自定义显示名称。此外,显示名称可以是模式,由静态文本和动态占位符的组合而成。目前支持以下占位符:
{displayName}: @RepeatedTest方法的显示名称
{currentRepetition}: 当前重复次数
{totalRepetitions}: 重复的总次数
package tech.pdai.junit5;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
* Repeat Test.
public class RepeatTest {
@BeforeEach
void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition();
int totalRepetitions = repetitionInfo.getTotalRepetitions();
String methodName = testInfo.getTestMethod().get().getName();
System.out.println(String.format("About to execute repetition %d of %d for %s", //
currentRepetition, totalRepetitions, methodName));
@RepeatedTest(3)
void repeatedTest() {
// ...
@RepeatedTest(2)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
assertEquals(2, repetitionInfo.getTotalRepetitions());
@RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
void customDisplayName(TestInfo testInfo) {
assertEquals(testInfo.getDisplayName(), "Repeat! 1/1");
@RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
@DisplayName("Details...")
void customDisplayNameWithLongPattern(TestInfo testInfo) {
assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1");
@RepeatedTest(value = 2, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
void repeatedTestInGerman() {
// ...
测试:参数化测试
JUnit Jupiter开箱即用,提供了不少source注解。下面的每个小节都为他们提供了简要的概述和示例。请参阅org.junit.jupiter.params.provider包中的JavaDoc以获取更多信息。
@ValueSource
@ValueSource是最简单的source之一。它可以让你指定一个原生类型(String,int,long或double)的数组,并且只能为每次调用提供一个参数。
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertNotNull(argument);
@EnumSource
@EnumSource提供了一个使用Enum常量的简便方法。该注释提供了一个可选的name参数,可以指定使用哪些常量。如果省略,所有的常量将被用在下面的例子中。
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit);
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(TimeUnit timeUnit) {
assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
@EnumSource注解还提供了一个可选的mode参数,可以对将哪些常量传递给测试方法进行细化控制。例如,您可以从枚举常量池中排除名称或指定正则表达式,如下例所示。
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = EXCLUDE, names = { "DAYS", "HOURS" })
void testWithEnumSourceExclude(TimeUnit timeUnit) {
assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));
assertTrue(timeUnit.name().length() > 5);
@ParameterizedTest
@EnumSource(value = TimeUnit.class, mode = MATCH_ALL, names = "^(M|N).+SECONDS$")
void testWithEnumSourceRegex(TimeUnit timeUnit) {
String name = timeUnit.name();
assertTrue(name.startsWith("M") || name.startsWith("N"));
assertTrue(name.endsWith("SECONDS"));
@MethodSource
@MethodSource允许你引用一个或多个测试类的工厂方法。这样的方法必须返回一个Stream,Iterable,Iterator或者参数数组。另外,这种方法不能接受任何参数。默认情况下,除非测试类用@TestInstance(Lifecycle.PER_CLASS)注解,否则这些方法必须是静态的。
如果只需要一个参数,则可以返回参数类型的实例Stream,如以下示例所示。
@ParameterizedTest
@MethodSource("stringProvider")
void testWithSimpleMethodSource(String argument) {
assertNotNull(argument);
static Stream<String> stringProvider() {
return Stream.of("foo", "bar");
支持原始类型(DoubleStream,IntStream和LongStream)的流,示例如下:
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
assertNotEquals(9, argument);
static IntStream range() {
return IntStream.range(0, 20).skip(10);
如果测试方法声明多个参数,则需要返回一个集合或Arguments实例流,如下所示。请注意,Arguments.of(Object…)是Arguments接口中定义的静态工厂方法。
@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List<String> list) {
assertEquals(3, str.length());
assertTrue(num >=1 && num <=2);
assertEquals(2, list.size());
static Stream<Arguments> stringIntAndListProvider() {
return Stream.of(
Arguments.of("foo", 1, Arrays.asList("a", "b")),
Arguments.of("bar", 2, Arrays.asList("x", "y"))
@CsvSource
@CsvSource允许您将参数列表表示为以逗号分隔的值(例如,字符串文字)。
@ParameterizedTest
@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })
void testWithCsvSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
@CsvSource使用'作为转义字符。 请参阅上述示例和下表中的’baz, qux’值。 一个空的引用值''会导致一个空的String; 而一个完全空的值被解释为一个null引用。如果null引用的目标类型是基本类型,则引发ArgumentConversionException。
示例输入 结果字符列表 @CsvSource({ “foo, bar” }) "foo", "bar" @CsvSource({ “foo, ‘baz, qux’” }) "foo", "baz, qux" @CsvSource({ “foo, ‘’” }) "foo", "" @CsvSource({ “foo, “ }) "foo", null
@CsvFileSource
@CsvFileSource让你使用classpath中的CSV文件。CSV文件中的每一行都会导致参数化测试的一次调用。
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv")
void testWithCsvFileSource(String first, int second) {
assertNotNull(first);
assertNotEquals(0, second);
two-column.csv
foo, 1
bar, 2
"baz, qux", 3
与@CsvSource中使用的语法相反,@CsvFileSource使用双引号"作为转义字符,请参阅上面例子中的"baz, qux"值,一个空的转义值""会产生一个空字符串, 一个完全为空的值被解释为null引用,如果null引用的目标类型是基本类型,则引发ArgumentConversionException。
@ArgumentsSource
可以使用@ArgumentsSource指定一个自定义的,可重用的ArgumentsProvider。
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
static class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream< ? extends Arguments > provideArguments(ExtensionContext context) {
return Stream.of("foo", "bar").map(Arguments::of);
测试:动态测试
除了这些标准测试外,JUnit Jupiter还引入了一种全新的测试编程模型。这种新的测试是动态测试,它是由 @TestFactory 注解的工厂方法在运行时生成的。
与@Test方法相比,@TestFactory方法本身不是测试用例,而是测试用例的工厂。因此,动态测试是工厂的产物。从技术上讲,@TestFactory方法必须返回DynamicNode实例的Stream,Collection,Iterable或Iterator。 DynamicNode的可实例化的子类是DynamicContainer和DynamicTest。 DynamicContainer实例由一个显示名称和一个动态子节点列表组成,可以创建任意嵌套的动态节点层次结构。然后,DynamicTest实例将被延迟执行,从而实现测试用例的动态甚至非确定性生成。
任何由@TestFactory返回的Stream都要通过调用stream.close()来正确关闭,使得使用诸如Files.lines()之类的资源变得安全。
与@Test方法一样,@TestFactory方法不能是private或static,并且可以选择声明参数,以便通过ParameterResolvers解析。
DynamicTest是运行时生成的测试用例。它由显示名称和Executable组成。 Executable是@FunctionalInterface,这意味着动态测试的实现可以作为lambda表达式或方法引用来提供。
package tech.pdai.junit5;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
* Dynamic Test.
public class DynamicsTest {
// This will result in a JUnitException!
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(true)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () -> assertTrue(true)),
dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))
@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () -> assertTrue(true)),
dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))
).iterator();
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
return Stream.of("A", "B", "C")
.map(str -> dynamicTest("test" + str, () -> { /* ... */ }));
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTests() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
@Override
public Integer next() {
return current;
// Generates display names like: input:5, input:37, input:85, etc.
Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
@TestFactory
Stream<DynamicNode> dynamicTestsWithContainers() {
return Stream.of("A", "B", "C")
.map(input -> dynamicContainer("Container " + input, Stream.of(
dynamicTest("not null", () -> assertNotNull(input)),
dynamicContainer("properties", Stream.of(
dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
单元测试 - Mockito 详解
Mock通常是指,在测试一个对象A时,我们构造一些假的对象来模拟与A之间的交互,而这些Mock对象的行为是我们事先设定且符合预期。通过这些Mock对象来测试A在正常逻辑,异常逻辑或压力情况下工作是否正常。而Mockito是最流行的Java mock框架之一。
什么是 Mock 测试
Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
先来看看下面这个示例:
从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks
从图中可以清晰的看出, mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。
Mock 适用在什么场景
在使用Mock的过程中,发现Mock是有一些通用性的,对于一些应用场景,是非常适合使用Mock的:
真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
真实对象很难被创建(比如具体的web容器)
真实对象的某些行为很难触发(比如网络错误)
真实情况令程序的运行速度很慢
真实对象有用户界面
测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
当然,也有一些不得不Mock的场景:
一些比较难构造的Object:这类Object通常有很多依赖,在单元测试中构造出这样类通常花费的成本太大。
执行操作的时间较长Object:有一些Object的操作费时,而被测对象依赖于这一个操作的执行结果,例如大文件写操作,数据的更新等等,出于测试的需求,通常将这类操作进行Mock。
异常逻辑:一些异常的逻辑往往在正常测试中是很难触发的,通过Mock可以人为的控制触发异常逻辑。
在一些压力测试的场景下,也不得不使用Mock,例如在分布式系统测试中,通常需要测试一些单点(如namenode,jobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量),这时候就需要应用Mock去满足。
Mockito
Mockito是最流行的Java mock框架之一.
Mockito 官方网站
PowerMockito Github
Maven包引入
<?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>pdai.tech</groupId>
<artifactId>java-mockito</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.7</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
测试:Hello World
本例子主要用来测试DemoService类,但是DemoService又依赖于DemoDao,这时候我们便可以mock出DemoDao的返回预期值,从而测试DemoService类。
待测试类DemoService
package tech.pdai.mockito.service;
import tech.pdai.mockito.dao.DemoDao;
public class DemoService {
private DemoDao demoDao;
public DemoService(DemoDao demoDao) {
this.demoDao = demoDao;
public int getDemoStatus(){
return demoDao.getDemoStatus();
依赖DemoDao
package tech.pdai.mockito.dao;
import java.util.Random;
public class DemoDao {
public int getDemoStatus(){
return new Random().nextInt();
package tech.pdai.mockito;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import tech.pdai.mockito.dao.DemoDao;
import tech.pdai.mockito.service.DemoService;
* Hello World Test.
public class HelloWorldTest {
@Test
public void helloWorldTest() {
// mock DemoDao instance
DemoDao mockDemoDao = Mockito.mock(DemoDao.class);
// 使用 mockito 对 getDemoStatus 方法打桩
Mockito.when(mockDemoDao.getDemoStatus()).thenReturn(1);
// 调用 mock 对象的 getDemoStatus 方法,结果永远是 1
Assert.assertEquals(1, mockDemoDao.getDemoStatus());
// mock DemoService
DemoService mockDemoService = new DemoService(mockDemoDao);
Assert.assertEquals(1, mockDemoService.getDemoStatus() );
测试:适用@Mock注解
@Mock 注解可以理解为对 mock 方法的一个替代。
使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效, 比如放在@Before方法中初始化。
比较优雅优雅的写法是用MockitoJUnitRunner,它可以自动执行MockitoAnnotations.initMocks 方法。
package tech.pdai.mockito;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Random;
import static org.mockito.Mockito.when;
* Mock Annotation
@RunWith(MockitoJUnitRunner.class)
public class MockAnnotationTest {
@Mock
private Random random;
@Test
public void test() {
when(random.nextInt()).thenReturn(100);
Assert.assertEquals(100, random.nextInt());
测试:参数匹配
如果参数匹配既申明了精确匹配,也声明了模糊匹配;又或者同一个值的精确匹配出现了两次,使用时会匹配符合匹配条件的最新声明的匹配。
package tech.pdai.mockito;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
* Mock Parameter Test.
@RunWith(MockitoJUnitRunner.class)
public class ParameterTest {
@Mock
private List<String> testList;
@Test
public void test01() {
// 精确匹配 0
when(testList.get(0)).thenReturn("a");
Assert.assertEquals("a", testList.get(0));
// 精确匹配 0
when(testList.get(0)).thenReturn("b");
Assert.assertEquals("b", testList.get(0));
// 模糊匹配
when(testList.get(anyInt())).thenReturn("c");
Assert.assertEquals("c", testList.get(0));
Assert.assertEquals("c", testList.get(1));
anyInt 只是用来匹配参数的工具之一,目前 mockito 有多种匹配函数,部分如下:
函数名 匹配类型 any() 所有对象类型 anyInt() 基本类型 int、非 null 的 Integer 类型 anyChar() 基本类型 char、非 null 的 Character 类型 anyShort() 基本类型 short、非 null 的 Short 类型 anyBoolean() 基本类型 boolean、非 null 的 Boolean 类型 anyDouble() 基本类型 double、非 null 的 Double 类型 anyFloat() 基本类型 float、非 null 的 Float 类型 anyLong() 基本类型 long、非 null 的 Long 类型 anyByte() 基本类型 byte、非 null 的 Byte 类型 anyString() String 类型(不能是 null) anyList() List 类型(不能是 null) anyMap() Map<K, V>类型(不能是 null) anyCollection() Collection类型(不能是 null) anySet() Set类型(不能是 null) any(Class type) type类型的对象(不能是 null) isNull() null notNull() 非 null isNotNull() 非 null
测试:Mock异常
Mockito 使用 thenThrow 让方法抛出异常
如下代码中,包含两个例子:一个是单个异常,一个是多个异常。
package tech.pdai.mockito;
import org.junit.Assert;
import org.junit.Test;
import java.util.Random;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
* Exception Test.
public class ThrowTest {
* 例子1: thenThrow 用来让函数调用抛出异常.
@Test
public void throwTest1() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));
try {
mockRandom.nextInt();
Assert.fail(); // 上面会抛出异常,所以不会走到这里
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常", ex.getMessage());
* thenThrow 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。
@Test
public void throwTest2() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));
try {
mockRandom.nextInt();
Assert.fail();
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常1", ex.getMessage());
try {
mockRandom.nextInt();
Assert.fail();
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常2", ex.getMessage());
对应返回类型是 void 的函数,thenThrow 是无效的,要使用 doThrow。
package tech.pdai.mockito;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.doThrow;
* Do Throw for void return.
@RunWith(MockitoJUnitRunner.class)
public class DoThrowTest {
static class ExampleService {
public void hello() {
System.out.println("Hello");
@Mock
private ExampleService exampleService;
@Test
public void test() {
// 这种写法可以达到效果
doThrow(new RuntimeException("异常")).when(exampleService).hello();
try {
exampleService.hello();
Assert.fail();
} catch (RuntimeException ex) {
Assert.assertEquals("异常", ex.getMessage());
此外还有,可以查看官方文档
doAnswer(Answer)
doNothing()
doCallRealMethod()
测试:spy 和 @Spy 注解
spy 和 mock不同,不同点是:
spy 的参数是对象示例,mock 的参数是 class。
被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
下面是一个对比:
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
public class MockitoDemo {
// 测试 spy
@Test
public void test_spy() {
ExampleService spyExampleService = spy(new ExampleService());
// 默认会走真实方法
Assert.assertEquals(3, spyExampleService.add(1, 2));
// 打桩后,不会走了
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
// 但是参数比匹配的调用,依然走真实方法
Assert.assertEquals(3, spyExampleService.add(2, 1));
// 测试 mock
@Test
public void test_mock() {
ExampleService mockExampleService = mock(ExampleService.class);
// 默认返回结果是返回类型int的默认值
Assert.assertEquals(0, mockExampleService.add(1, 2));
spy 对应注解 @Spy,和 @Mock 是一样用的。
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
public class MockitoDemo {
private ExampleService spyExampleService;
@Test
public void test_spy() {
MockitoAnnotations.initMocks(this);
Assert.assertEquals(3, spyExampleService.add(1, 2));
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
对于@Spy,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。
所以下面两种写法是等价的:
// 写法1
private ExampleService spyExampleService;
// 写法2
private ExampleService spyExampleService = new ExampleService();
如果没有无参构造函数,必须使用写法2。例子:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
class ExampleService {
private int a;
public ExampleService(int a) {
this.a = a;
int add(int b) {
return a+b;
public class MockitoDemo {
private ExampleService spyExampleService = new ExampleService(1);
@Test
public void test_spy() {
MockitoAnnotations.initMocks(this);
Assert.assertEquals(3, spyExampleService.add(2));
测试:测试隔离
根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。
比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
@Mock
private ExampleService exampleService;
@Test
public void test01() {
System.out.println("---call test01---");
System.out.println("打桩前: " + exampleService.add(1, 2));
when(exampleService.add(1, 2)).thenReturn(100);
System.out.println("打桩后: " + exampleService.add(1, 2));
@Test
public void test02() {
System.out.println("---call test02---");
System.out.println("打桩前: " + exampleService.add(1, 2));
when(exampleService.add(1, 2)).thenReturn(100);
System.out.println("打桩后: " + exampleService.add(1, 2));
将两个单测一起运行,运行结果是:
---call test01---
打桩前: 0
打桩后: 100
---call test02---
打桩前: 0
打桩后: 100
test01 先被执行,打桩前调用add(1, 2)的结果是0,打桩后是 100。
然后 test02 被执行,打桩前调用add(1, 2)的结果是0,而非 100,这证明了我们上面的说法。
测试:结合PowerMock支持静态方法
PowerMock 是一个增强库,用来增加 Mockito 、EasyMock 等测试库的功能。
Mockito为什么不能mock静态方法?
因为Mockito使用继承的方式实现mock的,用CGLIB生成mock对象代替真实的对象进行执行,为了mock实例的方法,你可以在subclass中覆盖它,而static方法是不能被子类覆盖的,所以Mockito不能mock静态方法。
但PowerMock可以mock静态方法,因为它直接在bytecode上工作。
Mockito 默认是不支持静态方法
比如我们在 ExampleService 类中定义静态方法 add:
public class ExampleService {
public static int add(int a, int b) {
return a+b;
尝试给静态方法打桩,会报错:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
@Test
public void test() {
// 会报错
when(ExampleService.add(1, 2)).thenReturn(100);
可以用 Powermock 弥补 Mockito 缺失的静态方法 mock 功能
在 pom.xml 中配置以下依赖:(版本的匹配问题可以参考:github.com/powermock/p…)
<properties>
<powermock.version>2.0.2</powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
@RunWith(PowerMockRunner.class) // 这是必须的
@PrepareForTest(ExampleService.class) // 声明要处理 ExampleService
public class MockitoDemo {
@Test
public void test() {
PowerMockito.mockStatic(ExampleService.class); // 这也是必须的
when(ExampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, ExampleService.add(1, 2));
Assert.assertEquals(0, ExampleService.add(2, 2));
PowerMockRunner 支持 Mockito 的 @Mock 等注解
上面我们用了 PowerMockRunner ,MockitoJUnitRunner 就不能用了。但不要担心, @Mock 等注解还能用。
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.Random;
import static org.mockito.Mockito.*;
@RunWith(PowerMockRunner.class)
public class MockitoDemo {
@Mock
private Random random;
@Test
public void test() {
when(random.nextInt()).thenReturn(1);
Assert.assertEquals(1, random.nextInt());
单元测试 - IDEA下单元测试详解
工欲善其事必先利其器,我们在写单元测试一定要使用工具,这将能大幅度提升编码的效率。本文以IDEA为例,看看如何利用插件提升效率。
准备一个待测试的类, 其中还包含着错误。
package tech.pdai.junit4.module;
public class Calculator {
public int result = 0;
* add.
* @param operand1 first param
* @param operand2 second param
* @return sum
public int add(int operand1, int operand2) {
result = operand1 + operand2;
return result;
public int subtract(int operand1, int operand2) {
result = operand1 - operand2;
return result;
public int multiple(int operand1, int operand2) {
result = operand1 * operand2;
for (; ; ) { //死循环
public int divide(int operand1, int operand2) {
result = operand1 / 0;
return result;
public int getResult() {
return this.result;
自动生成单元测试
第一个插件,首推的是JunitGeneratorV2.0
在大量的单元测试时,如何提升测试的效率呢?肯定是并行,所以你可以用如下的插件
JUnit4 Parallel Runner
代码覆盖率
如何快速看本地代码测试覆盖率呢?
代码覆盖率
单元测试 - SpringBoot2+H2+Mockito实战
在真实的开发中,我们通常是使用SpringBoot的,目前SpringBoot是v2.4.x的版本(SpringBoot 2.2.2.RELEASE之前默认是使用 JUnit4,之后版本默认使用Junit5);所以我们写个基于SpringBoot2.4+H2的内存库的简单例子,同时加点必要的单元测试。
为何H2会被用来做单元测试
一个 Junit单元测试的流程包括
初始化数据
在真实的测试代码开发中,有几类问题会造成困扰:
数据库环境的搭建
搭建一套完整的数据库往往比较耗时,然而一旦将数据库配置加入测试范围,就必须长期维护其稳定性;
这同时也会带来代码库同步的困扰。
保证数据库的"干净"
大多数情况下,每个测试用例在启动前(初始化数据)都期望数据库是"干净"的状态;然而使用真实的数据库却很难保证这点,原因是:
多个应用可能会共享一个物理数据库;
测试用例在销毁数据时很难保证完全清除,可能一次意外的调试也会产生垃圾数据;
H2内存数据库很好的解决了上述问题,本身作为嵌入式数据库并不需要额外的看护成本;在程序退出时,所有数据都能保证完全清除。
SpringBoot对单测试的差异
SpringBoot 2.2.2.RELEASE之前默认是使用 JUnit4,之后版本默认使用Junit5。
Springboot+junit4:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootQuickStartApplicationTests {
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
@Test
public void contextLoads() throws Exception {
RequestBuilder request = null;
request = MockMvcRequestBuilders.get("/")
.contentType(MediaType.APPLICATION_JSON);
mvc.perform(request)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
Springboot+junit5:
@SpringBootTest
// 使用spring的测试框架
@ExtendWith(SpringExtension.class)
class SpringbootQuickStartApplicationTests {
private MockMvc mockMvc;
@BeforeEach // 类似于junit4的@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
@Test
void contextLoads() throws Exception {
RequestBuilder request = null;
request = MockMvcRequestBuilders.get("/")
.contentType(MediaType.APPLICATION_JSON);
mockMvc.perform(request)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
Spring Boot 2.4.2 + H2 + Lombok + Spring Boot Test (默认包含了 Junit5 + Mockito)。
Demo程序准备
pom.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>tech.pdai</groupId>
<artifactId>java-springboot-unit5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>java-springboot-unit5</name>
<description>java-springboot-unit5</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
datasource:
platform: h2
driverClassName: org.h2.Driver
url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
username: pdai
password: pdai
schema: classpath:db/schema/user-schema.sql
data: classpath:db/data/user-data.sql
initialization-mode: always
console:
settings:
trace: true
web-allow-others: true
enabled: true
path: /h2-console
show-sql: true
hibernate:
ddl-auto: update
generate-ddl: false
open-in-view: false
数据库文件准备
schema
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(35),
phone varchar(35)
insert into user(id,name,phone) values(1,'pdai','123456');
insert into user(id,name,phone) values(2,'zhangsan','123456');
entity
package tech.pdai.springboot2unit5.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
* User.
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
public class User {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String phone;
package tech.pdai.springboot2unit5.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import tech.pdai.springboot2unit5.entity.User;
* user dao.
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
service
package tech.pdai.springboot2unit5.service;
import tech.pdai.springboot2unit5.entity.User;
import java.util.List;
* user service.
public interface IUserService {
* find all user.
* @return list
List<User> findAll();
package tech.pdai.springboot2unit5.service.impl;
import org.springframework.stereotype.Service;
import tech.pdai.springboot2unit5.dao.UserRepository;
import tech.pdai.springboot2unit5.entity.User;
import tech.pdai.springboot2unit5.service.IUserService;
import java.util.List;
* User service impl.
@Service
public class UserServiceImpl implements IUserService {
* user dao.
private final UserRepository userRepository;
* init.
* @param userRepository2 user dao
public UserServiceImpl(final UserRepository userRepository2) {
this.userRepository = userRepository2;
* find all user.
* @return list
@Override
public List<User> findAll() {
return userRepository.findAll();
Controller
package tech.pdai.springboot2unit5.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot2unit5.entity.User;
import tech.pdai.springboot2unit5.service.IUserService;
import java.util.List;
* User controller.
@RestController
public class UserController {
* user service.
private final IUserService userService;
* init.
* @param userService2 user service
public UserController(final IUserService userService2) {
this.userService = userService2;
* find user list.
* @return list
@GetMapping("user/list")
public ResponseEntity<List<User>> list() {
return ResponseEntity.ok(userService.findAll());
在实际的项目中可以使用profile来区分测试ut,使用test profile(包含H2内存库),真实环境使用MySQL或其它。
package tech.pdai.springboot2unit5;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Profile;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import tech.pdai.springboot2unit5.service.IUserService;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
* A way to test from H2.
* Just a demo, and change profile to 'test' for H2, and 'product' for MySQL.
@Profile("default")
@ExtendWith(SpringExtension.class)
@SpringBootTest
class JavaSpringbootUnit5ApplicationTests {
@Autowired
IUserService userService;
@Test
@DisplayName("Integration test")
void contextLoads() {
assertFalse(userService.findAll().isEmpty());
assertEquals("pdai", userService.findAll().get(0).getName());
controller
mockMvc
package tech.pdai.springboot2unit5.controller;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import tech.pdai.springboot2unit5.entity.User;
import tech.pdai.springboot2unit5.service.IUserService;
import java.util.Collections;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
* user controller test - use mockito.
@ExtendWith(SpringExtension.class)
@WebMvcTest(value = UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private IUserService userService;
* test find all user.
* @throws Exception exception
@Test
@DisplayName("Test findAll()")
public void list() throws Exception {
Mockito.when(userService.findAll()).thenReturn(
Collections.singletonList(new User(1, "pdai.tech", "1221111")));
mockMvc.perform(MockMvcRequestBuilders.get("/user/list")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
verify(userService, times(1)).findAll();
service
package tech.pdai.springboot2unit5.service.impl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import tech.pdai.springboot2unit5.entity.User;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ExtendWith(SpringExtension.class)
class UserServiceImplTest {
@Mock
private UserServiceImpl userService;
@Test
public void findAll() {
//Given
Mockito.when(userService.findAll()).thenReturn(
Collections.singletonList(new User(1, "pdai.tech", "1221111")));
//When
List<User> userDtoList = userService.findAll();
//Then
assertFalse(userDtoList.isEmpty());
verify(userService, times(1)).findAll();
groovy+spock
温文尔雅的钥匙 · python学习之 实现QQ自动发送消息 - 简书 1 年前 |