java & sofabolt

This commit is contained in:
法然
2022-11-24 11:11:33 +08:00
parent d805c659e2
commit 070f04f1de
9 changed files with 2827 additions and 100 deletions

571
Java三方库/Junit.md Normal file
View 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. 使用@RunWithParameterized.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
*/
//使用 @RunWithParameterized.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 "));
// 集合匹配符
// hasItemIterable变量中含有指定元素时测试通过
assertThat(actual, hasItem("Magci"));
// hasEntryMap变量中含有指定键值对时测试通过
assertThat(actual, hasEntry("mgc", "Magci"));
// hasKeyMap变量中含有指定键时测试通过
assertThat(actual, hasKey("mgc"));
// hasValueMap变量中含有指定值时测试通过
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
View 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 {
}
```

View 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());
}
```
### 建立多连接与连接预热
### 自动断连与重连
### 序列化与反序列化器

View File

@@ -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配置规则的部分没有配置规则的部分使用原来的方法。

View File

@@ -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会自动重启一定要选上
```

View File

@@ -1,4 +1,8 @@
JobDataMap
https://blog.csdn.net/qq_30859353/article/details/120533838
JobDataMap中可以包含不限量的序列化的数据对象在job实例执行的时候可以使用其中的数据JobDataMap是Java Map接口的一个实现额外增加了一些便于存取基本类型的数据的方法。
将job加入到scheduler之前在构建JobDetail时可以将数据放入JobDataMap如下示例

View File

@@ -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}.ymlproperties。如果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方法...
}
```

View File

@@ -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
}

View 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下面这些类库将被一同依赖进去
* JUnitjava测试事实上的标准。
* Spring Test & Spring Boot TestSpring的测试支持。
* AssertJ提供了流式的断言方式。
* Hamcrest提供了丰富的matcher。
* Mockitomock框架可以按类型创建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);
}
}
```