앞에서 진행했던 컨트롤러, 서비스뿐만 아니라 직접 데이터베이스에 접근하여 쿼리를 사용하는 
DAO 클래스도 Junit 단위 테스트를 진행할 수 있다.

앞에 설명했던 부분들과 마찬가지로 bean 생성을 해야하는데 DAO의 경우에는 dataSource와 sqlSession, mybatis, transaction등 설정해야 하는 부분들이 많다.

이번에는 xml이 아닌 java configuration으로 설정했을 때는 어떻게 설정을 진행해야 하는지 살펴보자.

설정

우선 테스트를 진행하려는 클래스를 bean으로 생성하여야 한다.
DAO 클래스의 경우 sqlSession이 빈으로 생성되어 있어야 생성할 수 있으므로 @DependOn 어노테이션을 사용하여 의존성을 설정해 준다.


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
package com.wedul.wedulpos.user.test;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
 
import com.wedul.wedulpos.user.controller.UserController;
import com.wedul.wedulpos.user.dao.UserDao;
import com.wedul.wedulpos.user.service.UserService;
import com.wedul.wedulpos.user.serviceImpl.UserServiceImpl;
 
/**
 * User 테스트를 위한 Configuration
 * 
 * @author wedul
 *
 */
@Configuration
public class UserTestConfigruation {
    
    @Bean("userController")
    public UserController userController() {
        return new UserController();
    }
    
    @Bean("userService")
    public UserService userService() {
        return new UserServiceImpl();
    }
    
    @Bean("userDao")
    @DependsOn("sqlSession")
    public UserDao userDao() {
        return new UserDao("user");
    }
 
}
cs



그리고 앞서 말했듯이 데이터베이스 접속을 위한 설정을 가지고 올 수 있도록 Configuration 클래스를 작성해 주어야 한다.


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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.wedul.wedulpos.user;
 
import java.io.IOException;
 
import javax.sql.DataSource;
 
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
 
/**
 * DB 테스트를위한 Configuration
 * 
 * @author wedul
 *
 */
@Configuration
public class CommonTestConfiguration {
 
    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl(
                "jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=yes&characterEncoding=UTF8&autoReconnect=true");
        dataSource.setUsername("root");
        dataSource.setPassword("dbsafer00");
 
        return dataSource;
    }
 
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactory(org.springframework.context.ApplicationContext applicationContext,
            DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setConfigLocation(applicationContext.getResource("classpath:/mybatis-config.xml"));
        sqlSessionFactory.setMapperLocations(applicationContext.getResources("classpath:/mapper/**/*.xml"));
 
        return sqlSessionFactory;
    }
 
    @Bean(name = "sqlSession", destroyMethod = "clearCache")
    public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
 
        return transactionManager;
    }
}
cs




이렇게 하면 기본적인 DAO 클래스 테스트를 위한 설정이 완료되었다.
이제 테스트를 위한 클래스를 작성해보자.

Test 클래스

테스트 클래스는 서비스와 마찬가지로 기본적인 설정이 담긴 Annotation을 먼저 정의한 후, @Before에서 선 작업을 진행하고 @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
package com.wedul.wedulpos.user.test;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
 
import com.wedul.wedulpos.user.CommonTestConfiguration;
import com.wedul.wedulpos.user.dao.UserDao;
import com.wedul.wedulpos.user.dto.UserDto;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {UserTestConfigruation.class, CommonTestConfiguration.class})
public class UserTest {
    
    
    @Autowired
    UserDao userDao;
    
    UserDto dto;
    
    @Before
    public void setUp() throws Exception {
        dto = new UserDto();
        dto.setId("fasdf");
        dto.setPasswd("dd");
        dto.setName("cjung");
        dto.setEmail("rokking1@naver.com");
        dto.setTel("010-8680-2222");
    }
    
    @Test
    @Transactional
    @Rollback(true)
    public void testUserController() throws Exception {
        userDao.insertUser(dto);
    }
 
}
cs




만약 insert, update, delete와 같은 작업이 동반되어 있는 경우에 롤백을 하고 싶은 경우에는 @Transactional로 트랜잭션을 먼저 설정한 후, @Rollback 어노테이션을 이용해서 롤백을 진행한다고 선언한다.



전장에서 컨트롤러 테스트 방법을 공부하였다.
이번에는 서비스를 가지고 테스트 하는 방법을 설명한다. 


