mirror of
https://github.com/Estom/notes.git
synced 2026-04-05 11:57:37 +08:00
java & sofabolt
This commit is contained in:
571
Java三方库/Junit.md
Normal file
571
Java三方库/Junit.md
Normal file
@@ -0,0 +1,571 @@
|
||||
## 1 概述
|
||||
|
||||
### 是什么
|
||||
|
||||
https://blog.csdn.net/weixin_43498556/article/details/120839089
|
||||
|
||||
JUnit是Java编程语言的单元测试框架,用于编写和可重复运行的自动化测试。
|
||||
|
||||
|
||||
1. 编码完成就可以立刻测试,尽早发现问题
|
||||
2. 将测试保存成为了代码,可以随时快速执行
|
||||
3. 可以嵌入持续集成流水线,自动为每次代码修改保驾护航
|
||||
|
||||
|
||||
### 注意事项
|
||||
|
||||
* 测试方法必须使用 @Test 修饰
|
||||
* 测试方法必须使用 public void 进行修饰,不能带参数
|
||||
* 一般使用单元测试会新建一个 test 目录存放测试代码,在生产部署的时候只需要将 test 目录下代码删除即可
|
||||
* 测试代码的包应该和被测试代码包结构保持一致
|
||||
* 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖
|
||||
* 测试类一般使用 Test 作为类名的后缀
|
||||
* 测试方法使一般用 test 作为方法名的前缀
|
||||
|
||||
### 测试失败
|
||||
|
||||
|
||||
* Failure:一般是由于测试结果和预期结果不一致引发的,表示测试的这个点发现了问题
|
||||
* Error:是由代码异常引起的,它可以产生于测试代码本身的错误,也可以是被测试代码中隐藏的 bug
|
||||
|
||||
|
||||
## 2 无框架使用
|
||||
|
||||
|
||||
### 创建 Test Case 类
|
||||
|
||||
1. 创建一个名为 TestJunit.java 的测试类。
|
||||
2. 向测试类中添加名为 testPrintMessage() 的方法。
|
||||
3. 向方法中添加 Annotaion @Test。
|
||||
4. 执行测试条件并且应用 Junit 的 assertEquals API 来检查。
|
||||
```java
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
public class TestJunit {
|
||||
|
||||
String message = "Hello World";
|
||||
MessageUtil messageUtil = new MessageUtil(message);
|
||||
|
||||
@Test
|
||||
public void testPrintMessage() {
|
||||
assertEquals(message,messageUtil.printMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 创建 Test Runner 类
|
||||
> 不与springboot/maven框架兼容运行时,需要创建字的测试启动类。
|
||||
> 如果是用maven插件,代码会帮忙自动执行所有的测试类。如果没有maven插件,则需要自己创建TestRunner类,启动测试过程。仍旧是java体系下的基本方法。
|
||||
|
||||
1. 创建一个 TestRunner 类
|
||||
2. 运用 JUnit 的 JUnitCore 类的 runClasses 方法来运行上述测试类的测试案例
|
||||
3. 获取在 Result Object 中运行的测试案例的结果
|
||||
4. 获取 Result Object 的 getFailures() 方法中的失败结果
|
||||
5. 获取 Result object 的 wasSuccessful() 方法中的成功结果
|
||||
|
||||
```java
|
||||
import org.junit.runner.JUnitCore;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.notification.Failure;
|
||||
|
||||
public class TestRunner {
|
||||
public static void main(String[] args) {
|
||||
Result result = JUnitCore.runClasses(TestJunit.class);
|
||||
for (Failure failure : result.getFailures()) {
|
||||
System.out.println(failure.toString());
|
||||
}
|
||||
System.out.println(result.wasSuccessful());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3 Junit4使用
|
||||
|
||||
|
||||
### 3.1 注解
|
||||
|
||||
* @Test:将一个普通方法修饰成一个测试方法 @Test(excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test(timeout = 毫秒数) :测试方法执行时间是否符合预期
|
||||
* @BeforeClass: 会在所有的方法执行前被执行,static 方法 (全局只会执行一次,而且是第一个运行)
|
||||
* @AfterClass:会在所有的方法执行之后进行执行,static 方法 (全局只会执行一次,而且是最后一个运行)
|
||||
* @Before:会在每一个测试方法被运行前执行一次
|
||||
* @After:会在每一个测试方法运行后被执行一次
|
||||
* @Ignore:所修饰的测试方法会被测试运行器忽略
|
||||
* @RunWith:可以更改测试运行器 org.junit.runner.Runner
|
||||
* @Parameters:参数化注解
|
||||
* @SuiteClasses 用于套件测试
|
||||
|
||||
|
||||
|
||||
### 3.2 断言
|
||||
|
||||
* assertEquals
|
||||
* assertNotEquals
|
||||
* assertFalse
|
||||
* assertTrue
|
||||
* assertNotNull
|
||||
* assertNull
|
||||
* assertArrayEquals
|
||||
* assertSame
|
||||
* assertNotSame
|
||||
* assertThat
|
||||
* assertThrowss
|
||||
|
||||
```java
|
||||
|
||||
public class AssertTests {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
```
|
||||
### 3.3 测试执行顺序
|
||||
|
||||
```java
|
||||
import org.junit.*;
|
||||
|
||||
/**
|
||||
* JunitTest
|
||||
*
|
||||
* @author Brian Tse
|
||||
* @since 2021/10/19 9:10
|
||||
*/
|
||||
public class JunitTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
System.out.println("@BeforeClass --> 全局只会执行一次,而且是第一个运行");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
System.out.println("@AfterClass --> 全局只会执行一次,而且是最后一个运行");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
System.out.println("@Before --> 在测试方法运行之前运行(每个测试方法之前都会执行一次)");
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
System.out.println("@After --> 在测试方法运行之后允许(每个测试方法之后都会执行一次)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCase1() {
|
||||
System.out.println("@Test --> 测试方法1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCase2() {
|
||||
System.out.println("@Test --> 测试方法2");
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 异常测试
|
||||
|
||||
### 3.5 忽略测试
|
||||
带有@Ignore注解的测试方法不会被执行
|
||||
|
||||
```java
|
||||
@Ignore
|
||||
@Test
|
||||
public void testCase2() {
|
||||
System.out.println("@Test --> 测试方法2");
|
||||
}
|
||||
```
|
||||
### 3.6 超时测试
|
||||
|
||||
JUnit提供了一个超时选项,如果一个测试用例比起指定的毫秒数花费了更多的时间,那么JUnit将自动将它标记为失败,timeout参数和@Test注解一起使用,例如@Test(timeout=1000)。 继续使用刚才的例子,现在将testCase1的执行时间延长到2000毫秒,并加上时间参数,设置超时为1000毫秒,然后执行测试类
|
||||
```
|
||||
@Test(timeout = 1000)
|
||||
public void testCase1() throws InterruptedException {
|
||||
TimeUnit.SECONDS.sleep(2);
|
||||
System.out.println("@Test --> 测试方法1");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3.7 参数化测试
|
||||
JUnit 4引入了一项名为参数化测试的新功能。参数化测试允许开发人员使用不同的值反复运行相同的测试。创建参数化测试需要遵循五个步骤:
|
||||
|
||||
1. 使用@RunWith(Parameterized.class)注释测试类。
|
||||
2. 创建一个使用@Parameters注释的公共静态方法,该方法返回一个对象集合作为测试数据集。
|
||||
3. 创建一个公共构造函数,它接受相当于一行“测试数据”的内容。
|
||||
4. 为测试数据的每个“列”创建一个实例变量。
|
||||
5. 使用实例变量作为测试数据的来源创建测试用例。
|
||||
6. 对于每行数据,将调用一次测试用例。让我们看看参数化测试的实际效果。
|
||||
```java
|
||||
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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* PrimeNumberCheckerTest
|
||||
*
|
||||
* @author Brian Tse
|
||||
* @since 2021/10/19 10:44
|
||||
*/
|
||||
//使用 @RunWith(Parameterized.class)注释测试类
|
||||
@RunWith(Parameterized.class)
|
||||
public class PrimeNumberCheckerTest {
|
||||
|
||||
// 为测试数据的每个“列”创建一个实例变量
|
||||
private Integer inputNumber;
|
||||
private Boolean expectedResult;
|
||||
private PrimeNumberChecker primeNumberChecker;
|
||||
|
||||
@Before
|
||||
public void initialize() {
|
||||
primeNumberChecker = new PrimeNumberChecker();
|
||||
}
|
||||
|
||||
// 创建一个公共构造函数,它接受相当于一行“测试数据”的内容
|
||||
public PrimeNumberCheckerTest(Integer inputNumber, Boolean expectedResult) {
|
||||
this.inputNumber = inputNumber;
|
||||
this.expectedResult = expectedResult;
|
||||
}
|
||||
|
||||
//创建一个使用 @Parameters注释的公共静态方法,该方法返回一个对象集合作为测试数据集
|
||||
@Parameterized.Parameters
|
||||
public static Collection primeNumbers() {
|
||||
return Arrays.asList(new Object[][]{{2, true}, {6, false}, {19, true}, {22, false}, {23, true}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrimeNumberChecker() {
|
||||
// 使用实例变量作为测试数据的来源创建测试用例
|
||||
System.out.println("Parameterized Number is : " + inputNumber);
|
||||
assertEquals(expectedResult, primeNumberChecker.validate(inputNumber));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结果如下
|
||||
|
||||
```java
|
||||
Parameterized Number is : 2
|
||||
|
||||
Parameterized Number is : 6
|
||||
|
||||
Parameterized Number is : 19
|
||||
|
||||
Parameterized Number is : 22
|
||||
|
||||
Parameterized Number is : 23
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 4 异常测试用例
|
||||
|
||||
如何验证代码是否按预期抛出异常?验证代码是否正常完成很重要,但确保代码在异常情况下按预期运行也很重要。
|
||||
|
||||
|
||||
### assertThrows方法
|
||||
Unit 4.13版本可以使用assertThrows方法。此方法可以断言给定的函数调用(例如,指定为lambda表达式或方法引用)会导致引发特定类型的异常。此外,它还返回抛出的异常,以便进一步断言(例如,验证抛出的消息和原因是否符合预期)。此外,可以在引发异常后对域对象的状态进行断言。
|
||||
|
||||
|
||||
```
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ExceptionTest
|
||||
*
|
||||
* @author Brian Tse
|
||||
* @since 2021/10/19 9:25
|
||||
*/
|
||||
public class ExceptionTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionAndState() {
|
||||
List<Object> list = new ArrayList<>();
|
||||
// 可以断言给定的函数调用会导致引发特定类型的异常
|
||||
IndexOutOfBoundsException thrown = assertThrows(
|
||||
IndexOutOfBoundsException.class,
|
||||
() -> list.add(1, new Object()));
|
||||
|
||||
// 根据返回抛出的异常,进一步断言抛出的消息和原因是否符合预期
|
||||
assertEquals("Index: 1, Size: 0", thrown.getMessage());
|
||||
// 在引发异常后对域对象的状态进行断言
|
||||
assertTrue(list.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Try/Catch 语句
|
||||
如果项目中尚未使用JUnit 4.13或代码库不支持lambdas,则可以使用JUnit 3.x中流行的try/catch习惯用法:
|
||||
```
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* ExceptionTest
|
||||
*
|
||||
* @author Brian Tse
|
||||
* @since 2021/10/19 9:25
|
||||
*/
|
||||
public class ExceptionTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionAndState() {
|
||||
List<Object> list = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Object o = list.get(0);
|
||||
fail("Expected an IndexOutOfBoundsException to be thrown");
|
||||
} catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
|
||||
assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### expected 参数和 @Test 注释一起使用
|
||||
Junit 用代码处理提供了一个追踪异常的选项。你可以测试代码是否它抛出了想要得到的异常。expected 参数和 @Test 注释一起使用。现在让我们看看 @Test(expected)。
|
||||
|
||||
```java
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* ExceptionTest
|
||||
*
|
||||
* @author Brian Tse
|
||||
* @since 2021/10/19 9:25
|
||||
*/
|
||||
public class ExceptionTest {
|
||||
|
||||
@Test(expected = IndexOutOfBoundsException.class)
|
||||
public void testExceptionAndState() {
|
||||
new ArrayList<Object>().get(0);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
应谨慎使用’expected’参数。如果方法中的any代码抛出’IndexOutOfBoundsException’,则上述测试将通过。使用该方法,无法测试异常中消息的值,或引发异常后域对象的状态
|
||||
|
||||
|
||||
## 5 assertThat和Matchers
|
||||
### assertThat
|
||||
此断言语法的优点包括:更具可读性和可键入性。
|
||||
|
||||
JUnit中的部分断言的可读性并不是很好,有时我们不得不自己编写表达式并断言其结果,并且因为我们没有提供失败的信息,当这个断言失败时只会抛出java.lang.AssertionError,无法知道到底是哪一部分出错。
|
||||
```
|
||||
assertTrue(responseString.contains("color") || responseString.contains("colour"));
|
||||
// ==> failure message:
|
||||
// java.lang.AssertionError:
|
||||
|
||||
assertThat(responseString, anyOf(containsString("color"), containsString("colour")));
|
||||
// ==> failure message:
|
||||
// java.lang.AssertionError:
|
||||
// Expected: (a string containing "color" or a string containing "colour")
|
||||
// but: was "responseString字符串"
|
||||
```
|
||||
|
||||
JUnit4.4引入了Hamcrest框架,Hamcest提供了一套匹配符Matcher,这些匹配符更接近自然语言,可读性高,更加灵活。并且使用全新的断言语法:assertThat,结合Hamcest提供的匹配符,只用这一个方法,就可以实现所有的测试。
|
||||
|
||||
assertThat语法如下:
|
||||
```java
|
||||
assertThat(T actual, Matcher matcher);
|
||||
assertThat(String reason, T actual, Matcher matcher);
|
||||
```
|
||||
其中reason为断言失败时的输出信息,actual为断言的值或对象,matcher为断言的匹配器,里面的逻辑决定了给定的actual对象满不满足断言。
|
||||
|
||||
JUnit4的匹配器定义在org.hamcrest.CoreMatchers 和 org.junit.matchers.JUnitMatchers 中。通过静态导入的方式引入相应的匹配器。
|
||||
|
||||
### JUnitMatchers
|
||||
org.junit.matchers.JUnitMatchers比较器中大部分都被标记为Deprecated,并使用org.hamcrest.CoreMatchers对应方法进行取代,但有两个方法得到了保留:
|
||||
```java
|
||||
static <T extends Exception> Matcher<T> isException(Matcher<T> exceptionMatcher)
|
||||
static <T extends Throwable> Matcher<T> isThrowable Matcher<T> throwableMatcher)
|
||||
```
|
||||
### CoreMatchers
|
||||
Hamcrest CoreMatchers在JUnit4.9版本被包含在JUnit的分发包中。
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
// 一般匹配符
|
||||
// allOf:所有条件必须都成立,测试才通过
|
||||
assertThat(actual, allOf(greaterThan(1), lessThan(3)));
|
||||
// anyOf:只要有一个条件成立,测试就通过
|
||||
assertThat(actual, anyOf(greaterThan(1), lessThan(1)));
|
||||
// anything:无论什么条件,测试都通过
|
||||
assertThat(actual, anything());
|
||||
// is:变量的值等于指定值时,测试通过
|
||||
assertThat(actual, is(2));
|
||||
// not:和is相反,变量的值不等于指定值时,测试通过
|
||||
assertThat(actual, not(1));
|
||||
|
||||
// 数值匹配符
|
||||
// closeTo:浮点型变量的值在3.0±0.5范围内,测试通过
|
||||
assertThat(actual, closeTo(3.0, 0.5));
|
||||
// greaterThan:变量的值大于指定值时,测试通过
|
||||
assertThat(actual, greaterThan(3.0));
|
||||
// lessThan:变量的值小于指定值时,测试通过
|
||||
assertThat(actual, lessThan(3.5));
|
||||
// greaterThanOrEuqalTo:变量的值大于等于指定值时,测试通过
|
||||
assertThat(actual, greaterThanOrEqualTo(3.3));
|
||||
// lessThanOrEqualTo:变量的值小于等于指定值时,测试通过
|
||||
assertThat(actual, lessThanOrEqualTo(3.4));
|
||||
|
||||
// 字符串匹配符
|
||||
// containsString:字符串变量中包含指定字符串时,测试通过
|
||||
assertThat(actual, containsString("ci"));
|
||||
// startsWith:字符串变量以指定字符串开头时,测试通过
|
||||
assertThat(actual, startsWith("Ma"));
|
||||
// endsWith:字符串变量以指定字符串结尾时,测试通过
|
||||
assertThat(actual, endsWith("i"));
|
||||
// euqalTo:字符串变量等于指定字符串时,测试通过
|
||||
assertThat(actual, equalTo("Magci"));
|
||||
// equalToIgnoringCase:字符串变量在忽略大小写的情况下等于指定字符串时,测试通过
|
||||
assertThat(actual, equalToIgnoringCase("magci"));
|
||||
// equalToIgnoringWhiteSpace:字符串变量在忽略头尾任意空格的情况下等于指定字符串时,测试通过
|
||||
assertThat(actual, equalToIgnoringWhiteSpace(" Magci "));
|
||||
|
||||
// 集合匹配符
|
||||
// hasItem:Iterable变量中含有指定元素时,测试通过
|
||||
assertThat(actual, hasItem("Magci"));
|
||||
// hasEntry:Map变量中含有指定键值对时,测试通过
|
||||
assertThat(actual, hasEntry("mgc", "Magci"));
|
||||
// hasKey:Map变量中含有指定键时,测试通过
|
||||
assertThat(actual, hasKey("mgc"));
|
||||
// hasValue:Map变量中含有指定值时,测试通过
|
||||
assertThat(actual, hasValue("Magci"));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 6 Springboot & Junit
|
||||
|
||||
### 步骤
|
||||
1. 导入Springboot相关的依赖,spring-boot-starter
|
||||
2. 写测试类,添加@SpringbootTest。该注解能够增加Spring的上下文,及@Autowire进行bean的注入。
|
||||
3. 写测试方法,添加@Test注解,填写测试用例。通过Assertions方法,进行断言。
|
||||
4. @BeforeEach、@BeforeAll、@AfterEach、@AfterAll。能够在不同阶段执行相关的操作。
|
||||
5. 通过MockBean添加mock规则。使用when().thenReturn()方法进行mock掉一个bean的所有方法。可以在@BeforeEach中执行mock方法,或者@BeforeAll中执行。没配置的规则,返回默认值。
|
||||
6. 通过Spybean进行部分注Mock。首先注入bean,只mock配置规则的部分,没有配置规则的部分使用原来的方法。
|
||||
|
||||
|
||||
### 实例
|
||||
```java
|
||||
/**
|
||||
* 类上面的两个注解不能缺少
|
||||
*@RunWith(SpringRunner.class)
|
||||
*@SpringBootTest(classes = 启动类(引导类).class)
|
||||
* 当此测试类所在的包与启动类所在的包:在同一级包下或是启动类所在包的子包
|
||||
*测试方法的注解不能缺少
|
||||
*@Test
|
||||
*直接注入UserService对象就能够实现测试接口的调用,记得加@Autowired。
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = SpringbootTestApplication.class)
|
||||
public class UserServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Test
|
||||
public void addUser() {
|
||||
String str = "哈希辣妈";
|
||||
int i = userService.addUser(str);
|
||||
System.out.println("返回结果:" + i);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
840
Java三方库/mockito.md
Normal file
840
Java三方库/mockito.md
Normal file
@@ -0,0 +1,840 @@
|
||||
|
||||
> 官网http://mockito.org/
|
||||
> API http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html
|
||||
> 抄笔记,https://www.letianbiji.com/java-mockito/mockito-test-isolate.html
|
||||
## 基本概念
|
||||
### 单元测试UT
|
||||
工作一段时间后,才真正意识到代码质量的重要性。虽然囫囵吞枣式地开发,表面上看来速度很快,但是给后续的维护与拓展制造了很多隐患。
|
||||
作为一个想专业但还不专业的程序员,通过构建覆盖率比较高的单元测试用例,可以比较显著地提高代码质量。如后续需求变更、版本迭代时,重新跑一次单元测试即可校验自己的改动是否正确。
|
||||
|
||||
### 是什么
|
||||
|
||||
Mockito是mocking框架,它让你用简洁的API做测试。而且Mockito简单易学,它可读性强和验证语法简洁。
|
||||
|
||||
### Stub和Mock异同
|
||||
相同:Stub和Mock都是模拟外部依赖
|
||||
不同:Stub是完全模拟一个外部依赖, 而Mock还可以用来判断测试通过还是失败
|
||||
|
||||
|
||||
### 与Junit框架一同使用
|
||||
|
||||
请MockitoAnnotations.initMocks(testClass); 必须至少使用一次。 要处理注释,我们可以使用内置的运行器MockitoJUnitRunner或规则MockitoRule 。 我们还可以在@Before注释的Junit方法中显式调用initMocks()方法。
|
||||
```java
|
||||
//1
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ApplicationTest {
|
||||
//code
|
||||
}
|
||||
//2
|
||||
public class ApplicationTest {
|
||||
@Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
|
||||
|
||||
//code
|
||||
}
|
||||
//3
|
||||
public class ApplicationTest {
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使得@Mock和@Spy等注解生效
|
||||
|
||||
### 使用限制
|
||||
|
||||
* 不能mock静态方法
|
||||
* 不能mock private方法
|
||||
* 不能mock final class
|
||||
|
||||
## 2 使用教程
|
||||
|
||||
### 添加依赖
|
||||
> 在spring-boot-starter-test中包含这两个依赖
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
### 添加引用
|
||||
|
||||
```java
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.Assert.*;
|
||||
```
|
||||
|
||||
### 典型事例
|
||||
```java
|
||||
package demo;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
public class ExampleServiceTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
// 创建mock对象
|
||||
HttpService mockHttpService = mock(HttpService.class);
|
||||
// 使用 mockito 对 queryStatus 方法打桩
|
||||
when(mockHttpService.queryStatus()).thenReturn(1);
|
||||
// 调用 mock 对象的 queryStatus 方法,结果永远是 1
|
||||
Assert.assertEquals(1, mockHttpService.queryStatus());
|
||||
|
||||
ExampleService exampleService = new ExampleService();
|
||||
exampleService.setHttpService(mockHttpService);
|
||||
Assert.assertEquals("Hello", exampleService.hello() );
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
1. 通过 mock 函数生成了一个 HttpService 的 mock 对象(这个对象是动态生成的)。
|
||||
|
||||
2. 通过 when .. thenReturn 指定了当调用 mock对象的 queryStatus 方法时,返回 1 ,这个叫做打桩。
|
||||
|
||||
3. 然后将 mock 对象注入到 exampleService 中,exampleService.hello() 的返回永远是 Hello。
|
||||
|
||||
|
||||
4. mock 对象的方法的返回值默认都是返回类型的默认值。例如,返回类型是 int,默认返回值是 0;返回类型是一个类,默认返回值是 null。
|
||||
|
||||
|
||||
### mock 类
|
||||
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Random mockRandom = mock(Random.class);
|
||||
|
||||
System.out.println( mockRandom.nextBoolean() );
|
||||
System.out.println( mockRandom.nextInt() );
|
||||
System.out.println( mockRandom.nextDouble() );
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void when_thenReturn(){
|
||||
//mock一个Iterator类
|
||||
Iterator iterator = mock(Iterator.class);
|
||||
//预设当iterator调用next()时第一次返回hello,第n次都返回world
|
||||
when(iterator.next()).thenReturn("hello").thenReturn("world");
|
||||
//使用mock的对象
|
||||
String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
|
||||
//验证结果
|
||||
assertEquals("hello world world",result);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### mock接口
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List mockList = mock(List.class);
|
||||
|
||||
Assert.assertEquals(0, mockList.size());
|
||||
Assert.assertEquals(null, mockList.get(0));
|
||||
|
||||
mockList.add("a"); // 调用 mock 对象的写方法,是没有效果的
|
||||
|
||||
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
|
||||
Assert.assertEquals(null, mockList.get(0)); // 没有指定 get(0) 返回值,这里结果是默认值
|
||||
|
||||
when(mockList.get(0)).thenReturn("a"); // 指定 get(0)时返回 a
|
||||
|
||||
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
|
||||
Assert.assertEquals("a", mockList.get(0)); // 因为上面指定了 get(0) 返回 a,所以这里会返回 a
|
||||
|
||||
Assert.assertEquals(null, mockList.get(1)); // 没有指定 get(1) 返回值,这里结果是默认值
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Mock注解
|
||||
|
||||
@Mock 注解可以理解为对 mock 方法的一个替代。
|
||||
|
||||
使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效。
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Mock
|
||||
private Random random;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
// 让注解生效
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
when(random.nextInt()).thenReturn(100);
|
||||
|
||||
Assert.assertEquals(100, random.nextInt());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 3 when参数匹配
|
||||
参数匹配器可以用在when和verify的方法中。
|
||||
|
||||
|
||||
### 精确匹配
|
||||
|
||||
其中when(mockList.get(0)).thenReturn("a"); 指定了get(0)的返回值,这个 0 就是参数的精确匹配。我们还可以让不同的参数对应不同的返回值,例如:
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Mock
|
||||
private List<String> mockStringList;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
mockStringList.add("a");
|
||||
|
||||
when(mockStringList.get(0)).thenReturn("a");
|
||||
when(mockStringList.get(1)).thenReturn("b");
|
||||
|
||||
Assert.assertEquals("a", mockStringList.get(0));
|
||||
Assert.assertEquals("b", mockStringList.get(1));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 模糊匹配
|
||||
|
||||
可以使用 Mockito.anyInt() 匹配所有类型为 int 的参数:
|
||||
|
||||
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Mock
|
||||
private List<String> mockStringList;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
mockStringList.add("a");
|
||||
|
||||
when(mockStringList.get(anyInt())).thenReturn("a"); // 使用 Mockito.anyInt() 匹配所有的 int
|
||||
|
||||
Assert.assertEquals("a", mockStringList.get(0));
|
||||
Assert.assertEquals("a", mockStringList.get(1));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mockStudent() {
|
||||
Student student = mock(Student.class);
|
||||
|
||||
student.sleep(1, "1", "admin");
|
||||
|
||||
verify(student).sleep(anyInt(), anyString(), eq("admin"));
|
||||
verify(student).sleep(anyInt(), anyString(), eq("admin"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 4 then返回值
|
||||
### thenReturn 设置方法的返回值
|
||||
thenReturn 用来指定特定函数和参数调用的返回值。
|
||||
|
||||
比如:
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
Random mockRandom = mock(Random.class);
|
||||
|
||||
when(mockRandom.nextInt()).thenReturn(1);
|
||||
|
||||
Assert.assertEquals(1, mockRandom.nextInt());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
thenReturn 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Random mockRandom = mock(Random.class);
|
||||
|
||||
when(mockRandom.nextInt()).thenReturn(1, 2, 3);
|
||||
|
||||
Assert.assertEquals(1, mockRandom.nextInt());
|
||||
Assert.assertEquals(2, mockRandom.nextInt());
|
||||
Assert.assertEquals(3, mockRandom.nextInt());
|
||||
Assert.assertEquals(3, mockRandom.nextInt());
|
||||
Assert.assertEquals(3, mockRandom.nextInt());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### thenThrow 让方法抛出异常
|
||||
thenThrow 用来让函数调用抛出异常。
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
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 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### then、thenAnswer 自定义方法处理逻辑
|
||||
then 和 thenAnswer 的效果是一样的。它们的参数是实现 Answer 接口的对象,在改对象中可以获取调用参数,自定义返回值
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
static class ExampleService {
|
||||
|
||||
public int add(int a, int b) {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Mock
|
||||
private ExampleService exampleService;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(exampleService.add(anyInt(),anyInt())).thenAnswer(new Answer<Integer>() {
|
||||
@Override
|
||||
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||
Object[] args = invocation.getArguments();
|
||||
// 获取参数
|
||||
Integer a = (Integer) args[0];
|
||||
Integer b = (Integer) args[1];
|
||||
|
||||
// 根据第1个参数,返回不同的值
|
||||
if (a == 1) {
|
||||
return 9;
|
||||
}
|
||||
if (a == 2) {
|
||||
return 99;
|
||||
}
|
||||
if (a == 3) {
|
||||
throw new RuntimeException("异常");
|
||||
}
|
||||
return 999;
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertEquals(9, exampleService.add(1, 100));
|
||||
Assert.assertEquals(99, exampleService.add(2, 100));
|
||||
|
||||
try {
|
||||
exampleService.add(3, 100);
|
||||
Assert.fail();
|
||||
} catch (RuntimeException ex) {
|
||||
Assert.assertEquals("异常", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 5 verify校验操作
|
||||
|
||||
|
||||
使用 verify 可以校验 mock 对象是否发生过某些操作
|
||||
示例
|
||||
```java
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
static class ExampleService {
|
||||
|
||||
public int add(int a, int b) {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ExampleService exampleService = mock(ExampleService.class);
|
||||
|
||||
// 设置让 add(1,2) 返回 100
|
||||
when(exampleService.add(1, 2)).thenReturn(100);
|
||||
|
||||
exampleService.add(1, 2);
|
||||
|
||||
// 校验是否调用过 add(1, 2) -> 校验通过
|
||||
verify(exampleService).add(1, 2);
|
||||
|
||||
// 校验是否调用过 add(2, 2) -> 校验不通过
|
||||
verify(exampleService).add(2, 2);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
verify 配合 time 方法,可以校验某些操作发生的次数
|
||||
示例:
|
||||
```java
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
static class ExampleService {
|
||||
|
||||
public int add(int a, int b) {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ExampleService exampleService = mock(ExampleService.class);
|
||||
|
||||
// 第1次调用
|
||||
exampleService.add(1, 2);
|
||||
|
||||
// 校验是否调用过一次 add(1, 2) -> 校验通过
|
||||
verify(exampleService, times(1)).add(1, 2);
|
||||
|
||||
// 第2次调用
|
||||
exampleService.add(1, 2);
|
||||
|
||||
// 校验是否调用过两次 add(1, 2) -> 校验通过
|
||||
verify(exampleService, times(2)).add(1, 2);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 6 Spy
|
||||
|
||||
spy 和 mock不同,不同点是:
|
||||
* spy 的参数是对象示例,mock 的参数是 class。
|
||||
* 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
|
||||
|
||||
### Spy对象
|
||||
```java
|
||||
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 对应注解 @Spy,和 @Mock 是一样用的。
|
||||
```java
|
||||
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 {
|
||||
|
||||
@Spy
|
||||
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初始化
|
||||
|
||||
对于@Spy,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。如果没有无参构造函数,必须使用写法2。
|
||||
|
||||
```
|
||||
// 写法1
|
||||
@Spy
|
||||
private ExampleService spyExampleService;
|
||||
|
||||
// 写法2
|
||||
@Spy
|
||||
private ExampleService spyExampleService = new ExampleService();
|
||||
```
|
||||
|
||||
## 7 @InjectMocks
|
||||
|
||||
mockito 会将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。
|
||||
|
||||
注入方式有多种,mockito 会按照下面的顺序尝试注入:
|
||||
|
||||
* 构造函数注入
|
||||
* 设值函数注入(set函数)
|
||||
* 属性注入
|
||||
|
||||
|
||||
package demo;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class HttpService {
|
||||
|
||||
public int queryStatus() {
|
||||
// 发起网络请求,提取返回结果
|
||||
// 这里用随机数模拟结果
|
||||
return new Random().nextInt(2);
|
||||
}
|
||||
|
||||
}
|
||||
package demo;
|
||||
|
||||
public class ExampleService {
|
||||
|
||||
private HttpService httpService;
|
||||
|
||||
public String hello() {
|
||||
int status = httpService.queryStatus();
|
||||
if (status == 0) {
|
||||
return "你好";
|
||||
}
|
||||
else if (status == 1) {
|
||||
return "Hello";
|
||||
}
|
||||
else {
|
||||
return "未知状态";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
编写测试类:
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
public class ExampleServiceTest {
|
||||
|
||||
@Mock
|
||||
private HttpService httpService;
|
||||
|
||||
@InjectMocks
|
||||
private ExampleService exampleService = new ExampleService(); // 会将 httpService 注入进去
|
||||
|
||||
@Test
|
||||
public void test01() {
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(httpService.queryStatus()).thenReturn(0);
|
||||
|
||||
Assert.assertEquals("你好", exampleService.hello());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 8 链式调用
|
||||
thenReturn、doReturn 等函数支持链式调用,用来指定函数特定调用次数时的行为。
|
||||
|
||||
|
||||
```java
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class MockitoDemo {
|
||||
|
||||
static class ExampleService {
|
||||
|
||||
public int add(int a, int b) {
|
||||
return a+b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ExampleService exampleService = mock(ExampleService.class);
|
||||
|
||||
// 让第1次调用返回 100,第2次调用返回 200
|
||||
when(exampleService.add(1, 2)).thenReturn(100).thenReturn(200);
|
||||
|
||||
Assert.assertEquals(100, exampleService.add(1, 2));
|
||||
Assert.assertEquals(200, exampleService.add(1, 2));
|
||||
Assert.assertEquals(200, exampleService.add(1, 2));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 9 Springboot与Mockito
|
||||
|
||||
* spring-boot-starter-test中已经加入了Mockito依赖,所以我们无需手动引入。
|
||||
* 另外要注意一点,在SpringBoot环境下,我们可能会用@SpringBootTest注解。
|
||||
```java
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@BootstrapWith(SpringBootTestContextBootstrapper.class)
|
||||
@ExtendWith({SpringExtension.class})
|
||||
public @interface SpringBootTest {
|
||||
```
|
||||
* 如果用这个注解,跑单元测试的时候会加载SpringBoot的上下文,初始化Spring容器一次,显得格外的慢,这可能也是很多人放弃在Spring环境下使用单元测试的原因之一。
|
||||
* 不过我们可以不用这个Spring环境,单元测试的目的应该是只测试这一个函数的逻辑正确性,某些容器中的相关依赖可以通过Mockito仿真。
|
||||
|
||||
* 所以我们可以直接拓展自MockitoExtendsion,这样跑测试就很快了。
|
||||
```
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ListMockTest {
|
||||
}
|
||||
```
|
||||
834
Java基础教程/Java并发编程/sofabolt启发.md
Normal file
834
Java基础教程/Java并发编程/sofabolt启发.md
Normal file
@@ -0,0 +1,834 @@
|
||||
## sofabolt基础通信模型
|
||||
|
||||
> 四个线程的模型:(非常重要。非常简单。非常关键)
|
||||
> 1. 有的时候,不必非得给一次通信扣上同步或者一部的的帽子。
|
||||
> 2. 客户端的同步异步称为阻塞和非阻塞,服务器端的同步异步称为同步异步。
|
||||
> 3. 只要链路上有一处是一部的,我们称整个调用链路就是异步的。
|
||||
> 4. 将客户端分为客户端线程和客户端连接线程,将服务端分为服务端线程和服务端连接线程。
|
||||
> 1. 如果客户端线程等待连接线程返回结果,则称为阻塞的。如果不等待连接线程的结果,称为非阻塞的。
|
||||
> 2. 如果服务端连接线程等待服务端线程的结果,则称为同步的。如果不等待服务端处理线程的结果,称为异步的。
|
||||
|
||||
### 四种客户端模型
|
||||
|
||||
我们提供了四种通信模型,这四种模型都是客户端的调用模式。
|
||||
|
||||
1. Oneway 调用(客户端非阻塞)
|
||||
|
||||
当前线程发起调用后,不关心调用结果,不做超时控制,只要请求已经发出,就完成本次调用。注意 Oneway 调用不保证成功,而且发起方无法知道调用结果。因此通常用于可以重试,或者定时通知类的场景,调用过程是有可能因为网络问题,机器故障等原因,导致请求失败。业务场景需要能接受这样的异常场景,才可以使用。请参考示例。
|
||||
|
||||
1. Sync 同步调用(客户端阻塞)
|
||||
|
||||
当前线程发起调用后,需要在指定的超时时间内,等到响应结果,才能完成本次调用。如果超时时间内没有得到结果,那么会抛出超时异常。这种调用模式最常用。注意要根据对端的处理能力,合理设置超时时间。请参考示例。
|
||||
|
||||
3. Future调用(客户端半阻塞)
|
||||
|
||||
当前线程发起调用,得到一个 RpcResponseFuture 对象,当前线程可以继续执行下一次调用。可以在任意时刻,使用 RpcResponseFuture 对象的 get() 方法来获取结果,如果响应已经回来,此时就马上得到结果;如果响应没有回来,则会阻塞住当前线程,直到响应回来,或者超时时间到。请参考示例。
|
||||
|
||||
4. Callback异步调用(客户端不阻塞)
|
||||
|
||||
当前线程发起调用,则本次调用马上结束,可以马上执行下一次调用。发起调用时需要注册一个回调,该回调需要分配一个异步线程池。待响应回来后,会在回调的异步线程池,来执行回调逻辑。请参考示例。
|
||||
|
||||
|
||||
```java
|
||||
package com.alipay.remoting.demo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alipay.remoting.Connection;
|
||||
import com.alipay.remoting.ConnectionEventType;
|
||||
import com.alipay.remoting.InvokeCallback;
|
||||
import com.alipay.remoting.exception.RemotingException;
|
||||
import com.alipay.remoting.rpc.RpcClient;
|
||||
import com.alipay.remoting.rpc.RpcResponseFuture;
|
||||
import com.alipay.remoting.rpc.common.BoltServer;
|
||||
import com.alipay.remoting.rpc.common.CONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.DISCONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.PortScan;
|
||||
import com.alipay.remoting.rpc.common.RequestBody;
|
||||
import com.alipay.remoting.rpc.common.SimpleClientUserProcessor;
|
||||
import com.alipay.remoting.rpc.common.SimpleServerUserProcessor;
|
||||
import com.alipay.remoting.util.RemotingUtil;
|
||||
|
||||
/**
|
||||
* basic usage demo
|
||||
*
|
||||
* basic usage of rpc client and rpc server
|
||||
*
|
||||
* @author xiaomin.cxm
|
||||
* @version $Id: BasicUsageDemo.java, v 0.1 Apr 6, 2016 8:58:36 PM xiaomin.cxm Exp $
|
||||
*/
|
||||
public class BasicUsageDemoByJunit {
|
||||
static Logger logger = LoggerFactory
|
||||
.getLogger(BasicUsageDemoByJunit.class);
|
||||
|
||||
BoltServer server;
|
||||
RpcClient client;
|
||||
|
||||
int port = PortScan.select();
|
||||
String ip = "127.0.0.1";
|
||||
String addr = "127.0.0.1:" + port;
|
||||
|
||||
int invokeTimes = 5;
|
||||
|
||||
SimpleServerUserProcessor serverUserProcessor = new SimpleServerUserProcessor();
|
||||
SimpleClientUserProcessor clientUserProcessor = new SimpleClientUserProcessor();
|
||||
CONNECTEventProcessor clientConnectProcessor = new CONNECTEventProcessor();
|
||||
CONNECTEventProcessor serverConnectProcessor = new CONNECTEventProcessor();
|
||||
DISCONNECTEventProcessor clientDisConnectProcessor = new DISCONNECTEventProcessor();
|
||||
DISCONNECTEventProcessor serverDisConnectProcessor = new DISCONNECTEventProcessor();
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
server = new BoltServer(port, true);
|
||||
server.start();
|
||||
server.addConnectionEventProcessor(ConnectionEventType.CONNECT, serverConnectProcessor);
|
||||
server.addConnectionEventProcessor(ConnectionEventType.CLOSE, serverDisConnectProcessor);
|
||||
server.registerUserProcessor(serverUserProcessor);
|
||||
|
||||
client = new RpcClient();
|
||||
client.addConnectionEventProcessor(ConnectionEventType.CONNECT, clientConnectProcessor);
|
||||
client.addConnectionEventProcessor(ConnectionEventType.CLOSE, clientDisConnectProcessor);
|
||||
client.registerUserProcessor(clientUserProcessor);
|
||||
client.init();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stop() {
|
||||
try {
|
||||
server.stop();
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Stop server failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneway() throws InterruptedException {
|
||||
RequestBody req = new RequestBody(2, "hello world oneway");
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
try {
|
||||
client.oneway(addr, req);
|
||||
Thread.sleep(100);
|
||||
} catch (RemotingException e) {
|
||||
String errMsg = "RemotingException caught in oneway!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSync() throws InterruptedException {
|
||||
RequestBody req = new RequestBody(1, "hello world sync");
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
try {
|
||||
String res = (String) client.invokeSync(addr, req, 3000);
|
||||
logger.warn("Result received in sync: " + res);
|
||||
Assert.assertEquals(RequestBody.DEFAULT_SERVER_RETURN_STR, res);
|
||||
} catch (RemotingException e) {
|
||||
String errMsg = "RemotingException caught in sync!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
} catch (InterruptedException e) {
|
||||
String errMsg = "InterruptedException caught in sync!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFuture() throws InterruptedException {
|
||||
RequestBody req = new RequestBody(2, "hello world future");
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
try {
|
||||
RpcResponseFuture future = client.invokeWithFuture(addr, req, 3000);
|
||||
String res = (String) future.get();
|
||||
Assert.assertEquals(RequestBody.DEFAULT_SERVER_RETURN_STR, res);
|
||||
} catch (RemotingException e) {
|
||||
String errMsg = "RemotingException caught in future!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
} catch (InterruptedException e) {
|
||||
String errMsg = "InterruptedException caught in future!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallback() throws InterruptedException {
|
||||
RequestBody req = new RequestBody(1, "hello world callback");
|
||||
final List<String> rets = new ArrayList<String>(1);
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
client.invokeWithCallback(addr, req, new InvokeCallback() {
|
||||
Executor executor = Executors.newCachedThreadPool();
|
||||
|
||||
@Override
|
||||
public void onResponse(Object result) {
|
||||
logger.warn("Result received in callback: " + result);
|
||||
rets.add((String) result);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable e) {
|
||||
logger.error("Process exception in callback.", e);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
}, 1000);
|
||||
|
||||
} catch (RemotingException e) {
|
||||
latch.countDown();
|
||||
String errMsg = "RemotingException caught in callback!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
String errMsg = "InterruptedException caught in callback!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
if (rets.size() == 0) {
|
||||
Assert.fail("No result! Maybe exception caught!");
|
||||
}
|
||||
Assert.assertEquals(RequestBody.DEFAULT_SERVER_RETURN_STR, rets.get(0));
|
||||
rets.clear();
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 两种服务器模型
|
||||
|
||||
提供两种模型,都是服务器的模型。
|
||||
|
||||
1. 同步处理器
|
||||
同步处理返回结果
|
||||
|
||||
```java
|
||||
package com.alipay.remoting.rpc.common;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alipay.remoting.BizContext;
|
||||
import com.alipay.remoting.InvokeContext;
|
||||
import com.alipay.remoting.NamedThreadFactory;
|
||||
import com.alipay.remoting.rpc.protocol.SyncUserProcessor;
|
||||
|
||||
/**
|
||||
* a demo user processor for rpc server
|
||||
*
|
||||
* @author xiaomin.cxm
|
||||
* @version $Id: SimpleServerUserProcessor.java, v 0.1 Jan 7, 2016 3:01:49 PM xiaomin.cxm Exp $
|
||||
*/
|
||||
public class SimpleServerUserProcessor extends SyncUserProcessor<RequestBody> {
|
||||
|
||||
/** logger */
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(SimpleServerUserProcessor.class);
|
||||
|
||||
/** delay milliseconds */
|
||||
private long delayMs;
|
||||
|
||||
/** whether delay or not */
|
||||
private boolean delaySwitch;
|
||||
|
||||
/** executor */
|
||||
private ThreadPoolExecutor executor;
|
||||
|
||||
/** default is true */
|
||||
private boolean timeoutDiscard = true;
|
||||
|
||||
private AtomicInteger invokeTimes = new AtomicInteger();
|
||||
|
||||
private AtomicInteger onewayTimes = new AtomicInteger();
|
||||
private AtomicInteger syncTimes = new AtomicInteger();
|
||||
private AtomicInteger futureTimes = new AtomicInteger();
|
||||
private AtomicInteger callbackTimes = new AtomicInteger();
|
||||
|
||||
private String remoteAddr;
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public SimpleServerUserProcessor() {
|
||||
this.delaySwitch = false;
|
||||
this.delayMs = 0;
|
||||
this.executor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(4), new NamedThreadFactory("Request-process-pool"));
|
||||
}
|
||||
|
||||
public SimpleServerUserProcessor(long delay) {
|
||||
this();
|
||||
if (delay < 0) {
|
||||
throw new IllegalArgumentException("delay time illegal!");
|
||||
}
|
||||
this.delaySwitch = true;
|
||||
this.delayMs = delay;
|
||||
}
|
||||
|
||||
public SimpleServerUserProcessor(long delay, int core, int max, int keepaliveSeconds,
|
||||
int workQueue) {
|
||||
this(delay);
|
||||
this.executor = new ThreadPoolExecutor(core, max, keepaliveSeconds, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(workQueue), new NamedThreadFactory(
|
||||
"Request-process-pool"));
|
||||
}
|
||||
|
||||
// ~~~ override methods
|
||||
|
||||
@Override
|
||||
public Object handleRequest(BizContext bizCtx, RequestBody request) throws Exception {
|
||||
logger.warn("Request received:" + request + ", timeout:" + bizCtx.getClientTimeout()
|
||||
+ ", arriveTimestamp:" + bizCtx.getArriveTimestamp());
|
||||
|
||||
if (bizCtx.isRequestTimeout()) {
|
||||
String errMsg = "Stop process in server biz thread, already timeout!";
|
||||
processTimes(request);
|
||||
logger.warn(errMsg);
|
||||
throw new Exception(errMsg);
|
||||
}
|
||||
|
||||
this.remoteAddr = bizCtx.getRemoteAddress();
|
||||
|
||||
//test biz context get connection
|
||||
Assert.assertNotNull(bizCtx.getConnection());
|
||||
Assert.assertTrue(bizCtx.getConnection().isFine());
|
||||
|
||||
Long waittime = (Long) bizCtx.getInvokeContext().get(InvokeContext.BOLT_PROCESS_WAIT_TIME);
|
||||
Assert.assertNotNull(waittime);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Server User processor process wait time {}", waittime);
|
||||
}
|
||||
|
||||
latch.countDown();
|
||||
logger.warn("Server User processor say, remote address is [" + this.remoteAddr + "].");
|
||||
Assert.assertEquals(RequestBody.class, request.getClass());
|
||||
processTimes(request);
|
||||
if (!delaySwitch) {
|
||||
return RequestBody.DEFAULT_SERVER_RETURN_STR;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(delayMs);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return RequestBody.DEFAULT_SERVER_RETURN_STR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interest() {
|
||||
return RequestBody.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean timeoutDiscard() {
|
||||
return this.timeoutDiscard;
|
||||
}
|
||||
|
||||
// ~~~ public methods
|
||||
public int getInvokeTimes() {
|
||||
return this.invokeTimes.get();
|
||||
}
|
||||
|
||||
public int getInvokeTimesEachCallType(RequestBody.InvokeType type) {
|
||||
return new int[] { this.onewayTimes.get(), this.syncTimes.get(), this.futureTimes.get(),
|
||||
this.callbackTimes.get() }[type.ordinal()];
|
||||
}
|
||||
|
||||
public String getRemoteAddr() throws InterruptedException {
|
||||
latch.await(100, TimeUnit.MILLISECONDS);
|
||||
return this.remoteAddr;
|
||||
}
|
||||
|
||||
// ~~~ private methods
|
||||
private void processTimes(RequestBody req) {
|
||||
this.invokeTimes.incrementAndGet();
|
||||
if (req.getMsg().equals(RequestBody.DEFAULT_ONEWAY_STR)) {
|
||||
this.onewayTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_SYNC_STR)) {
|
||||
this.syncTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_FUTURE_STR)) {
|
||||
this.futureTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_CALLBACK_STR)) {
|
||||
this.callbackTimes.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~ getters and setters
|
||||
/**
|
||||
* Getter method for property <tt>timeoutDiscard</tt>.
|
||||
*
|
||||
* @return property value of timeoutDiscard
|
||||
*/
|
||||
public boolean isTimeoutDiscard() {
|
||||
return timeoutDiscard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>timeoutDiscard<tt>.
|
||||
*
|
||||
* @param timeoutDiscard value to be assigned to property timeoutDiscard
|
||||
*/
|
||||
public void setTimeoutDiscard(boolean timeoutDiscard) {
|
||||
this.timeoutDiscard = timeoutDiscard;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 异步处理器
|
||||
直接返回结果,异步处理。其实这都是服务器上开不开异步线程的问题,与sofabolt框架关系不大。
|
||||
|
||||
```java
|
||||
package com.alipay.remoting.rpc.common;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alipay.remoting.AsyncContext;
|
||||
import com.alipay.remoting.BizContext;
|
||||
import com.alipay.remoting.NamedThreadFactory;
|
||||
import com.alipay.remoting.rpc.protocol.AsyncUserProcessor;
|
||||
|
||||
/**
|
||||
* a demo aysnc user processor for rpc server
|
||||
*
|
||||
* @author xiaomin.cxm
|
||||
* @version $Id: SimpleServerUserProcessor.java, v 0.1 Jan 7, 2016 3:01:49 PM xiaomin.cxm Exp $
|
||||
*/
|
||||
public class AsyncServerUserProcessor extends AsyncUserProcessor<RequestBody> {
|
||||
|
||||
/** logger */
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(AsyncServerUserProcessor.class);
|
||||
|
||||
/** delay milliseconds */
|
||||
private long delayMs;
|
||||
|
||||
/** whether delay or not */
|
||||
private boolean delaySwitch;
|
||||
|
||||
/** whether exception */
|
||||
private boolean isException;
|
||||
|
||||
/** whether null */
|
||||
private boolean isNull;
|
||||
|
||||
/** executor */
|
||||
private ThreadPoolExecutor executor;
|
||||
|
||||
private ThreadPoolExecutor asyncExecutor;
|
||||
|
||||
private AtomicInteger invokeTimes = new AtomicInteger();
|
||||
|
||||
private AtomicInteger onewayTimes = new AtomicInteger();
|
||||
private AtomicInteger syncTimes = new AtomicInteger();
|
||||
private AtomicInteger futureTimes = new AtomicInteger();
|
||||
private AtomicInteger callbackTimes = new AtomicInteger();
|
||||
|
||||
private String remoteAddr;
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public AsyncServerUserProcessor() {
|
||||
this.delaySwitch = false;
|
||||
this.isException = false;
|
||||
this.delayMs = 0;
|
||||
this.executor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(4), new NamedThreadFactory("Request-process-pool"));
|
||||
this.asyncExecutor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(4), new NamedThreadFactory(
|
||||
"Another-aysnc-process-pool"));
|
||||
}
|
||||
|
||||
public AsyncServerUserProcessor(boolean isException, boolean isNull) {
|
||||
this();
|
||||
this.isException = isException;
|
||||
this.isNull = isNull;
|
||||
}
|
||||
|
||||
public AsyncServerUserProcessor(long delay) {
|
||||
this();
|
||||
if (delay < 0) {
|
||||
throw new IllegalArgumentException("delay time illegal!");
|
||||
}
|
||||
this.delaySwitch = true;
|
||||
this.delayMs = delay;
|
||||
}
|
||||
|
||||
public AsyncServerUserProcessor(long delay, int core, int max, int keepaliveSeconds,
|
||||
int workQueue) {
|
||||
this(delay);
|
||||
this.executor = new ThreadPoolExecutor(core, max, keepaliveSeconds, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(workQueue), new NamedThreadFactory(
|
||||
"Request-process-pool"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestBody request) {
|
||||
this.asyncExecutor.execute(new InnerTask(bizCtx, asyncCtx, request));
|
||||
}
|
||||
|
||||
class InnerTask implements Runnable {
|
||||
private BizContext bizCtx;
|
||||
private AsyncContext asyncCtx;
|
||||
private RequestBody request;
|
||||
|
||||
public InnerTask(BizContext bizCtx, AsyncContext asyncCtx, RequestBody request) {
|
||||
this.bizCtx = bizCtx;
|
||||
this.asyncCtx = asyncCtx;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
logger.warn("Request received:" + request);
|
||||
remoteAddr = bizCtx.getRemoteAddress();
|
||||
latch.countDown();
|
||||
logger.warn("Server User processor say, remote address is [" + remoteAddr + "].");
|
||||
Assert.assertEquals(RequestBody.class, request.getClass());
|
||||
processTimes(request);
|
||||
if (isException) {
|
||||
this.asyncCtx.sendResponse(new IllegalArgumentException("Exception test"));
|
||||
} else if (isNull) {
|
||||
this.asyncCtx.sendResponse(null);
|
||||
} else {
|
||||
if (!delaySwitch) {
|
||||
this.asyncCtx.sendResponse(RequestBody.DEFAULT_SERVER_RETURN_STR);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(delayMs);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.asyncCtx.sendResponse(RequestBody.DEFAULT_SERVER_RETURN_STR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processTimes(RequestBody req) {
|
||||
this.invokeTimes.incrementAndGet();
|
||||
if (req.getMsg().equals(RequestBody.DEFAULT_ONEWAY_STR)) {
|
||||
this.onewayTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_SYNC_STR)) {
|
||||
this.syncTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_FUTURE_STR)) {
|
||||
this.futureTimes.incrementAndGet();
|
||||
} else if (req.getMsg().equals(RequestBody.DEFAULT_CALLBACK_STR)) {
|
||||
this.callbackTimes.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interest() {
|
||||
return RequestBody.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public int getInvokeTimes() {
|
||||
return this.invokeTimes.get();
|
||||
}
|
||||
|
||||
public int getInvokeTimesEachCallType(RequestBody.InvokeType type) {
|
||||
return new int[] { this.onewayTimes.get(), this.syncTimes.get(), this.futureTimes.get(),
|
||||
this.callbackTimes.get() }[type.ordinal()];
|
||||
}
|
||||
|
||||
public String getRemoteAddr() throws InterruptedException {
|
||||
latch.await(100, TimeUnit.MILLISECONDS);
|
||||
return this.remoteAddr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 客户端和服务端初始化
|
||||
* 客户端初始化方法
|
||||
```java
|
||||
package com.alipay.remoting.demo;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alipay.remoting.ConnectionEventType;
|
||||
import com.alipay.remoting.exception.RemotingException;
|
||||
import com.alipay.remoting.rpc.RpcClient;
|
||||
import com.alipay.remoting.rpc.common.CONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.DISCONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.RequestBody;
|
||||
import com.alipay.remoting.rpc.common.SimpleClientUserProcessor;
|
||||
|
||||
/**
|
||||
* a demo for rpc client, you can just run the main method after started rpc server of {@link RpcServerDemoByMain}
|
||||
*
|
||||
* @author tsui
|
||||
* @version $Id: RpcClientDemoByMain.java, v 0.1 2018-04-10 10:39 tsui Exp $
|
||||
*/
|
||||
public class RpcClientDemoByMain {
|
||||
static Logger logger = LoggerFactory
|
||||
.getLogger(RpcClientDemoByMain.class);
|
||||
|
||||
static RpcClient client;
|
||||
|
||||
static String addr = "127.0.0.1:8999";
|
||||
|
||||
SimpleClientUserProcessor clientUserProcessor = new SimpleClientUserProcessor();
|
||||
CONNECTEventProcessor clientConnectProcessor = new CONNECTEventProcessor();
|
||||
DISCONNECTEventProcessor clientDisConnectProcessor = new DISCONNECTEventProcessor();
|
||||
|
||||
public RpcClientDemoByMain() {
|
||||
// 1. create a rpc client
|
||||
client = new RpcClient();
|
||||
// 2. add processor for connect and close event if you need
|
||||
client.addConnectionEventProcessor(ConnectionEventType.CONNECT, clientConnectProcessor);
|
||||
client.addConnectionEventProcessor(ConnectionEventType.CLOSE, clientDisConnectProcessor);
|
||||
// 3. do init
|
||||
client.init();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new RpcClientDemoByMain();
|
||||
RequestBody req = new RequestBody(2, "hello world sync");
|
||||
try {
|
||||
String res = (String) client.invokeSync(addr, req, 3000);
|
||||
System.out.println("invoke sync result = [" + res + "]");
|
||||
} catch (RemotingException e) {
|
||||
String errMsg = "RemotingException caught in oneway!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("interrupted!");
|
||||
}
|
||||
client.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
* 服务器端初始化
|
||||
|
||||
```java
|
||||
package com.alipay.remoting.demo;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.alipay.remoting.ConnectionEventType;
|
||||
import com.alipay.remoting.rpc.common.BoltServer;
|
||||
import com.alipay.remoting.rpc.common.CONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.DISCONNECTEventProcessor;
|
||||
import com.alipay.remoting.rpc.common.SimpleServerUserProcessor;
|
||||
|
||||
/**
|
||||
* a demo for rpc server, you can just run the main method to start a server
|
||||
*
|
||||
* @author tsui
|
||||
* @version $Id: RpcServerDemoByMain.java, v 0.1 2018-04-10 10:37 tsui Exp $
|
||||
*/
|
||||
public class RpcServerDemoByMain {
|
||||
static Logger logger = LoggerFactory
|
||||
.getLogger(RpcServerDemoByMain.class);
|
||||
|
||||
BoltServer server;
|
||||
|
||||
int port = 8999;
|
||||
|
||||
SimpleServerUserProcessor serverUserProcessor = new SimpleServerUserProcessor();
|
||||
CONNECTEventProcessor serverConnectProcessor = new CONNECTEventProcessor();
|
||||
DISCONNECTEventProcessor serverDisConnectProcessor = new DISCONNECTEventProcessor();
|
||||
|
||||
public RpcServerDemoByMain() {
|
||||
// 1. create a Rpc server with port assigned
|
||||
server = new BoltServer(port);
|
||||
// 2. add processor for connect and close event if you need
|
||||
server.addConnectionEventProcessor(ConnectionEventType.CONNECT, serverConnectProcessor);
|
||||
server.addConnectionEventProcessor(ConnectionEventType.CLOSE, serverDisConnectProcessor);
|
||||
// 3. register user processor for client request
|
||||
server.registerUserProcessor(serverUserProcessor);
|
||||
// 4. server start
|
||||
if (server.start()) {
|
||||
System.out.println("server start ok!");
|
||||
} else {
|
||||
System.out.println("server start failed!");
|
||||
}
|
||||
// server.getRpcServer().stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new RpcServerDemoByMain();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 2 进阶功能
|
||||
|
||||
### 请求上下文
|
||||
```java
|
||||
public void testOneway() throws InterruptedException {
|
||||
RequestBody req = new RequestBody(2, "hello world oneway");
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
try {
|
||||
InvokeContext invokeContext = new InvokeContext();
|
||||
client.oneway(addr, req, invokeContext);
|
||||
Assert.assertEquals("127.0.0.1", invokeContext.get(InvokeContext.CLIENT_LOCAL_IP));
|
||||
Assert.assertEquals("127.0.0.1", invokeContext.get(InvokeContext.CLIENT_REMOTE_IP));
|
||||
Assert.assertNotNull(invokeContext.get(InvokeContext.CLIENT_LOCAL_PORT));
|
||||
Assert.assertNotNull(invokeContext.get(InvokeContext.CLIENT_REMOTE_PORT));
|
||||
Assert.assertNotNull(invokeContext.get(InvokeContext.CLIENT_CONN_CREATETIME));
|
||||
logger.warn("CLIENT_CONN_CREATETIME:"
|
||||
+ invokeContext.get(InvokeContext.CLIENT_CONN_CREATETIME));
|
||||
Thread.sleep(100);
|
||||
} catch (RemotingException e) {
|
||||
String errMsg = "RemotingException caught in oneway!";
|
||||
logger.error(errMsg, e);
|
||||
Assert.fail(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
```
|
||||
|
||||
### 双工通信
|
||||
```java
|
||||
|
||||
@Test
|
||||
public void testServerSyncUsingConnection1() throws Exception {
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
RequestBody req1 = new RequestBody(1, RequestBody.DEFAULT_CLIENT_STR);
|
||||
String serverres = (String) client.invokeSync(addr, req1, 1000);
|
||||
Assert.assertEquals(serverres, RequestBody.DEFAULT_SERVER_RETURN_STR);
|
||||
|
||||
Assert.assertNotNull(serverConnectProcessor.getConnection());
|
||||
Connection serverConn = serverConnectProcessor.getConnection();
|
||||
RequestBody req = new RequestBody(1, RequestBody.DEFAULT_SERVER_STR);
|
||||
String clientres = (String) server.getRpcServer().invokeSync(serverConn, req, 1000);
|
||||
Assert.assertEquals(clientres, RequestBody.DEFAULT_CLIENT_RETURN_STR);
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerSyncUsingConnection() throws Exception {
|
||||
Connection clientConn = client.createStandaloneConnection(ip, port, 1000);
|
||||
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
RequestBody req1 = new RequestBody(1, RequestBody.DEFAULT_CLIENT_STR);
|
||||
String serverres = (String) client.invokeSync(clientConn, req1, 1000);
|
||||
Assert.assertEquals(serverres, RequestBody.DEFAULT_SERVER_RETURN_STR);
|
||||
|
||||
Assert.assertNotNull(serverConnectProcessor.getConnection());
|
||||
Connection serverConn = serverConnectProcessor.getConnection();
|
||||
RequestBody req = new RequestBody(1, RequestBody.DEFAULT_SERVER_STR);
|
||||
String clientres = (String) server.getRpcServer().invokeSync(serverConn, req, 1000);
|
||||
Assert.assertEquals(clientres, RequestBody.DEFAULT_CLIENT_RETURN_STR);
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerSyncUsingAddress() throws Exception {
|
||||
Connection clientConn = client.createStandaloneConnection(ip, port, 1000);
|
||||
String remote = RemotingUtil.parseRemoteAddress(clientConn.getChannel());
|
||||
String local = RemotingUtil.parseLocalAddress(clientConn.getChannel());
|
||||
logger.warn("Client say local:" + local);
|
||||
logger.warn("Client say remote:" + remote);
|
||||
|
||||
for (int i = 0; i < invokeTimes; i++) {
|
||||
RequestBody req1 = new RequestBody(1, RequestBody.DEFAULT_CLIENT_STR);
|
||||
String serverres = (String) client.invokeSync(clientConn, req1, 1000);
|
||||
Assert.assertEquals(serverres, RequestBody.DEFAULT_SERVER_RETURN_STR);
|
||||
|
||||
Assert.assertNotNull(serverConnectProcessor.getConnection());
|
||||
// only when client invoked, the remote address can be get by UserProcessor
|
||||
// otherwise, please use ConnectionEventProcessor
|
||||
String remoteAddr = serverUserProcessor.getRemoteAddr();
|
||||
RequestBody req = new RequestBody(1, RequestBody.DEFAULT_SERVER_STR);
|
||||
String clientres = (String) server.getRpcServer().invokeSync(remoteAddr, req, 1000);
|
||||
Assert.assertEquals(clientres, RequestBody.DEFAULT_CLIENT_RETURN_STR);
|
||||
}
|
||||
|
||||
Assert.assertTrue(serverConnectProcessor.isConnected());
|
||||
Assert.assertEquals(1, serverConnectProcessor.getConnectTimes());
|
||||
Assert.assertEquals(invokeTimes, serverUserProcessor.getInvokeTimes());
|
||||
}
|
||||
```
|
||||
|
||||
### 建立多连接与连接预热
|
||||
|
||||
|
||||
### 自动断连与重连
|
||||
|
||||
|
||||
### 序列化与反序列化器
|
||||
13
Junit/概述.md
13
Junit/概述.md
@@ -1,13 +0,0 @@
|
||||
## 概述
|
||||
|
||||
|
||||
|
||||
## 关键点
|
||||
|
||||
|
||||
1. 导入Springboot相关的依赖,spring-boot-starter
|
||||
2. 写测试类,添加@SpringbootTest。该注解能够增加Spring的上下文,及@Autowire进行bean的注入。
|
||||
3. 写测试方法,添加@Test注解,填写测试用例。通过Assertions方法,进行断言。
|
||||
4. @BeforeEach、@BeforeAll、@AfterEach、@AfterAll。能够在不同阶段执行相关的操作。
|
||||
5. 通过MockBean添加mock规则。使用when().thenReturn()方法进行mock掉一个bean的所有方法。可以在@BeforeEach中执行mock方法,或者@BeforeAll中执行。没配置的规则,返回默认值。
|
||||
6. 通过Spybean进行部分注Mock。首先注入bean,只mock配置规则的部分,没有配置规则的部分使用原来的方法。
|
||||
@@ -1,7 +1,8 @@
|
||||
supervisord
|
||||
===
|
||||
|
||||
配置后台服务/常驻进程的进程管家工具
|
||||
配置后台服务/常驻进程的进程管家工具。
|
||||
supervisord的出现,可以用来管理后台运行的程序。通过supervisorctl客户端来控制supervisord守护进程服务,真正进行进程监听的是supervisorctl客户端,而运行supervisor服务时是需要制定相应的supervisor配置文件的。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -10,6 +11,12 @@ supervisord
|
||||
apt-get install supervisor
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
Supervisord工具的整个使用流程:
|
||||
1. 首先通过echo_supervisord_conf 生成配置文件模板
|
||||
2. 然后你根据自己的需求进行修改,接着就使用相应的命令来使用supervisorctl客户端
|
||||
3. 而supervisorctl客户端会将对应的信息传递给supervisord守护进程服务,让supervisord守护进程服务进行进程守护。
|
||||
## 实例
|
||||
|
||||
生成配置文件 `/etc/supervisord.conf`
|
||||
@@ -27,9 +34,86 @@ supervisord: 启动 supervisor 服务
|
||||
supervisorctl start app
|
||||
supervisorctl stop app
|
||||
supervisorctl reload # 修改/添加配置文件需要执行这个
|
||||
supervisorctl status
|
||||
webserver RUNNING pid 1120, uptime 0:08:07
|
||||
```
|
||||
|
||||
启动supervisor程序
|
||||
|
||||
```shell
|
||||
supervisord -c /home/nianshi/supervisor/conf/supervisord.conf
|
||||
```
|
||||
|
||||
## 下载地址
|
||||
|
||||
https://pypi.python.org/pypi/meld3
|
||||
https://pypi.python.org/pypi/supervisor
|
||||
https://pypi.python.org/pypi/supervisor
|
||||
|
||||
|
||||
## 配置文件
|
||||
|
||||
一般apt安装后配置文件默认位置是/etc/supervisor/supervisord.conf。其中注释是以分号开头
|
||||
```shell
|
||||
#指定了socket file的位置
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ;UNIX socket 文件,supervisorctl 会使用
|
||||
;chmod=0700 ;socket文件的mode,默认是0700
|
||||
;chown=nobody:nogroup ;socket文件的owner,格式:uid:gid
|
||||
|
||||
#用于启动一个含有前端的服务,可以从Web页面中管理服务。其中,port用于设置访问地址,username和password用于设置授权认证。
|
||||
;[inet_http_server] ;HTTP服务器,提供web管理界面
|
||||
;port=127.0.0.1:9001 ;Web管理后台运行的IP和端口,如果开放到公网,需要注意安全性
|
||||
;username=user ;登录管理后台的用户名
|
||||
;password=123 ;登录管理后台的密码
|
||||
|
||||
# 管理服务本身的配置
|
||||
[supervisord]
|
||||
logfile=/tmp/supervisord.log ;日志文件,默认是 $CWD/supervisord.log
|
||||
logfile_maxbytes=50MB ;日志文件大小,超出会rotate,默认 50MB,如果设成0,表示不限制大小
|
||||
logfile_backups=10 ;日志文件保留备份数量默认10,设为0表示不备份
|
||||
loglevel=info ;日志级别,默认info,其它: debug,warn,trace
|
||||
pidfile=/tmp/supervisord.pid ;pid 文件
|
||||
nodaemon=false ;是否在前台启动,默认是false,即以 daemon 的方式启动
|
||||
minfds=1024 ;可以打开的文件描述符的最小值,默认 1024
|
||||
minprocs=200 ;可以打开的进程数的最小值,默认 200
|
||||
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ;通过UNIX socket连接supervisord,路径与unix_http_server部分的file一致
|
||||
;serverurl=http://127.0.0.1:9001 ; 通过HTTP的方式连接supervisord
|
||||
|
||||
; [program:xx]是被管理的进程配置参数,xx是进程的名称
|
||||
[program:xx]
|
||||
command=/opt/apache-tomcat-8.0.35/bin/catalina.sh run ; 程序启动命令
|
||||
autostart=true ; 在supervisord启动的时候也自动启动
|
||||
startsecs=10 ; 启动10秒后没有异常退出,就表示进程正常启动了,默认为1秒
|
||||
autorestart=true ; 程序退出后自动重启,可选值:[unexpected,true,false],默认为unexpected,表示进程意外杀死后才重启
|
||||
startretries=3 ; 启动失败自动重试次数,默认是3
|
||||
user=tomcat ; 用哪个用户启动进程,默认是root
|
||||
priority=999 ; 进程启动优先级,默认999,值小的优先启动
|
||||
redirect_stderr=true ; 把stderr重定向到stdout,默认false
|
||||
stdout_logfile_maxbytes=20MB ; stdout 日志文件大小,默认50MB
|
||||
stdout_logfile_backups = 20 ; stdout 日志文件备份数,默认是10
|
||||
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
|
||||
stdout_logfile=/opt/apache-tomcat-8.0.35/logs/catalina.out
|
||||
stopasgroup=false ;默认为false,进程被杀死时,是否向这个进程组发送stop信号,包括子进程
|
||||
killasgroup=false ;默认为false,向进程组发送kill信号,包括子进程
|
||||
# 对事件进行的管理
|
||||
;[eventlistener:theeventlistenername]
|
||||
|
||||
#对任务组的管理 ,包含其它配置文件
|
||||
;[group:thegroupname]
|
||||
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
|
||||
;priority=999 ; the relative start priority (default 999)
|
||||
|
||||
[include]
|
||||
files = supervisord.d/*.ini ;可以指定一个或多个以.ini结束的配置文件
|
||||
```
|
||||
|
||||
```shell
|
||||
command 要执行的命令
|
||||
priority 优先级
|
||||
numprocs 启动几个进程
|
||||
autostart supervisor启动的时候是否随着同时启动
|
||||
autorestart 当程序over的时候,这个program会自动重启,一定要选上
|
||||
```
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
JobDataMap
|
||||
|
||||
https://blog.csdn.net/qq_30859353/article/details/120533838
|
||||
|
||||
|
||||
JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
|
||||
|
||||
将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap,如下示例:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
> * 原生配置文件,xml定义的配置文件
|
||||
> * 属性配置文件:spring配置key-value的文件
|
||||
|
||||
## 1 properties配置基础
|
||||
## 1 properties配置文件
|
||||
|
||||
### properties默认配置文件
|
||||
用于配置容器端口名、数据库链接信息、日志级别。pom是项目编程的配置,properties是软件部署的配置。
|
||||
@@ -92,6 +92,55 @@ com.didispace.blog.test2=${random.int[10,20]}
|
||||
```
|
||||
|
||||
|
||||
### 读取规则
|
||||
|
||||
将配置文件中的值引入到java程序中。
|
||||
|
||||
在Spring应用程序的environment中读取属性的时候,每个属性的唯一名称符合如下规则:
|
||||
|
||||
* 通过.分离各个元素
|
||||
* 最后一个.将前缀与属性名称分开
|
||||
* 必须是字母(a-z)和数字(0-9)
|
||||
* 必须是小写字母
|
||||
* 用连字符-来分隔单词
|
||||
* 唯一允许的其他字符是[和],用于List的索引
|
||||
* 不能以数字开头
|
||||
|
||||
```
|
||||
this.environment.containsProperty("spring.jpa.database-platform")
|
||||
```
|
||||
|
||||
### 配置提示
|
||||
|
||||
引入配置提示的依赖。并在maven插件中,将该依赖排除。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
|
||||
|
||||
## 2 yaml配置文件
|
||||
### yaml基本语法
|
||||
|
||||
@@ -137,7 +186,7 @@ k:
|
||||
```
|
||||
|
||||
### yaml的实例
|
||||
```
|
||||
```java
|
||||
@Data
|
||||
public class Person {
|
||||
|
||||
@@ -195,6 +244,13 @@ person:
|
||||
## 3 其他配置方式
|
||||
### 系统环境变量
|
||||
|
||||
java程序启动参数 -D是用来做什么:
|
||||
Set a system property value. If value is a string that contains spaces, you must enclose the string in double quotes。在运行改程序时加上JVM参数-Ddubbo.token=“666” 或者 -Ddubbo.token=666,那么运行之后你可以看到控制台输出了666!一点值得注意的是,需要设置的是JVM参数而不是program参数
|
||||
|
||||
```
|
||||
java -Dfoo="some string" SomeClass
|
||||
```
|
||||
|
||||
* 列表形式:由于环境变量中无法使用[和]符号,所以使用_来替代。任何由下划线包围的数字都会被认为是[]的数组形式。
|
||||
```
|
||||
MY_FOO_1_ = my.foo[1]
|
||||
@@ -219,7 +275,7 @@ java -jar xxx.jar --server.port=8888
|
||||
|
||||
## 4 多环境配置
|
||||
|
||||
### 配置方法
|
||||
### 多环境配置文件
|
||||
对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包。
|
||||
|
||||
在Spring Boot中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:
|
||||
@@ -228,20 +284,67 @@ java -jar xxx.jar --server.port=8888
|
||||
* application-test.properties:测试环境
|
||||
* application-prod.properties:生产环境
|
||||
|
||||
|
||||
### 多配置文件的加载规则
|
||||
application.yml/properties总是会被加载,不管是否配置spring.profile.active.
|
||||
|
||||
|
||||
1. 配置文件的方式。在默认配置文件中application.properties指定环境。最先加载application.yml/properties,然后再按照spring.profile.active加载相应的application-{profile}.yml(properties)。如果application和application-{profile}中键有重复会被application-{profile}替换为最新的。
|
||||
2. Java系统属性方式。在启动jar时指定加载配置(Java系统属性方式)或者通过java代码设置系统属性的方式。
|
||||
```java
|
||||
// -Dspring.profiles.active=mydev123一定要放-jar之前能触发java属性方式
|
||||
java -Dspring.profiles.active=mydev123 -jar SpringBootEnv-1.0.jar
|
||||
|
||||
// 通过java代码设置系统属性的方式。
|
||||
System.setProperty("spring.profiles.active","mydev");
|
||||
```
|
||||
3. 命令行的方式。在启动jar时指定加载配置(命令行方式)
|
||||
```shell
|
||||
java -jar SpringBootEnv-1.0.jar --spring.profiles.active=dev56789
|
||||
```
|
||||
4. 系统环境变量的方式。在启动jar时指定加载配置(系统环境变量方式)。首先增加一个名称为SPRING_PROFILES_ACTIVE的系统环境变量,启动spring程序。
|
||||
|
||||
```shell
|
||||
#当前系统是windows
|
||||
set SPRING_PROFILES_ACTIVE=dev987
|
||||
java -jar SpringBootEnv-1.0.jar
|
||||
```
|
||||
|
||||
以上四种方式的优先级
|
||||
```
|
||||
命令行方式 > Java系统属性方式 > 系统环境变量方式 > 配置文件方式
|
||||
```
|
||||
如果需要激活多个profile可以使用逗号隔开,
|
||||
```
|
||||
spring.profiles.active=dev,test
|
||||
```
|
||||
|
||||
|
||||
### 配置加载顺序
|
||||
|
||||
SpringBoot启动会扫描以下位置的application.properties/yml文件作为spring boot的默认配置文件:
|
||||
|
||||
```
|
||||
#file: 指当前项目根目录
|
||||
file:./config/
|
||||
file:./
|
||||
#classpath: 指当前项目的resources目录
|
||||
classpath:/config/
|
||||
classpath:
|
||||
```
|
||||
|
||||
1. 命令行中传入的参数。
|
||||
1. SPRING_APPLICATION_JSON中的属性。SPRING_APPLICATION_JSON是以JSON格式配置在系统环境变量中的内容。
|
||||
1. java:comp/env中的JNDI属性。
|
||||
1. Java的系统属性,可以通过System.getProperties()获得的内容。
|
||||
1. 操作系统的环境变量
|
||||
1. 通过random.*配置的随机属性
|
||||
1. 位于当前应用jar包之外,针对不同{profile}环境的配置文件内容,例如:application-{profile}.properties或是YAML定义的配置文件
|
||||
1. 位于当前应用jar包之内,针对不同{profile}环境的配置文件内容,例如:application-{profile}.properties或是YAML定义的配置文件
|
||||
1. 位于当前应用jar包之外的application.properties和YAML配置内容
|
||||
1. 位于当前应用jar包之内的application.properties和YAML配置内容
|
||||
1. 在@Configuration注解修改的类中,通过@PropertySource注解定义的属性
|
||||
1. 应用默认属性,使用SpringApplication.setDefaultProperties定义的内容1.
|
||||
2. SPRING_APPLICATION_JSON中的属性。SPRING_APPLICATION_JSON是以JSON格式配置在系统环境变量中的内容。
|
||||
3. java:comp/env中的JNDI属性。
|
||||
4. Java的系统属性,可以通过System.getProperties()获得的内容。
|
||||
5. 操作系统的环境变量
|
||||
6. 通过random.*配置的随机属性
|
||||
7. 位于当前应用jar包之外,针对不同{profile}环境的配置文件内容,例如:application-{profile}.properties或是YAML定义的配置文件
|
||||
8. 位于当前应用jar包之内,针对不同{profile}环境的配置文件内容,例如:application-{profile}.properties或是YAML定义的配置文件
|
||||
9. 位于当前应用jar包之外的application.properties和YAML配置内容
|
||||
10. 位于当前应用jar包之内的application.properties和YAML配置内容
|
||||
11. 在@Configuration注解修改的类中,通过@PropertySource注解定义的属性
|
||||
12. 应用默认属性,使用SpringApplication.setDefaultProperties定义的内容1.
|
||||
|
||||
## 5 配置绑定
|
||||
### 使用Java程序读取
|
||||
@@ -261,53 +364,6 @@ public class getProperties {
|
||||
}
|
||||
}
|
||||
```
|
||||
### 读取规则
|
||||
|
||||
将配置文件中的值引入到java程序中。
|
||||
|
||||
在Spring应用程序的environment中读取属性的时候,每个属性的唯一名称符合如下规则:
|
||||
|
||||
* 通过.分离各个元素
|
||||
* 最后一个.将前缀与属性名称分开
|
||||
* 必须是字母(a-z)和数字(0-9)
|
||||
* 必须是小写字母
|
||||
* 用连字符-来分隔单词
|
||||
* 唯一允许的其他字符是[和],用于List的索引
|
||||
* 不能以数字开头
|
||||
|
||||
```
|
||||
this.environment.containsProperty("spring.jpa.database-platform")
|
||||
```
|
||||
|
||||
### 配置提示
|
||||
|
||||
引入配置提示的依赖。并在maven插件中,将该依赖排除。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
|
||||
|
||||
### @ConfigurationProperties+Componet
|
||||
@@ -347,7 +403,7 @@ public class Application {
|
||||
```
|
||||
|
||||
|
||||
### @Configuration+ @EnableConfigurationProperties
|
||||
### @ConfigurationProperties+ @EnableConfigurationProperties
|
||||
|
||||
1. 在配置类上开启属性配置功能。开启car的属性配置功能
|
||||
2. 该中方法对配置类进行修改然后装配。不需要修改类本身的代码。
|
||||
@@ -372,22 +428,157 @@ public class FooProperties {
|
||||
|
||||
|
||||
### @Value
|
||||
* 通过占位符的方式加载自定义的参数
|
||||
通过占位符的方式加载自定义的参数
|
||||
|
||||
```
|
||||
@Component
|
||||
public class Book {
|
||||
* 注入普通字符;
|
||||
* 注入操作系统属性;
|
||||
* 注入表达式运算结果;
|
||||
* 注入其他Bean的属性;
|
||||
* 注入文件内容;
|
||||
* 注入网址内容;
|
||||
* 注入属性文件。
|
||||
|
||||
@Value("${book.name}")
|
||||
private String name;
|
||||
@Value("${book.author}")
|
||||
private String author;
|
||||
```java
|
||||
|
||||
// 省略getter和setter
|
||||
/**
|
||||
* 配置类
|
||||
**/
|
||||
@Configuration
|
||||
@ComponentScan("com.kongzi")
|
||||
@PropertySource("classpath:db.properties")
|
||||
public class ElConfig
|
||||
{
|
||||
/**
|
||||
* 注入普通字符串
|
||||
*/
|
||||
@Value("您好,欢迎访问 carefree 的博客")
|
||||
private String comment;
|
||||
|
||||
/**
|
||||
* 注入操作系统属性
|
||||
*/
|
||||
@Value("#{systemProperties['os.name']}")
|
||||
private String osName;
|
||||
|
||||
/**
|
||||
* 注入表达式运算结果
|
||||
*/
|
||||
@Value("#{ T(java.lang.Math).random() * 100.0 }")
|
||||
private double randomNumber;
|
||||
|
||||
/**
|
||||
* 注入其他Bean的属性
|
||||
*/
|
||||
@Value("#{otherUser.userName}")
|
||||
private String fromUserName;
|
||||
|
||||
@Value("#{otherUser.blogUrl}")
|
||||
private String fromBlogUrl;
|
||||
|
||||
/**
|
||||
* 注入文件资源
|
||||
*/
|
||||
@Value("classpath:info.txt")
|
||||
private Resource testFile;
|
||||
|
||||
/**
|
||||
* 注入网址资源
|
||||
*/
|
||||
@Value("https://blog.csdn.net/carefree31441")
|
||||
private Resource testUrl;
|
||||
|
||||
/**
|
||||
* 注入配置文件
|
||||
*/
|
||||
@Value("${jdbc.driver}")
|
||||
private String jdbc_driver;
|
||||
|
||||
@Value("${jdbc.url}")
|
||||
private String jdbc_url;
|
||||
|
||||
@Value("${jdbc.username}")
|
||||
private String jdbc_username;
|
||||
|
||||
@Value("${jdbc.password}")
|
||||
private String jdbc_password;
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer propertyConfigurer()
|
||||
{
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
public void outputResource()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.out.println("注入普通字符串:");
|
||||
System.out.println(comment);
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入操作系统属性:");
|
||||
System.out.println(osName);
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入表达式运算结果:");
|
||||
System.out.println(randomNumber);
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入其他Bean的属性:");
|
||||
System.out.println("用户名称:" + fromUserName);
|
||||
System.out.println("博客地址:"+ fromBlogUrl);
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入文件资源:");
|
||||
System.out.println("文件中的内容:" + IOUtils.toString(testFile.getInputStream()));
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入配置文件(方式一):");
|
||||
System.out.println("数据库驱动:" + jdbc_driver);
|
||||
System.out.println("数据库连接:" + jdbc_url);
|
||||
System.out.println("数据库用户:" + jdbc_username);
|
||||
System.out.println("数据库密码:" + jdbc_password);
|
||||
System.out.println("------------------------------------------------");
|
||||
|
||||
System.out.println("注入配置文件(方式二):");
|
||||
System.out.println("数据库驱动:" + environment.getProperty("jdbc.driver"));
|
||||
System.out.println("数据库连接:" + environment.getProperty("jdbc.url"));
|
||||
System.out.println("数据库用户:" + environment.getProperty("jdbc.username"));
|
||||
System.out.println("数据库密码:" + environment.getProperty("jdbc.password"));
|
||||
System.out.println("------------------------------------------------");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* @Value支持的表达式格式
|
||||
|
||||
### @PropertySource注解加载properties文件
|
||||
PropertySource注解的作用是加载指定的属性文件,配置属性如下
|
||||
```
|
||||
#{...}
|
||||
${...}
|
||||
@PropertySource(value= {"classpath:config/mail.properties"},ignoreResourceNotFound=false,encoding="UTF-8",name="mail.properties")
|
||||
```
|
||||
* 其中value是设置需要加载的属性文件,可以一次性加载多个(多个时以,分隔);
|
||||
* encoding用于指定读取属性文件所使用的编码,我们通常使用的是UTF-8;
|
||||
* ignoreResourceNotFound含义是当指定的配置文件不存在是否报错,默认是false;如这里配置的config/mail.properties,若在classpath路径下不存在时,则ignoreResourceNotFound为true的时候,程序不会报错,如果ignoreResourceNotFound为false的时候,程序直接报错。实际项目开发中,最好设置ignoreResourceNotFound为false,该参数默认值为false。
|
||||
* name的值我们设置的是mail.properties。这个值在Springboot的环境中必须是唯一的,如果不设置,则值为:“class path resource [config/mail.properties]“。
|
||||
|
||||
|
||||
```java
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mail")
|
||||
@PropertySource(value = "classpath:config/mail.properties",encoding = "UTF-8")
|
||||
public class MailConfig
|
||||
{
|
||||
private String host;
|
||||
|
||||
private String port;
|
||||
|
||||
//省略getter与setter方法...
|
||||
}
|
||||
```
|
||||
@@ -21,10 +21,10 @@
|
||||
@startuml
|
||||
|
||||
object HttpServlet{
|
||||
doGet()
|
||||
doPost()
|
||||
doPut()
|
||||
doDelete()
|
||||
doGet
|
||||
doPost
|
||||
doPut
|
||||
doDelete
|
||||
}
|
||||
note bottom : 这是所有http请求的入口。\
|
||||
\n 可以通过子类,不断丰富和具体要执行的内容。
|
||||
@@ -35,16 +35,16 @@ object HttpServletBean{
|
||||
}
|
||||
|
||||
object FrameworkServlet{
|
||||
doGet()--> processRequest()
|
||||
doPost()--> processRequest()
|
||||
doPut()--> processRequest()
|
||||
doDelete() --> processRequest()
|
||||
processRequest()-->doService()
|
||||
doGet--> processRequest
|
||||
doPost --> processRequest
|
||||
doPut --> processRequest
|
||||
doDelete --> processRequest
|
||||
processRequest -->doService
|
||||
}
|
||||
|
||||
object DispatcherServlet{
|
||||
doService()-->doDispatcher()
|
||||
doDispatcher()
|
||||
doService -->doDispatcher
|
||||
doDispatcher
|
||||
}
|
||||
|
||||
|
||||
|
||||
216
Spring/Springboot/springboot-test.md
Normal file
216
Spring/Springboot/springboot-test.md
Normal file
@@ -0,0 +1,216 @@
|
||||
|
||||
> springboot with junit4 &junit5 https://segmentfault.com/a/1190000040803747
|
||||
|
||||
## 1 概述
|
||||
### 多种测试模式
|
||||
|
||||
|
||||
* @RunWith(SpringJUnit4ClassRunner.class)启动Spring上下文环境。
|
||||
* @RunWith(MockitoJUnitRunner.class)mockito方法进行测试。对底层的类进行mock,测试速度快。
|
||||
* @RunWith(PowerMockRunner.class)powermock方法进行测试。对底层类进行mock,测试方法更全面。
|
||||
|
||||
### spring-boot-starter-test
|
||||
SpringBoot中有关测试的框架,主要来源于 spring-boot-starter-test。一旦依赖了spring-boot-starter-test,下面这些类库将被一同依赖进去:
|
||||
* JUnit:java测试事实上的标准。
|
||||
* Spring Test & Spring Boot Test:Spring的测试支持。
|
||||
* AssertJ:提供了流式的断言方式。
|
||||
* Hamcrest:提供了丰富的matcher。
|
||||
* Mockito:mock框架,可以按类型创建mock对象,可以根据方法参数指定特定的响应,也支持对于mock调用过程的断言。
|
||||
* JSONassert:为JSON提供了断言功能。
|
||||
* JsonPath:为JSON提供了XPATH功能。
|
||||
|
||||
|
||||
|
||||
### junit4 & junit5对比
|
||||
|
||||
|
||||
| 功能 | JUnit4 | JUnit5 |
|
||||
|------------------|--------------|--------------|
|
||||
| 声明一种测试方法 | @Test | @Test |
|
||||
| 在当前类中的所有测试方法之前执行 | @BeforeClass | @BeforeAll |
|
||||
| 在当前类中的所有测试方法之后执行 | @AfterClass | @AfterAll |
|
||||
| 在每个测试方法之前执行 | @Before | @BeforeEach |
|
||||
| 在每个测试方法之后执行 | @After | @AfterEach |
|
||||
| 禁用测试方法/类 | @Ignore | @Disabled |
|
||||
| 测试工厂进行动态测试 | NA | @TestFactory |
|
||||
| 嵌套测试 | NA | @Nested |
|
||||
| 标记和过滤 | @Category | @Tag |
|
||||
| 注册自定义扩展 | NA | @ExtendWith |
|
||||
|
||||
|
||||
### RunWith 和 ExtendWith
|
||||
|
||||
在 JUnit4 版本,在测试类加 @SpringBootTest 注解时,同样要加上 @RunWith(SpringRunner.class)才生效,即:
|
||||
```
|
||||
@SpringBootTest
|
||||
@RunWith(SpringRunner.class)
|
||||
class HrServiceTest {
|
||||
...
|
||||
}
|
||||
```
|
||||
但在 JUnit5 中,官网告知 @RunWith 的功能都被 @ExtendWith 替代,即原 @RunWith(SpringRunner.class) 被同功能的 @ExtendWith(SpringExtension.class) 替代。但 JUnit5 中 @SpringBootTest 注解中已经默认包含了 @ExtendWith(SpringExtension.class)。
|
||||
|
||||
因此,在 JUnit5 中只需要单独使用 @SpringBootTest 注解即可。其他需要自定义拓展的再用 @ExtendWith,不要再用 @RunWith 了。
|
||||
|
||||
|
||||
### mockito
|
||||
|
||||
Mockito 框架中最核心的两个概念就是 Mock 和 Stub。测试时不是真正的操作外部资源,而是通过自定义的代码进行模拟操作。我们可以对任何的依赖进行模拟,从而使测试的行为不需要任何准备工作或者不具备任何副作用。
|
||||
|
||||
1. 当我们在测试时,如果只关心某个操作是否执行过,而不关心这个操作的具体行为,这种技术称为 mock。比如我们测试的代码会执行发送邮件的操作,我们对这个操作进行 mock;测试的时候我们只关心是否调用了发送邮件的操作,而不关心邮件是否确实发送出去了。
|
||||
|
||||
2. 另一种情况,当我们关心操作的具体行为,或者操作的返回结果的时候,我们通过执行预设的操作来代替目标操作,或者返回预设的结果作为目标操作的返回结果。这种对操作的模拟行为称为 stub(打桩)。比如我们测试代码的异常处理机制是否正常,我们可以对某处代码进行 stub,让它抛出异常。再比如我们测试的代码需要向数据库插入一条数据,我们可以对插入数据的代码进行stub,让它始终返回1,表示数据插入成功。
|
||||
|
||||
### powermock
|
||||
|
||||
需要手动引入测试类。依赖mockito,注意版本的对应关系。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
</dependency>
|
||||
|
||||
```
|
||||
## 2 使用
|
||||
|
||||
### 注解
|
||||
|
||||
@RunWith:
|
||||
1. 表示运行方式,@RunWith(JUnit4TestRunner)、@RunWith(SpringRunner.class)、@RunWith(PowerMockRunner.class) 三种运行方式,分别在不同的场景中使用。
|
||||
2. 当一个类用@RunWith注释或继承一个用@RunWith注释的类时,JUnit将调用它所引用的类来运行该类中的测试而不是开发者去在junit内部去构建它。我们在开发过程中使用这个特性
|
||||
|
||||
@SpringBootTest:
|
||||
1. 注解制定了一个测试类运行了Spring Boot环境。提供了以下一些特性:
|
||||
1. 当没有特定的ContextConfiguration#loader()(@ContextConfiguration(loader=...))被定义那么就是SpringBootContextLoader作为默认的ContextLoader。
|
||||
2. 自动搜索到SpringBootConfiguration注解的文件。
|
||||
3. 允许自动注入Environment类读取配置文件。
|
||||
4. 提供一个webEnvironment环境,可以完整的允许一个web环境使用随机的端口或者自定义的端口。
|
||||
5. 注册了TestRestTemplate类可以去做接口调用。
|
||||
|
||||
2. 添加这个就能取到spring中的容器的实例,如果配置了@Autowired那么就自动将对象注入。
|
||||
|
||||
### 基本测试用例
|
||||
|
||||
Springboot整合JUnit的步骤
|
||||
1. 导入测试对应的starter
|
||||
|
||||
2. 测试类使用@SpringBootTest修饰
|
||||
|
||||
3.使用自动装配的形式添加要测试的对象。
|
||||
|
||||
```java
|
||||
@SpringBootTest(classes = {SpringbootJunitApplication.class})
|
||||
class SpringbootJunitApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private UsersDao users;
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
users.save();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### mock单元测试
|
||||
因为单元测试不用启动 Spring 容器,则无需加 @SpringBootTest,因为要用到 Mockito,只需要自定义拓展 MockitoExtension.class 即可,依赖简单,运行速度更快。
|
||||
|
||||
可以明显看到,单元测试写的代码,怎么是被测试代码长度的好几倍?其实单元测试的代码长度比较固定,都是造数据和打桩,但如果针对越复杂逻辑的代码写单元测试,还是越划算的
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HrServiceTest {
|
||||
@Mock
|
||||
private OrmDepartmentDao ormDepartmentDao;
|
||||
@Mock
|
||||
private OrmUserDao ormUserDao;
|
||||
@InjectMocks
|
||||
private HrService hrService;
|
||||
|
||||
@DisplayName("根据部门名称,查询用户")
|
||||
@Test
|
||||
void findUserByDeptName() {
|
||||
Long deptId = 100L;
|
||||
String deptName = "行政部";
|
||||
OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO();
|
||||
ormDepartmentPO.setId(deptId);
|
||||
ormDepartmentPO.setDepartmentName(deptName);
|
||||
OrmUserPO user1 = new OrmUserPO();
|
||||
user1.setId(1L);
|
||||
user1.setUsername("001");
|
||||
user1.setDepartmentId(deptId);
|
||||
OrmUserPO user2 = new OrmUserPO();
|
||||
user2.setId(2L);
|
||||
user2.setUsername("002");
|
||||
user2.setDepartmentId(deptId);
|
||||
List<OrmUserPO> userList = new ArrayList<>();
|
||||
userList.add(user1);
|
||||
userList.add(user2);
|
||||
|
||||
Mockito.when(ormDepartmentDao.findOneByDepartmentName(deptName))
|
||||
.thenReturn(
|
||||
Optional.ofNullable(ormDepartmentPO)
|
||||
.filter(dept -> deptName.equals(dept.getDepartmentName()))
|
||||
);
|
||||
Mockito.doReturn(
|
||||
userList.stream()
|
||||
.filter(user -> deptId.equals(user.getDepartmentId()))
|
||||
.collect(Collectors.toList())
|
||||
).when(ormUserDao).findByDepartmentId(deptId);
|
||||
|
||||
List<OrmUserPO> result1 = hrService.findUserByDeptName(deptName);
|
||||
List<OrmUserPO> result2 = hrService.findUserByDeptName(deptName + "error");
|
||||
|
||||
Assertions.assertEquals(userList, result1);
|
||||
Assertions.assertEquals(Collections.emptyList(), result2);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 集成单元测试
|
||||
还是那个方法,如果使用Spring上下文,真实的调用方法依赖,可直接用下列方式
|
||||
```java
|
||||
@SpringBootTest
|
||||
class HrServiceTest {
|
||||
@Autowired
|
||||
private HrService hrService;
|
||||
|
||||
@DisplayName("根据部门名称,查询用户")
|
||||
@Test
|
||||
void findUserByDeptName() {
|
||||
List<OrmUserPO> userList = hrService.findUserByDeptName("行政部");
|
||||
Assertions.assertTrue(userList.size() > 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
还可以使用@MockBean、@SpyBean替换Spring上下文中的对应的Bean:
|
||||
```java
|
||||
@SpringBootTest
|
||||
class HrServiceTest {
|
||||
@Autowired
|
||||
private HrService hrService;
|
||||
@SpyBean
|
||||
private OrmDepartmentDao ormDepartmentDao;
|
||||
|
||||
@DisplayName("根据部门名称,查询用户")
|
||||
@Test
|
||||
void findUserByDeptName() {
|
||||
String deptName="行政部";
|
||||
OrmDepartmentPO ormDepartmentPO = new OrmDepartmentPO();
|
||||
ormDepartmentPO.setDepartmentName(deptName);
|
||||
Mockito.when(ormDepartmentDao.findOneByDepartmentName(ArgumentMatchers.anyString()))
|
||||
.thenReturn(Optional.of(ormDepartmentPO));
|
||||
List<OrmUserPO> userList = hrService.findUserByDeptName(deptName);
|
||||
Assertions.assertTrue(userList.size() > 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user