일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Spring
- Pwnable.kr
- stack overflow
- System
- Shell code
- 정보보안기사
- PWN
- BOF
- OS
- 네트워크
- 웹해킹
- Lord of BOF
- 정보처리기사 실기
- Buffer Overflow
- pwnable
- 해킹
- Payload
- webhacking.kr
- 정보보안기사 실기
- wargame
- SQL
- 워게임
- Spring MVC
- hacking
- webhacking
- Operating System
- 운영체제
- LOB
- Spring Framework
- system hacking
- Today
- Total
DongDD's IT
[Spring] Spring Test 본문
[Spring] Spring Test
Spring Test
- Spring Framework에서 동작하는 클래스를 테스트하는 모듈
Unit Test
- 테스트할 클래스의 구현 내용만 테스트
- 사용하는 다른 클래스의 method 등은 Mock 또는 Stub으로 만들어 해당 구현 내용에 영향을 끼치지 않게함
Integration Test
- 실제 운영 환경에서 사용될 클래스를 통합하여 테스트
- 기능 검증을 위한 것이 아닌 spring framework에서 제대로 동작하는지 검증
Bean Test
Dependency 추가
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> </dependency> | cs |
Mockito
- 해당 클래스에 대해서만 테스트하기 위해 다른 클래스를 참조하는 값을 임의로 설정(Mocking, 가짜 객체로 만들어줌)
- 참조 : https://dongdd.tistory.com/165?category=767942
Unit Test
1. Service
1) JunitService1
1 2 3 4 5 6 7 8 | @Service public class JunitService1 { @Autowired private JunitService2 junitService2; public String get() { return junitService2.get(); } } | cs |
2) JunitService2
1 2 3 4 5 6 | @Service public class JunitService2 { public String get() { return "test"; } } | cs |
- Service1에서 Service2의 get 함수를 호출하고 Service2의 get 함수는 "test"를 return
2. Spring MVC Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration public class TestJUnitService { @InjectMocks private JunitService1 junitService1; @Mock private JunitService2 junitService2; @Test public void serviceTest() { BDDMockito.given(junitService2.get()) .willReturn("test1"); String returnValue = junitService1.get(); assertThat(returnValue, is("test1")); } } | cs |
- @Runwith : 기본으로 지정된 Test Runner 대신 지정하여 사용 → Test Runner 확장
-> TestRunner : Junit Framework에서 테스트 클래스 내의 method의 실행을 담당
- @ContextConfiguration : Application Context를 만들기 위한 설정 파일 지정
- @WebAppConfiguration : 가상의 web.xml(web app 전용 Application Context/DI)을 생성
-> Servlet API를 사용한 Mock 객체 생성 가능
- @InjectMocks : Test할 클래스를 지정하여 inject
- @Mock : Test할 클래스에서 사용하는 클래스을 mocking
- @Test : method에 지정하여 해당 method가 test를 진행하게 함
- BDDMockito를 이용하여 Service1이 Service2의 get()을 호출할 경우, "test" 를 return하도록 mocking
- assertThat을 사용하여 Service1이 제대로 동작했을 때의 return 값이 "test"와 일치하는지 확인
- 결과(성공)
- 결과(실패)
3. Spring Boot Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @RunWith(SpringRunner.class) @Import({JunitService1.class}) public class TestJunitService1 { @Autowired private JunitService1 junitService1; @MockBean private JunitService2 junitService2; @Test public void testJunit() { BDDMockito.given(junitService2.get()) .willReturn("test1"); String result = junitService1.get(); assertThat(result,is("test1")); } } | cs |
- Test Class에 @RunWith(SpringRunner.class)를 지정
- @Import({JunitService1.class}) : Test할 class를 import
- @InjectMocks 대신 @Autowired로 가져옴
- 해당 클래스에서 사용하는 다른 클래스들을 @MockBean으로 지정
- 나머지는 동일
- mvc와 같이 @InjectMocks, @Mock 사용해도 가능(Import 없앤 후 실행)
- 결과(성공)
- 결과(실패)
Integration Test
- JUnit에서 DI Container를 가동 후, 실제 환경처럼 테스트
1. Spring MVC Test
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration public class TestJUnitServiceIntegration { @Autowired private JunitService1 junitService1; @Test public void test() { String result = junitService1.get(); assertThat(result,is("test")); } } | cs |
- 실제로 juniService1.get()을 실행했을 때, return되는 값을 test
- mock으로 원하는 테스트 케이스를 설정하는 것이 아닌 실제 동작하여 나오는 결과를 test
- 결과(성공)
- 결과(실패)
2. Spring Boot
1 2 3 4 5 6 7 8 9 10 11 12 | @RunWith(SpringRunner.class) @SpringBootTest public class TestJUnitServiceIntegration { @Autowired private JunitService1 junitService1; @Test public void test() { String result = junitService1.get(); assertThat(result, is("test1")); } } | cs |
- @SpringBootTest : test에 사용할 Application Context(DI Container) 조작
- mock으로 원하는 테스트 케이스를 설정하는 것이 아닌 실제 동작하여 나오는 결과를 test
- 결과(성공)
- 결과(실패)
DI Container
1. Cache
- Test DI Container는 테스트 종료 전까지 캐시됨
-> 테스트 케이스간 공유하여 사용 가능
- @ContextConfiguration에 지정된 속성 값이 같으면 캐시 DI Container 사용
-> Test 시간 단축
2. DI Container 제거
- Test DI Container는 Java VM 시작 시 생성되고 종료 시 사라짐
- @DirtiesContext : 해당 annotation 지정 시, 지정한 클래스/메소드가 종료 시 Di Container 제거
Profile 지정
- application.yml 파일에 지정했던 profile 별 Data source들을 사용하기 위해 Test에 지정할 수 있음
- 참조 : https://dongdd.tistory.com/163
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @RunWith(SpringRunner.class) @Import({JunitService1.class}) @ActiveProfiles("test") public class TestJunitService1 { @Autowired private JunitService1 junitService1; @MockBean private JunitService2 junitService2; @Test public void testJunit() { BDDMockito.given(junitService2.get()) .willReturn("test"); String result = junitService1.get(); assertThat(result,is("test1")); } } | cs |
- @ActiveProfiles : 설정한 profile 중 어떤 profile을 선택하여 data source를 사용할지 설정
Property 지정
- @Value를 이용하여 property의 value를 이용하는 값을 test code에 사용할 때 원하는 값으로 변경하여 사용할 수 있음
1. application.properties
1 2 | spring.application.name = test server.port = 8080 | cs |
2. property 값 변경하여 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration @TestPropertySource(properties = "spring.application.name=aaa") public class TestJUnitService { @Value("${spring.application.name}") private String appName; @Test public void nameTest() { assertThat(appName,is("test")); } } | cs |
- @TestPropertySource(properties = "spring.application.name=aaa")와 같이 사용하여 property 값을 실제와 다르게 변경하여 사용할 수 있음
- 결과
3. property 파일 지정
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration @TestPropertySource(locations = "/application.properties") public class TestJUnitService { @Value("${spring.application.name}") private String appName; @Test public void nameTest() { assertThat(appName,is("test")); } } | cs |
- application.properties 파일의 위치 지정
- 결과
DataBase Test
- Database에 접근하는 bean을 테스트
Database Test Example
1. Test Datasource 생성
1 2 3 4 5 6 7 8 9 | @Configuration public class TestAppConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .setScriptEncoding("UTF-8").build(); } } | cs |
- Test하기 위해 필요한 datasource를 config파일에 정의
- 내장형 DB H2 사용
2. Repository 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Component public class TRepository { @Autowired private JdbcTemplate jdbcTemplate; @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } public void create(TestModel testModel) { jdbcTemplate.update("INSERT INTO test(id, name) VALUES(?, ?)", testModel.getId(), testModel.getName()); } public String get(Long id) { return jdbcTemplate.queryForObject("SELECT name FROM test WHERE id = ?", String.class, id); } } | cs |
- insert를 위한 create() method 정의
- select를 위한 get() method 정의
3. Table 생성 및 Data insert/select
1) sql 파일
1 2 3 4 | create table TEST ( id int primary key, name varchar(30) ); | cs |
- sql파일에 create table query를 작성
- create table을 위한 sql 파일 생성(test.sql)
2) Test Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration public class TestTRepository { @Autowired private TRepository tRepository; @Test @Sql(value = "/test.sql") @Order(1) public void insert() { TestModel testModel = new TestModel(); testModel.setId(Long.valueOf(123)); testModel.setName("test"); tRepository.create(testModel); } @Test @Order(2) public void select() { assertThat(tRepository.get(Long.valueOf(123)), is("test")); } } | cs |
- @Sql을 사용하여 해당 sql 파일을 실행
- insert하기 전 한번 수행되고 (123,"test") 값을 repository에 insert
- 이 후에 select하는 test가 수행되고 값이 제대로 들어간 것을 확인
- 순서를 보장하기 위해 @Order를 사용하여 Insert가 먼저 수행되게 함
- 결과(성공)
- 결과(실패)
Transaction
- 같은 DB를 여러 환경에서 Test하는 경우, 테스트를 수행하는 트랜잭션을 분리할 필요가 있음
- @Transactional 이용 -> default : Test method 실행 후 rollback
- commit이 필요한 method의 경우 @Commit 이용
1. Config 내용 추가
1 2 3 4 5 6 7 | @Configuration public class TestAppConfig { @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } } | cs |
- @Transactional을 사용하기 위해 TransactionManager 설정 필요
2. Insert/Select
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration @Transactional public class TestTRepository { @Autowired private TRepository tRepository; @Test @Sql(value = "/test.sql") @Commit @Order(1) public void insert() { TestModel testModel = new TestModel(); testModel.setId(Long.valueOf(123)); testModel.setName("test"); tRepository.create(testModel); } @Test @Order(2) public void select() { assertThat(tRepository.get(Long.valueOf(123)), is("test")); } } | cs |
- Transacional로 지정하고 insert된 값을 확인하기 위해 insert method에 @Commit 지정
- default : rollback
- 결과(성공)
- 결과(실패, Commit 없을 시)
Persistence Context
- JPA나 Hibernate의 경우, 인 메모리에 쌓아두었다가 commit될 때 sql을 실행하는 방식을 사용
- test code의 기본 룰은 rollback이기 때문에 테스트에 어려움이 생길 수 있음
- flush를 사용하여 명시적으로 sql문을 실행
1. JPA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @RunWith(SpringRunner.class) @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) @WebAppConfiguration public class TestTRepository { @Autowired private TRepository tRepository; @PersistenceContext private EntityManager entityManager; @Test public void insert() { TestModel testModel = new TestModel(); testModel.setId(Long.valueOf(123)); testModel.setName("test"); tRepository.create(testModel); entityManager.flush(); } @Test public void select() { assertThat(tRepository.get(Long.valueOf(123)), is("test")); } } | cs |
- EntityManager 이용
- entityManager의 flush()를 이용해 명시적으로 sql 쿼리문을 실행
- EntityManager를 사용하기 위해 EntityManagerFactory를 Bean으로 등록해줘야함
Spring MVC Test
- Controller에 대한 Test
- 해당 controller에 요청을 보내고 응답 값을 검증
MockMvc
- 웹 어플리케이션을 서버에 배포하지 않은 채로 MVC의 동작을 재현할 수 있는 클래스
1. DI Container 연계모드
- DI Container를 생성하여 실제 spring mvc 동작을 재현
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) public class TestSampleController { @Autowired private WebApplicationContext webApplicationContext; MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .build(); } } | cs |
- Test 시, 사용 할 Application context를 mockmvc에 설정
2. 단독 모드
- Spring Test가 생성된 DI Container를 사용해 spring mvc 동작을 재현
1 2 3 4 5 6 7 8 9 10 11 | @RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) public class TestSampleController1 { MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders .standaloneSetup(new SampleController()) .build(); } } | cs |
- standaloneSetup method로 test하고 싶은 controller 등록
3. Servlet Filter
- Servlet Filter를 MockMvc에도 추가하여 사용할 수 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) public class TestSampleController { @Autowired private WebApplicationContext webApplicationContext; MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(new CharacterEncodingFilter("UTF-8")) .build(); } } | cs |
- MockMvcBuilders의 addFilter를 사용하여 servlet filter 추가
Controller Test
1. Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RestController @RequestMapping(value = "/sample") public class SampleController { @RequestMapping(value = "/", method = RequestMethod.GET) public String getSample() { return "sample"; } @RequestMapping(value = "/", method = RequestMethod.POST) public TestModel postSample(@RequestBody TestModel testModel) { return testModel; } } | cs |
- "/sample/"으로 GET 요청 시, "sample" String 반환
- "/sample/"으로 POST 요청 시, 요청 값에 있는 testModel 반환
2. GET/POST Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | @RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebMvcConfig.class, TestAppConfig.class}) public class TestSampleController { @Autowired private WebApplicationContext webApplicationContext; @Autowired ObjectMapper objectMapper; MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .addFilter(new CharacterEncodingFilter("UTF-8")) .build(); } @Test public void getTest() throws Exception { mockMvc.perform(get("/sample/")) .andExpect(status().isOk()) .andExpect(content() .string(objectMapper .writeValueAsString("sample"))); } @Test public void postTest() throws Exception { TestModel testModel = new TestModel(); testModel.setId(Long.valueOf(123)); testModel.setName("test"); mockMvc.perform(post("/sample/") .content(objectMapper .writeValueAsString(testModel)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content() .string(objectMapper .writeValueAsString(testModel))); } } | cs |
1) GET 요청 Test
- "/sample/"로 get 요청을 수행
- andExpect로 응답 값이 200 OK가 오는지 검증
- andExpect로 응답 값의 body가 "sample"이 오는지 검증
- 결과(성공)
- 결과(실패)
2) POST 요청 test
- "/sample/"로 post 요청을 수행
- request body에 testModel을 Json으로 변환한 값을 넣어주고 content-type에 "application/json"을 넣어줌
- andExpect로 응답 값이 200 OK가 오는지 검증
- andExpect로 응답 값의 body가 보냈던 testModel이 오는지 확인
- 결과(성공)
- 결과(실패)
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring] Spring JPA 1 - JPA, JPA CRUD (0) | 2019.04.28 |
---|---|
[Spring] Spring Security - 인증, 인가, CSRF, Test (0) | 2019.04.20 |
[Spring] Spring MVC - Session, Async, 공통 처리, Locale (0) | 2019.04.14 |
[Spring] Spring REST (0) | 2019.04.14 |
[Spring] Spring Web Application, Controller, Exception Handler (0) | 2019.04.02 |