Assert 시리즈로 검증하기
- Assert 시리즈를 활용하면 해당 메소드의 결과값이 true인지 검증 뿐만 아니라 null 인지 등도 테스트를 진행할 수 있다.

 Assert로 설정한 대로 동작하지 않으면 테스트 도중 실패로 끝나기 때문에, 잘못된 결과 값이 나온다는 것을 확인하고 코드를 수정할 수 있다.

Assert 관련 메서드 종류는 다음과 같다.



Assert 메서드 종류
설명
assertArrayEquals(a, b)
배열 a와 b가 일치함을 확인한다.
assertEquals(a, b)
객체 a와 b가 일치함을 확인한다. (객체에 정의되어 있는 equals를 통해 비교한다.)
assertSame(a, b)
객체 a와 b 가 같은 객체임을 확인 한다. (객체 자체를 비교한다. == )
assertTrue(a)
조건 a가 참인지를 확인한다.
assertNotNull(a)
객체 a가 null인지 확인한다.



예제코드


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
package com.wedul.wedulpos.user.test;
 
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"test-confing.xml"})
public class UserControllerTest {
    
    @Autowired
    UserService userService;
    
    UserDto dto;
    
    @Before
    public void setUp() throws Exception {
        dto = new UserDto();
        dto.setId("dbsafer");
    }
    
    @Test
    public void testUserController() throws Exception {
        assertTrue(userService.login(dto));
        assertFalse(!userService.login(dto));
        assertNull(userService.findPassword(dto));
    }
 
}
cs




기존에는 이런 방식으로 많이 테스트를 진행하였지만 요근래에는 mockito에서 제공하는 방식을 이용하여 테스트를 많이 진행한다고 한다.

이는 다다음장에서 공부해보자.




입사 후 개발을 한지 벌써 3년 차가 되었다. 
개발을 처음 접할 때는 어떻게 구현해야 할지 어떻게 만들어야 하는지에 대한 관심이 더 컸다. 

하지만 요새는 개발 후 어떻게 테스트를 진행하여 먼저 버그를 예방(?)할 수 있는지 고민하기 시작했다.

성격이 덜렁거리거나 대충 하는 스타일은 아니었는데, 요즘 열정이 많이 식어서 그런지 단순한 부분에서 버그를 유발하는 것 같아서 TDD를 통해 고쳐보려고 Junit을 공부하기로 했다.

스프링의 각 영역인 Controller. Service, Dao에 대한 테스트를 총 3장에 걸쳐서 설명하겠다.

그리고 Mockito에 대한 설명을 추가로 진행하겠다.

0. 공통

우선 스프링에서 단위테스트를 진행하기 위해서는 몇가지 라이브러리가 필요하다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<!-- mockito프레임워크 사용을 위한 라이브러리 -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<!-- spring test는 MockMvc와 다른 테스트를 포함한다. -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.3.RELEASE</version>
    <scope>test</scope>
</dependency>
cs



1. Controller
Controller 단위 테스트를 진행하기 위해서 MockMvc를 활용할 수 있다.

MockMvc란?
-> 브라우저에서 요청과 응답을 의미하는 객체로서 Controller 테스테 사용을 용이하게 해주는 라이브러리이다.

먼저 테스트를 진행할 컨트롤러는 다음과 같다.



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
package com.wedul.wedulpos.user.controller;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
/**
 * User관련 컨트롤러
 * 
 * @author wedul
 *
 */
@RestController
@RequestMapping(value = "/user", method = RequestMethod.POST)
public class UserController {
    
    @Autowired
    UserService userService;
 
    /**
     * 로그인 요청
     * 
     * @param user
     * @return
     */
    @RequestMapping("/login")
    public boolean login(UserDto user) {
        return userService.login(user);    
    }
    
}
cs



그럼 이 컨트롤러 테스트를 위해 테스트 클래스를 만들어보자.


Annotation 설정

먼저 테스트 클래스를 만들기 위해서는 선 작업이 진행되어야 한다.

먼저 상위에 테스트 클래스에 대한 일부 설정값을 어노테이션으로 선언해야 한다.

SpringJUnit4ClassRunner.class
-> spring-test에서 제공하는 단위 테스트를 위한 클래스 러너

@ContextConfiguration
-> 테스트의 설정이 들어있는 xml의 위치

@Mock
-> 주입할 Service 객체를 Mock인스턴스로 선언

테스트를 진행하기전에 진행되야할 작업들은 @Before가 선언된 메서드에서 진행되어야 한다.
@Before 메서드에서는 다음과 같은 과정들이 선행되어야 한다.

