diff --git a/mybatis/mybatis-plus.md b/mybatis/mybatis-plus.md deleted file mode 100644 index 558209d..0000000 --- a/mybatis/mybatis-plus.md +++ /dev/null @@ -1 +0,0 @@ -# Mybatis Plus diff --git a/spring/spring test/SpringTest.md b/spring/spring test/SpringTest.md index f6c5379..4070272 100644 --- a/spring/spring test/SpringTest.md +++ b/spring/spring test/SpringTest.md @@ -63,3 +63,477 @@ spring TestContext framework支持一致导入`ApplicationContext`和`WebApplica ### Test Fixtures依赖注入 当TestContext framework导入应用上下文时,其可以针对test class使用依赖注入。并且,可以跨测试场景重复使用应用程序上下文,避免在独立的测试用例之间重复执行fixture设置。 +### 事务管理 +TextContext framework默认会为每个test都创建并回滚一个事务。在编写test代码时,可以假定已经存在事务。默认情况下,test方法执行完后事务会被回滚,数据库状态会恢复到执行前的状态。 + +如果想要令test方法执行后事务被提交,可以使用`@Commit`注解。 + +### JDBC Support +`org.springframework.test.jdbc`包中包含`JdbcTestUtils`,其中包含了一系列jdbc相关的工具方法。 + +## Spring TestContext Framework +Spring TestContext Framework提供了通用的、注解驱动的单元测试和集成测试支持,并且该支持和底层的测试框架无关。并且,TestContext Framework的约定大于配置,并可以通过注解来覆盖默认值。 + +### Context Management +每个`TestContext`都为其负责的测试类提供了上下文管理和caching支持。测试类实例并不直接访问配置好的`ApplicationContext`,但如果test class实现了`ApplciationContextAware`接口,那么测试类中将包含field指向ApplicationContext的引用。 + +在使用TestContext Framework时,test class并不需要继承特定类或实现特定接口来配置test class所属的application context,相应的,application context的配置是通过在类级别指定`@ContextConfiguration`来实现的。如果test class没有显式的声明application context resource location或component classes,那么配置好的`ContextLoader`将会决定如何从默认location或默认configuration classes中加载context。 + +#### Context Configuration with Xml Resource +如果要通过xml配置的方式来导入`ApplicationContext`,可以指定`@ContextConfiguration`中的`location`属性,其是执行xml文件路径的数组。以`/`开头的路径代表classpath,而相对路径则是代表相对class文件的路径。示例如下: +```java +@ExtendWith(SpringExtension.class) +// ApplicationContext will be loaded from "/app-config.xml" and +// "/test-config.xml" in the root of the classpath +@ContextConfiguration(locations = {"/app-config.xml", "/test-config.xml"}) +class MyTest { + // class body... +} +``` +如果@ContextConfiguration省略了`location`和`value`属性,那么其默认会探测test class所在的路径。例如`com.example.MyTest`类,其默认会探测`classpath:com/example/MyTest-context.xml`。 + +#### Context Configuration with Component Classes +为test加载ApplicationContext时,可以通过componet classes来实现。为此,可以为test class指定`@ConfigurationContext`注解,并且在注解的`classes`属性指定一个class数组,其实现如下: +```java +@ExtendWith(SpringExtension.class) +// ApplicationContext will be loaded from AppConfig and TestConfig +@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) +class MyTest { + // class body... +} +``` +> 其中,component classes可以指定如下的任何一种对象: +> - 有`@Configuration`注解的类 +> - 有`@Component`、`@Service`、`@Repository`等注解的类 +> - 有`@Bean`注解方法返回的bean对象所属类 +> - 其他任何被注册为spring bean对象的类 + +如果,在指定@ConfigurationContext注解时,省略了`classes`属性,那么TestContext Framework会尝试查找是否存在默认的classes。`AnnotationConfigContextLoader`和`AnnotationConfigWebContextLoader`会查找Test Class中所有的静态内部类,并判断其是否符合configuration class需求。 + +示例如下所示: +```java +@SpringJUnitConfig +// ApplicationContext will be loaded from the static nested Config class +class OrderServiceTest { + + @Configuration + static class Config { + + // this bean will be injected into the OrderServiceTest class + @Bean + OrderService orderService() { + OrderService orderService = new OrderServiceImpl(); + // set properties, etc. + return orderService; + } + } + + @Autowired + OrderService orderService; + + @Test + void testOrderService() { + // test the orderService + } + +} +``` + +#### Context Configuration继承 +`@ContextConfiguration`支持从父类继承componet classes、resource locations、context initializers。可以指定`inheritLocations`和`inheritInitializers`来决定是否继承父类componet classes、resource locations、context initializers,两属性默认值为`true`。 + +示例如下: +```java +@ExtendWith(SpringExtension.class) +// ApplicationContext will be loaded from "/base-config.xml" +// in the root of the classpath +@ContextConfiguration("/base-config.xml") +class BaseTest { + // class body... +} + +// ApplicationContext will be loaded from "/base-config.xml" and +// "/extended-config.xml" in the root of the classpath +@ContextConfiguration("/extended-config.xml") +class ExtendedTest extends BaseTest { + // class body... +} +``` + +```java +// ApplicationContext will be loaded from BaseConfig +@SpringJUnitConfig(BaseConfig.class) +class BaseTest { + // class body... +} + +// ApplicationContext will be loaded from BaseConfig and ExtendedConfig +@SpringJUnitConfig(ExtendedConfig.class) +class ExtendedTest extends BaseTest { + // class body... +} +``` + +```java +// ApplicationContext will be initialized by BaseInitializer +@SpringJUnitConfig(initializers = BaseInitializer.class) +class BaseTest { + // class body... +} + +// ApplicationContext will be initialized by BaseInitializer +// and ExtendedInitializer +@SpringJUnitConfig(initializers = ExtendedInitializer.class) +class ExtendedTest extends BaseTest { + // class body... +} +``` +#### Context Configuration with Environment Profiles +可以通过为test class指定`@ActiveProfiles`注解来指定激活的profiles。 + +```java +@ExtendWith(SpringExtension.class) +// ApplicationContext will be loaded from "classpath:/app-config.xml" +@ContextConfiguration("/app-config.xml") +@ActiveProfiles("dev") +class TransferServiceTest { + + @Autowired + TransferService transferService; + + @Test + void testTransferService() { + // test the transferService + } +} +``` +`@ActiveProfiles`同样支持`inheritProfiles`属性,默认为true,可以关闭: +```java +// "dev" profile overridden with "production" +@ActiveProfiles(profiles = "production", inheritProfiles = false) +class ProductionTransferServiceTest extends AbstractIntegrationTest { + // test body +} +``` + +#### Context Configuration with TestPropertySource +可以通过在test class上声明`@TestPropertySource`注解来指定test properties文件位置。 + +`@TestPropertySource`可以指定lcoations和value属性,默认情况下支持xml和properties类型的文件,也可以通过`factory`指定一个`PropertySourceFactory`来支持不同格式的文件,例如`yaml`等。 + +每个path都会被解释为spring resource。 + +示例如下所示: +```java +@ContextConfiguration +@TestPropertySource("/test.properties") +class MyIntegrationTests { + // class body... +} +``` +```java +@ContextConfiguration +@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) +class MyIntegrationTests { + // class body... +} +``` +如果在声明`@TestPropertySource`时没有指定locations和value属性的值,其会在test class所在路径去查找。例如`com.example.MyTest`其默认会去查找`classpath:com/example/MyTest.properties`. + +##### 优先级 +test properties的优先级将会比定义在操作系统environment、java system properties、应用程序手动添加的propertySource高。 + +##### 继承和覆盖 +`@TestPropertySource`注解也支持`inheritLocations`和`inheritProperties`属性,可以从父类中继承resource location和inline properties。默认情况下,两属性的值为true。 + +示例如下: +```java +@TestPropertySource("base.properties") +@ContextConfiguration +class BaseTest { + // ... +} + +@TestPropertySource("extended.properties") +@ContextConfiguration +class ExtendedTest extends BaseTest { + // ... +} +``` +```java +@TestPropertySource(properties = "key1 = value1") +@ContextConfiguration +class BaseTest { + // ... +} + +@TestPropertySource(properties = "key2 = value2") +@ContextConfiguration +class ExtendedTest extends BaseTest { + // ... +} +``` +#### Loading WebApplicationContext +如果要导入WebApplicationContext,可以使用`@WebAppConfiguration`. + +在使用`@WebAppConfiguration`注解之后,还可以自由使用`@ConfigurationContext`等类。 + +```java +@ExtendWith(SpringExtension.class) + +// defaults to "file:src/main/webapp" +@WebAppConfiguration + +// detects "WacTests-context.xml" in the same package +// or static nested @Configuration classes +@ContextConfiguration +class WacTests { + //... +} +``` + +#### Context Caching +一旦TestContext framework为test导入了Application Context,那么context将会被缓存,并且为之后相同test suite中所有有相同context configuration的test复用。 + +一个`ApplicationContext`可以被其创建时的参数组唯一标识,创建ApplicationContext所用到的参数将会产生一个唯一的key,该key将会作为context缓存的key。context cache key将会用到如下参数: +- `locations`(`@ContextConfiguration`) +- `classes`(`@ContextConfiguration`) +- `contextInitializerClasses`(`@ContextConfiguration`) +- `contextCustomizers`(`ContextCustomizerFactory`) +- `contextLoader`(`@ContextConfiguration`) +- `parent`(`@ContextHierarchy`) +- `activeProfiles`(`@ActiveProfiles`) +- `propertySourceDescriptors`(`@TestPropertySource`) +- `propertySourceProperties`(`@TestPropertySource`) +- `resourceBasePath`(来源于`@WebAppConfiguration`) + + +如果两个test classes对应的key相同,那么它们将共用application context。 + +该context cache默认大小上限为32,到到达上限后,将采用`LRU`来淘汰被缓存的context。 + +### Test Fixture Dependency Injection +test class中的依赖对象将会自动从application context中注入,可以使用setter注入、field注入。 + +示例如下所示: +```java +@ExtendWith(SpringExtension.class) +// specifies the Spring configuration to load for this test fixture +@ContextConfiguration("repository-config.xml") +class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + @Autowired + HibernateTitleRepository titleRepository; + + @Test + void findById() { + Title title = titleRepository.findById(new Long(10)); + assertNotNull(title); + } +} +``` +setter注入示例如下所示: +```java +@ExtendWith(SpringExtension.class) +// specifies the Spring configuration to load for this test fixture +@ContextConfiguration("repository-config.xml") +class HibernateTitleRepositoryTests { + + // this instance will be dependency injected by type + HibernateTitleRepository titleRepository; + + @Autowired + void setTitleRepository(HibernateTitleRepository titleRepository) { + this.titleRepository = titleRepository; + } + + @Test + void findById() { + Title title = titleRepository.findById(new Long(10)); + assertNotNull(title); + } +} +``` +### 事务管理 +为了启用事务支持,需要在`ApplicationContext`中配置`PlatformTransactionManager`bean,此外,必须在test方法上声明`@Transactional`注解。 + +test-managed transaction是由`TransactionalTestExecutionListener`管理的,spring-managed transaction和applicaion-managed transaction都会加入到test-managed transaction,但是当指定了spring-managed transaction和applicaion-managed transaction的传播行为时,可能会在独立事务中运行。 + +> 当为test method指定@Transactional注解时,会导致test方法在事务中运行,并且默认会在事务完成后自动回滚。若test method没有指定@Transactional注解,那么test method将不会在事务中运行。test lifecycle method并不支持添加@Transactional注解。 + +示例如下所示: +```java +@SpringJUnitConfig(TestConfig.class) +@Transactional +class HibernateUserRepositoryTests { + + @Autowired + HibernateUserRepository repository; + + @Autowired + SessionFactory sessionFactory; + + JdbcTemplate jdbcTemplate; + + @Autowired + void setDataSource(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Test + void createUser() { + // track initial state in test database: + final int count = countRowsInTable("user"); + + User user = new User(...); + repository.save(user); + + // Manual flush is required to avoid false positive in test + sessionFactory.getCurrentSession().flush(); + assertNumUsers(count + 1); + } + + private int countRowsInTable(String tableName) { + return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + } + + private void assertNumUsers(int expected) { + assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user")); + } +} +``` + +#### 事务提交和回滚行为 +默认情况下,事务在test方法执行完后自动会回滚,但是,事务执行完后的行为可以通过`@Commit`或`@Rollback`注解来控制。 + + +如下是所有事务相关注解演示: +```java +@SpringJUnitConfig +@Transactional(transactionManager = "txMgr") +@Commit +class FictitiousTransactionalTest { + + @BeforeTransaction + void verifyInitialDatabaseState() { + // logic to verify the initial state before a transaction is started + } + + @BeforeEach + void setUpTestDataWithinTransaction() { + // set up test data within the transaction + } + + @Test + // overrides the class-level @Commit setting + @Rollback + void modifyDatabaseWithinTransaction() { + // logic which uses the test data and modifies database state + } + + @AfterEach + void tearDownWithinTransaction() { + // run "tear down" logic within the transaction + } + + @AfterTransaction + void verifyFinalDatabaseState() { + // logic to verify the final state after transaction has rolled back + } + +} +``` +### test request +spring mvc中的测试示例如下所示: +```java +@SpringJUnitWebConfig +class RequestScopedBeanTests { + + @Autowired UserService userService; + @Autowired MockHttpServletRequest request; + + @Test + void requestScope() { + request.setParameter("user", "enigma"); + request.setParameter("pswd", "$pr!ng"); + + LoginResults results = userService.loginUser(); + // assert results + } +} +``` + +## spring boot test +spring boot提供了`@SpringBootTest`注解,可以作为`@ContextConfiguration`注解的代替。该类实际是通过spring boot项目的启动类来创建了一个ApplicationContext。 + +默认情况下,`@SpringBootTest`并不会启动server,可以使用注解的`webEnvironment`来对运行环境重新定义,该属性可选值如下: +- `MOCK`(默认):将会导入一个ApplicationContext并且提供一个mock web environment。内置的server将不会被启动。如果classpath中没有web环境,那么其将会只创建一个非web的ApplicationContext。其可以和基于mock的`@AutoConfigureMockMvc`与`@AutoConfigureWebTestClient`一起使用 +- `RANDOM_PORT`:导入`WebServerApplicationContext`并提供一个真实的web环境,内部server启动,并监听随机端口 +- `DEFINED_PORT`导入`WebServerApplicationContext`并提供一个真实的web环境,内部server启动,监听指定端口(默认8080) +- `NONE`:通过SpringApplication导入ApplicationContext,但是不提供任何web环境 + +### @TestConfiguration +如果想要为测试新建Configuration顶级类,不应该使用`@Configuration`注解,这样会被@SpringBootApplciation或@ComponentScan扫描到,应该使用`@TestConfiguration`注解,并将类修改为嵌套类。 + +如果@TestConfiguration为顶层类,那么该类不会被注册,应该显式import: +```java +@RunWith(SpringRunner.class) +@SpringBootTest +@Import(MyTestsConfiguration.class) +public class MyTests { + + @Test + public void exampleTest() { + ... + } + +} +``` + +### testing with mock environment +```java +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +public class MockMvcExampleTests { + + @Autowired + private MockMvc mvc; + + @Test + public void exampleTest() throws Exception { + this.mvc.perform(get("/")).andExpect(status().isOk()) + .andExpect(content().string("Hello World")); + } + +} +``` + +### slice +通常,测试时只需要部分configuration,例如只需要service层而不需要web层。 + +`spring-boot-test-autoconfigure`模块提供了一系列注解,可以进行slice操作。该模块提供了`@…​Test`格式的注解用于导入ApplicationContext,并提供了一个或多个`@AutoConfigure…​`来自定义自动装配设置。 + + + + + + + + + +