From 070f04f1de9407bf089bdef9f40287566726d231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B3=95=E7=84=B6?= Date: Thu, 24 Nov 2022 11:11:33 +0800 Subject: [PATCH] java & sofabolt --- Java三方库/Junit.md | 571 ++++++++++++ Java三方库/mockito.md | 840 ++++++++++++++++++ Java基础教程/Java并发编程/sofabolt启发.md | 834 +++++++++++++++++ Junit/概述.md | 13 - Linux/Linux工具命令/supervisord.md | 88 +- Quartz/5 JobDataMap.md | 4 + ...性配置文件.md => 04 Properties配置文件.md} | 339 +++++-- Spring/Springboot/06 web开发.md | 22 +- Spring/Springboot/springboot-test.md | 216 +++++ 9 files changed, 2827 insertions(+), 100 deletions(-) create mode 100644 Java三方库/Junit.md create mode 100644 Java三方库/mockito.md create mode 100644 Java基础教程/Java并发编程/sofabolt启发.md delete mode 100644 Junit/概述.md rename Spring/Springboot/{04 属性配置文件.md => 04 Properties配置文件.md} (52%) create mode 100644 Spring/Springboot/springboot-test.md diff --git a/Java三方库/Junit.md b/Java三方库/Junit.md new file mode 100644 index 00000000..6430bb9a --- /dev/null +++ b/Java三方库/Junit.md @@ -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. 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 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 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().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 Matcher isException(Matcher exceptionMatcher) +static Matcher isThrowable Matcher 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); + } +} +``` + + + + diff --git a/Java三方库/mockito.md b/Java三方库/mockito.md new file mode 100644 index 00000000..7af8cddd --- /dev/null +++ b/Java三方库/mockito.md @@ -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 + + org.mockito + mockito-all + 1.9.5 + test + + + junit + junit + 4.11 + test + +``` + + +### 添加引用 + +```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 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 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() { + @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 { +} +``` \ No newline at end of file diff --git a/Java基础教程/Java并发编程/sofabolt启发.md b/Java基础教程/Java并发编程/sofabolt启发.md new file mode 100644 index 00000000..9da65548 --- /dev/null +++ b/Java基础教程/Java并发编程/sofabolt启发.md @@ -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 rets = new ArrayList(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 { + + /** 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(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(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 timeoutDiscard. + * + * @return property value of timeoutDiscard + */ + public boolean isTimeoutDiscard() { + return timeoutDiscard; + } + + /** + * Setter method for property timeoutDiscard. + * + * @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 { + + /** 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(4), new NamedThreadFactory("Request-process-pool")); + this.asyncExecutor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS, + new ArrayBlockingQueue(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(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()); + } +``` + +### 建立多连接与连接预热 + + +### 自动断连与重连 + + +### 序列化与反序列化器 \ No newline at end of file diff --git a/Junit/概述.md b/Junit/概述.md deleted file mode 100644 index 69289e35..00000000 --- a/Junit/概述.md +++ /dev/null @@ -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配置规则的部分,没有配置规则的部分使用原来的方法。 \ No newline at end of file diff --git a/Linux/Linux工具命令/supervisord.md b/Linux/Linux工具命令/supervisord.md index 166feba9..4a0ec7bd 100644 --- a/Linux/Linux工具命令/supervisord.md +++ b/Linux/Linux工具命令/supervisord.md @@ -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 \ No newline at end of file +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会自动重启,一定要选上 +``` diff --git a/Quartz/5 JobDataMap.md b/Quartz/5 JobDataMap.md index 88c5ca0c..7807e976 100644 --- a/Quartz/5 JobDataMap.md +++ b/Quartz/5 JobDataMap.md @@ -1,4 +1,8 @@ JobDataMap + +https://blog.csdn.net/qq_30859353/article/details/120533838 + + JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。 将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap,如下示例: diff --git a/Spring/Springboot/04 属性配置文件.md b/Spring/Springboot/04 Properties配置文件.md similarity index 52% rename from Spring/Springboot/04 属性配置文件.md rename to Spring/Springboot/04 Properties配置文件.md index b0d525aa..f5154eb8 100644 --- a/Spring/Springboot/04 属性配置文件.md +++ b/Spring/Springboot/04 Properties配置文件.md @@ -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 + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + + + + +``` + + ## 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 - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.springframework.boot - spring-boot-configuration-processor - - - - - - -``` ### @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方法... +} +``` \ No newline at end of file diff --git a/Spring/Springboot/06 web开发.md b/Spring/Springboot/06 web开发.md index 14ea986a..6c5b53ac 100644 --- a/Spring/Springboot/06 web开发.md +++ b/Spring/Springboot/06 web开发.md @@ -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 } diff --git a/Spring/Springboot/springboot-test.md b/Spring/Springboot/springboot-test.md new file mode 100644 index 00000000..1615fe70 --- /dev/null +++ b/Spring/Springboot/springboot-test.md @@ -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 + + org.powermock + powermock-api-mockito2 + + + + org.powermock + powermock-module-junit4 + + +``` +## 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 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 result1 = hrService.findUserByDeptName(deptName); + List 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 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 userList = hrService.findUserByDeptName(deptName); + Assertions.assertTrue(userList.size() > 0); + } +} +``` \ No newline at end of file