@ContextConfiguration에서 함께 선언된 test를 위한 xml에는 테스트에서 사용될 bean들을 만들어주는 설정들이 들어있어야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
// test-config.xml에 선언된 설정 내용
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <annotation-driven />
    
    <beans:bean id="userController" class="com.wedul.wedulpos.user.controller.UserController"></beans:bean>
    <beans:bean id="userService" class="com.wedul.wedulpos.user.serviceImpl.UserServiceImpl"></beans:bean>
</beans:beans>
cs



MockMvc 객체 생성

그럼 테스트를 진행할 Controller를 이용하여 MockMvc 객체를 생성한다. 테스트전에 선 작업이 진행하는 부분이기 때문에, @before 어노테이션이 선언된 메서드에서 작업을 진행한다.



1
2
3
4
5
6
7
8
9
@Autowired
UserController userController;
 
private MockMvc mockMvc;
 
@Before
public void setUp() throws Exception {
     mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
cs



MockMvc 객체를 이용해 테스트 진행

생성된 MockMvc 객체의 perform() 메소드를 이용하여, get, put, post, delete에 대한 요청을 진행할 수 있다.

또한 ContentType 설정 및 parameter 까지 자유롭게 생성하여 테스트에 사용할 수 있다.



1
2
3
4
5
@Test
public void testUserController() throws Exception {
    // login check
    mockMvc.perform(post("/user/login").param("id", "cjung"));
}
cs



테스트 요청 결과를 검증할 수 있는 방법을 ResultAction 인터페이스에서 제공하는 메서드들을 이용하여 검증할 수 있다.


// perform 요청에대한 예측결과를 검증한다.
ResultActions andExpect(ResultMatcher matcher) throws Exception;



1
2
3
4
5
6
7
8
9
10
11
12
13
mockMvc.perform(get("/person/1"))
    .andExpect(status().isOk())
    .andExpect(content().contentType(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.person.name").value("Jason"))
    .andExpect(redirectedUrl("/messages/123"));
 
 mockMvc.perform(post("/form"))
    .andExpect(status().isOk())
    .andExpect(redirectedUrl("/person/1"))
    .andExpect(model().size(1))
    .andExpect(model().attributeExists("person"))
    .andExpect(flash().attributeCount(1))
    .andExpect(flash().attribute("message", "success!"));
cs



// 결과에 대해 특정한 작업을 시행함. 파라미터로 전달하는 ResultHandler를 이용하여 다양하게 동작하게 설정가능
ResultActions andDo(ResultHandler handler) throws Exception;



1
mockMvc.perform(get("/form")).andDo(print());
cs




// 결과 자체를 전달받아, Reques, Response 등에 대한 정보를 반환받을 수 있음
MvcResult andReturn();


최종 완성 예제



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
45
46
47
48
49
50
51
52
53
54
55
package com.wedul.wedulpos.user.test;
 
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import java.nio.charset.Charset;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
import com.wedul.wedulpos.user.controller.UserController;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"test-confing.xml"})
public class UserControllerTest {
    
    @Autowired
    UserController userController;
    
    private MockMvc mockMvc;
    
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));
    
    @Before
    public void setUp() throws Exception {
         mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }
    
    @Test
    public void testUserController() throws Exception {
        // login check
        mockMvc.perform(post("/user/login").param("id", "cjung")).andExpect(status().isOk());
        
        // 비밀번호 찾기 확인
        System.out.println(mockMvc.perform(post("/user/password/find").param("id", "cjung")).andDo(print()));
        
        /* ObjectMapper mapper = new ObjectMapper();
         * this.mockMvc.perform(post(UserController.URL_USER_CREATE)
                .contentType(contentType)
                .content(mapper.writeValueAsString(new User("wedul"))))
                .andExpect(status().isOk())
                .andDo(print());*/
    }
 
}
cs





다음 시간에는 Service 테스트 방법에 대해 알아보자.

  1. 지나가는 초보자 2019.02.23 22:10

    정말 감사합니다.. 상세한설명에 초보자 눈높이에 맞춰져있어서 감동했습니다.

  2. july 2019.03.06 14:40

    하루 반정도 헤매다가 다시 이 게시글로 돌아와서 해결했습니다ㅋㅋㅋㅋ
    감사합니다!!!!!

  3. 너무 감사합니다 2019.05.10 12:03

    너무 잘 보고 갑니다 ㅠㅠ
    깔끔한 설명 너무 고맙습니다 ㅠㅠ

+ Recent posts