Maven Build 시 test 건너뛰기 

Maven Build (install) 시 Maven Test 이후에 Build를 하게 된다.

이때 jUnit 테스트 에서 Exception 발생 및 Assert not Equal 등으로 Test가 실패할 경우 Build 는 실패 (Fail) 하게 된다.

jUnit Test 가 실패해도 Build 는 되게끔 Maven Test 단계를 건너뛰는 방법은 아래와 같다.

 

1. CMD 에서 빌드시

mvn install -DskipTests 

 

2. IDE Run configurations 에서 빌드시

1. Goals 에 다음과 같이 옵션 부여 : install -DskipTests 

2. Skip Tests 체크박스 체크 후 run

3. pom 수정 : surefire plugin 사용 (skipTests 속성 값 true/false 로 지정)

1
2
3
4
5
6
7
8
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M4</version>
    <configuration>
      <skipTests>true</skipTests>
    </configuration>
</plugin>
cs

 

surefire doc:

https://maven.apache.org/surefire/maven-surefire-plugin/examples/skipping-tests.html

반응형

스프링프레임워크에서 JUnit 을 이용한 API 테스트 : Spring Framework + JUnit + JNDI

junit 없이 매번 was 재기동 및 postman 호출로 api 결과들을 확인하는 방식으로 테스트를 하다 

junit 을 한 번 사용해 볼까 하여 기존 프로젝트에 설정을 잡고 사용해보았다.

 

혹여 본 포스팅 내용을 참고 하신다면, 프로젝트마다 context 및 프로젝트내에서 사용중인 lib version 등의 설정이 다르므로 제가 작성한 글은 그냥 참고만 하시길 바랍니다. 

실제 프로젝트에서 사용중인 코드 내에서 일부를 발췌하였으므로 github 등에 소스 공유는 힘듭니다.

언제가 될진 모르겠지만 별도의 프로젝트를 만들어 추후 github에 올려놓고 repository 링크를 공유하겠습니다..

출하면 안될 것 같은 패키지 경로, 실제 소스, 결과 등의 코드는 생략하였습니다.

 

[pom.xml]

필요한 의존성 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>
cs

 

[ApiControllerTest.java]

src/test/java 밑에 패키지 생성 후 아래와 같이 작성한다.

※ 테스트 케이스 생성 방법

테스트를 하고자 하는 클래스 선택 -> new -> JUnit Test Case

실제 소스 경로와 동일한 경로의 테스트 소스 경로가 만들어진다.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package ~;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import java.nio.charset.Charset;
import java.util.Map;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import ~.BaseController;
import ~.IdolApiController;
import ~.IdolApiService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
                  "classpath:bmp/spring/context-*.xml" 
                , "classpath:bmp/sqlmap/mybatis-config.xml" 
                , "classpath:bmp/servlet/servlet-*.xml"
              
                })
@ActiveProfiles("dev")
@TransactionConfiguration(transactionManager="txManager", defaultRollback = false)
@Transactional
@WebAppConfiguration
public class ApiControllerTest3 {
    protected static final Logger logger = LoggerFactory.getLogger(ApiControllerTest3.class);
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;
    
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
    
    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }
    
    
    @Test
    public void testIdolApiController() throws Exception {
        
        JSONObject jobj = new JSONObject();
        jobj.put("id""test");
        jobj.put("clientIp""172.0.0.1");
        jobj.put("content""testcontent.mpg");
        String p = jobj.toString();
        
        MvcResult rs = null;
        
        rs = mvc.perform(post("/getdata")
                                       //.content("{\"said\":\"test\", \"clientIp\":\"172.0.0.1\", \"content\":\"testcontent.mpg\"}")
                                         .content(jobj.toString())
                                         .contentType(contentType)
                        )
        .andDo(print())
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andReturn();
        
        logger.debug("---------------------------");
        logger.debug("result : {}", rs.getModelAndView().getModel());
        
    }
}
 
cs

31 L : junit test 를 위해 클래스에 붙여주는 annotation

32 L : 참조할 bean 들의 설정이 잡혀있는 context 경로 (classpath/file 및 절대/상대 경로 주의)

38 L : 활성시킬 profile 지정 (profile과 관련된 내용은 여기를 참고)

39 L : 트랜잭션 설정

40 L : 트랜잭션 적용

41 L : web controller 테스트를 위한 annotation

53 L : @Before를 메소드에 달아줄 경우 @Test 실행 전에 실행된다.

55 L : MockMvc 빌드를 위한 코드 ( 정확히 모르니 추후 Mock 관련 공부를 하며 보충 )

70~77 L : 

post(url) : post 방식 호출

content(String) : body 로 전송

contentType(contentType) : 호출시 content Type 지정

andDo(print()) : 결과 출력

andReturn() : 결과값 리턴(MvcResult rs 변수에 담기)

 

※ context 가 아닌 특정 controller 만 주입 받아 처리하고 싶은 경우 아래와 같이..

참고 : https://thswave.github.io/java/2015/03/02/spring-mvc-test.html

@Mock
VodApiService vodApiService;
@InjectMocks
VodApiController vodApiController;
private MockMvc mvc;

@Before
public void setUp() throws Exception {
      MockitoAnnotations.initMocks(this);
      mvc = MockMvcBuilders.standaloneSetup(vodApiController).build();
}

[junit 실행 결과]

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
MockHttpServletRequest:
         HTTP Method = POST
        Request URI = /getData
          Parameters = {}
             Headers = {Content-Type=[application/json;charset=UTF-8]}
 
             Handler:
               Type = ~
             Method = ~
 
  Resolved Exception:
                Type = null
 
        ModelAndView:
           View name = jsonView
                View = null
           Attribute = result_msg
               value = 
           Attribute = data
              value = {~}
           Attribute = result_cd
               value = 1
 
            FlashMap:
 
MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[application/json;charset=UTF-8], Pragma=[no-cache], Cache-Control=[no-cache, no-store, max-age=0], Expires=[1]}
        Content type = application/json
               Body = {~}
       Forwarded URL = null
      Redirected URL = null
             Cookies = []
DEBUG: ~ - ---------------------------
DEBUG: ~ - result : {~}
cs

 

여기서 log가 debug 레벨로 잘 출력 됐음을 확인 할 수 있는데, 이는 log4j 설정이 잡혀 있는 파일 (본 프로젝트에선 log4j2.xml)을 src/test/resources 밑에 복사 붙여넣기 한 후, log 레벨을 debug 로 내렸기 때문이다.

src/main/resources 내의 log4j 설정 파일을 읽기 전에 src/test/resources 내의 log4j 설정 파일을 우선적으로 읽는다. 관련 내용은 여기를 참고.

log4j 설정 관련 글은 여기를 참고.

 

JNDI 를 사용하고 있는 경우, jUnit test

보통은 위와 같이 테스트 할 수 있지만,

JNDI 를 사용하여 DB 설정이 잡혀 있는 경우, 별도의 처리가 필요하다.

JNDI 설정의 경우 was 에 DB 설정 정보가 존재하며 was 구동시 이를 읽어 들이는 방식인데(정확히 모름+공부필요),

was가 아닌 JUnit 구동시 DB설정정보를 읽어들이지 못해 이와 관련된 Bean을 주입 받을 때 에러가 발생하기 때문이다.

이를 해결하는 방법은 아래와 같이 두가지 정도가 있는 듯 하다.

 

[방법 1 : 테스트용 context 별도 설정]

1) JNDI 설정이 잡혀있는 context-datasource.xml 을 복사하여 src/test/resources 경로 밑에 붙여넣은 후 파일명을 test-datasource.xml 로 바꿔준다.

[example]

아래와 같은 JNDI 설정을

1
2
3
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="java:/jdbc/testJNDI" />
</bean>
cs

아래와 같이 바꿔준다. (was에 있을 DB접속정보(jndi 설정)를 context에 직접 작성)

1
2
3
4
5
6
7
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 
    <property name="url" value="jdbc:mysql://~:3306/~?useSSL=false&amp;serverTimezone=UTC"/> 
    <property name="username" value="~"/> 
    <property name="password" value="~"/>
</bean>
 
cs

2) 위에서 작성한 JUnit테스트 소스인 ApiControllerTest.java 를 아래와 같이 수정한다.

@ContextConfiguration 부분에 선언된 context 에 1~2) 번에서 작성한 context (jndi 설정을 걷어낸 DB설정(test-datasource.xml))를 추가한다.

1
2
3
4
5
6
@ContextConfiguration(locations = {
                  "classpath:bmp/spring/context-*.xml"
              , "classpath:test-*.xml"
                , "classpath:bmp/sqlmap/mybatis-config.xml" 
                , "classpath:bmp/servlet/servlet-*.xml"
                })
cs

 

위에서 언급한 log4j 설정과 마찬가지로, src/test/resources 가 src/main/resources 보다 선행돼서 읽히기 때문에 src/main/resources 밑의 context 에 선언된 Bean 과 동일한 이름의 Bean이 src/test/resources 밑의 context 에 선언돼 있어도 상관이 없는 듯 하다.

 

 

[방법 2 : JNDI 바인딩]

1) SpringJUnit4ClassRunner 를 상속받는 ExtSpringJUnit4ClassRunner 작성.

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
package ~;
 
import org.junit.runners.model.InitializationError;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jndi.JndiTemplate;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
public class ExtSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
 
    public ExtSpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
        // TODO Auto-generated constructor stub
        try {
            bindJndi();
        } catch (Exception e) {
            
        }
    }
    
    private void bindJndi() throws Exception {
        SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
        builder.activate();
        
        JndiTemplate jt = new JndiTemplate();
        
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://~:3306/~?useSSL=false&amp;serverTimezone=UTC");
        ds.setUsername("~");
        ds.setPassword("~");
        
        jt.bind("java:/jdbc/testJNDI", ds);
    }
}
 
cs

위와 같이 Jndi 바인딩을 java 소스로 처리.

관련 부분은 여기를 참고했음.

(이런식으로 JNDI 설정을 잡아줄 수 있는 걸 보면 다른 부분 설정도 이와 비슷하게 처리가 가능할 듯)

 

2) 위에서 작성한 JUnit테스트 소스인 ApiControllerTest.java 를 아래와 같이 수정한다.

1
@RunWith(ExtSpringJUnit4ClassRunner.class)
cs

SpringJUnit4ClassRunner 대신 위에서 작성한 ExtSpringJUnit4ClassRunner를 사용한다.

나머지 부분은 동일하다.

(jndi 설정을 java소스(ExtSpringJUnit4ClassRunner.java)에서 잡아주었으므로 방법 1과 같이 별도의 context 를 @ContextConfiguration에 추가해줄 필요가 없다)

 

JUnit 사용시 Maven Build 주의사항

JUnit을 사용하여 테스트를 하고 있고 Maven Build 로 war 파일을 생성하고 있다면 주의해야 할 점.

테스트 코드 결과가 실패인 경우, Maven Build 의 결과가 Failure (실패)로 떨어진다.

이땐 아래와 같이 해결한다.

 

[방법 1 : UI 에서 build option 지정]

프로젝트 우클릭 > Run Configurations.. > 좌측 리스트에서 Maven Build 중 해당 프로젝트 선택 > skip Tests 옵션 체크

 

[방법 2 : pom.xml 에 build option 수정]

1
2
3
4
5
6
7
8
9
 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <configuration>
        <useSystemClassLoader>false</useSystemClassLoader>
        <skipTests>true</skipTests>
    </configuration>
</plugin>
cs

위에서 7번라인과 같이 skipTests 옵션 true 지정.

해당 내용은 여기를 참고했음.

 

 

전체적인 JUnit에 대한 설명 및 설정등은 여기를 참고했음.

(이것저것 공부하고 관련 레퍼런스 찾아보고 블로그/깃헙 돌아다니다 보면 이상하리만큼 자주 도달하게 되는 곳이 있는데 그중 하나인 곳.. 항상 감사드립니다..)

 

본 포스팅은 다른 포스팅들과 마찬가지로 수시로 수정 및 업데이트 하겠습니다.

 

참고 :

https://effectivesquid.tistory.com/entry/Spring-test-%EC%99%80-Junit4%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8

반응형

 

1. 테스트를 위한 계산기 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Calculator;
 
public class Calculator {
    
    int add(int i, int j) {
        return i+j;
    }
    
    int subtract(int i, int j) {
        return i-j;
    }
    
    int muliply(int i, int j) {
        return i*j;
    }
    
    int devide(int i, int j) {
        return i/j;
    }
}
 
cs

 

2. junit 테스트 케이스 생성

1) 클래스 생성 버튼 우측의 화살표 버튼을 눌러 JUnit Test Case 선택

 

2) JUnit Test 를 위한 JUnit 클래스 생성

Test 대상 Class뒤에 Test 를 붙여 이름을 짓는게 관례(Calculator+Test)

 

3. junit 테스트 케이스 작성

1) 기본적으로 생성된 test() 메소드 제거 후 아래와 같이 코딩

: add() 메소드 테스트

: assertEquals 는 버전에 따라 deprecated 된 메소드가 있으니 잘 보고 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Calculator;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class CalculatorTest {
 
    @Test
    public void add() {
        Calculator cal = new Calculator();
        cal.add(12);
        assertEquals(""33);
    }
 
}
 
cs

 

2) 지역 변수 cal 을 필드로 빼서 사용하기

11번라인의 Calculator cal = new Calculator(); 를 8번 라인으로 빼서 필드로 사용이 가능하나, @Before를 사용하자.

필드 초기화를 전역에서 명시적으로 수행할 경우, 테스트간 독립성이 보장되지 않는다.

반면 @Before 사용시 add(), devide() 와 같은 테스트 메소드 실행시마다 인스턴스를 초기화 하여 

테스트간 독립성이 보장된다.

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
package Calculator;
 
import static org.junit.Assert.*;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class CalculatorTest {
 
    private Calculator cal;
    
    @Before
    public void setup() {
        cal = new Calculator();
        System.out.println("setup !!!");
    }
    
    @Test
    public void add() {
        int r = cal.add(23);
        assertEquals("sc"5, r);
        System.out.println("add !!!");
    }
 
    @Test
    public void devide() {
        int r = cal.devide(33);
        assertEquals("success"1, r);
        System.out.println("devide !!!");
    }
 
    @After
    public void teardown() {
        System.out.println("teardown !!!");
    }
    
}
 
cs

 

[실행 결과]

setup !!!
devide !!!
teardown !!!
setup !!!
add !!!
teardown !!!

위처럼 @Before, @After 는 테스트 메소드 실행시마다 호출됨을 확인 할 수 있다.

 

 

참고 : 박재성님의 유투브 강의 https://www.youtube.com/watch?v=tyZMdwT3rIY

반응형

DB연동 및 mybatis 설정이 끝났다면

junit 을 이용하여 설정이 잘 되었는지 확인을 해보자.

 

1. 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
package com.jpp;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.jpp.main.config.MybatisConfig;
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class MainServiceTest {
    
    @Autowired
    private MainDAO mainDao;
    
    @Test
    public void getUserList() throws Exception {
        
        for(UserVO vo : mainDao.getUserList()) {
            System.out.println(vo.getName());
        }
        
    }
}
 
cs

Spring Framework 에서 Bean 을 가져오기위해 @ContextConfiguration(classes = ~) 을 사용했지만 

Spring Boot 는 @*Test(@SpringBootTest 를 포함한 각종 Test 어노테이션) 사용시 Bean 을 알아서 가져온다고 한다.

참고 및 부분 발췌

※JUnit 4 사용시 @RunWith(SpringRunner.class) 를 꼭 달아주어야 한다.

※ @SpringBootTest(classes = {MainDAO.class, MybatisConfig.class}) 와 같이 테스트에 사용할 특정 Bean 을 지정해 줄 수 도 있다.

※ ApplicationContext (스프링컨텍스트) 에서 DAO bean 을 .getBean으로 가져와 테스트 하는 것 또한 가능하다.

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.jpp;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.jpp.main.config.MybatisConfig;
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MainDAO.class, MybatisConfig.class})
public class MainServiceTest {
    
    //@Autowired
    //private MainDAO mainDao;
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void getUserList() throws Exception {
        
        MainDAO dao = context.getBean("mainDAO", MainDAO.class);
        
        for(UserVO vo : dao.getUserList()) {
            System.out.println(vo.getName());
        }
    }
}
 
cs

 

2. Run as JUnit Test 로 실행.

결과 :

 

콘솔 결과 :

 

만약 테스트를 기존에 사용하는 DB가 아닌 별도의 DB 에서 하고자 하는 경우는 어떻게 해야할까?

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.jpp;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import com.jpp.main.dao.MainDAO;
import com.jpp.main.vo.UserVO;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class MainServiceTest {
    
    @Autowired
    private MainDAO mainDao;
    
//    @Autowired
//    private ApplicationContext context;
    
    @Test
    public void getUserList() throws Exception {
        
        //MainDAO dao = context.getBean("mainDAO", MainDAO.class);
        
        for(UserVO vo : mainDao.getUserList()) {
            System.out.println(vo.getName());
        }
    }
    
    @TestConfiguration
    @MapperScan(value= {"com.jpp.main.mapper"})
    @EnableTransactionManagement
    public static class TestConfig {
 
            @Bean
            public DataSource customDataSource() {
                return DataSourceBuilder.create()
                                        .url("jdbc:mysql://ip:3306/~?useSSL=false&serverTimezone=UTC")
                                        .driverClassName("com.mysql.cj.jdbc.Driver")
                                        .username("id")
                                        .password("pw")
                                        .build();
            }
            
            @Bean
            public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
                    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
                    sessionFactory.setDataSource(dataSource);
                    
                    Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml");
                    
                    sessionFactory.setMapperLocations(res);
                    
                    return sessionFactory.getObject();
            }
 
            @Bean
            public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
                return new SqlSessionTemplate(sqlSessionFactory);
            }
    }
}
 
cs

위와 같이 @TestConfiguration 을 테스트 코드 내부에 inner Class로 작성하여 테스트만을 위한 DB 설정을 따로 해줄 수 있다.

테스트 코드 내부에 inner Class 로 @TestConfiguration 작성시 별다른 설정이 없어도 @SpringBootTest 가 해당 config Bean을 인식하여 DI 한다.

inner Class가 아닌 외부에 TestConfig.java 파일을 따로 작성해줄 경우 @Import(TestConfig.class) 어노테이션을 @SpringBootTest 와 함께 달아주어야 테스트 코드에서 주입 및 사용이 가능하다.

ex)

@RunWith(SpringRunner.class)
@SpringBootTest
@Import(TestConfig.class)

 

그럼 이제 위의 테스트 코드를 실행해 보자.

아래와 같은 에러가 발생한다.

 

The bean 'customDataSource', defined in com.jpp.MainServiceTest$TestConfig, could not be registered. A bean with that name has already been defined in class path resource [com/jpp/main/config/MybatisConfig.class] and overriding is disabled.

 

동일한 이름을 가진 Bean이 MybatisConfig.class에 이미 정의되어 있어 TestConfiguration 으로 작성한 Bean 을 등록할 수 없단다.

 

※ 동일한 Bean 의 이름이 존재할 경우 IoC 컨테이너가 Test 코드 내의 TestConfiguration 어노테이션이 달린 Bean 을 우선적으로 인식하여 주입해줄까 싶었는데 그것 까진 안되나보다.

27번라인을 아래와 같이 고쳐주자

@SpringBootTest(classes = {TestConfig.class, MainDAO.class})

: @SpringBootTest 어노테이션에 테스트 DB가 설정되어있는 Bean class와 이를 사용할 class 를 명시

 

다시 테스트 모드로 실행시켜보면 결과는 성공일 것이다.

 

 

※ Test용 Config 를 외부 클래스로 작성하고 테스트코드에서 사용하고자 할 경우

1. 앞서 언급한 @Import 를 사용하는 대신

2. @SpringBootTest(classes = {TestConfig.class}) 와 같이 테스트용 Config 를 명시하여 사용 할 수 도 있다.

   (@Import 를 사용하지 않아도 주입 받아 사용이 가능)

 

※ 단, 위처럼 프로젝트 내에 동일한 이름의 Bean 이 존재할 경우(2개의 DataSource Bean 이 동일한 이름으로 존재) @SpringBootTest(classes = {TestConfig.class, MainDAO.class})와 같이 테스트에 사용할 Bean을 명시하여 사용해야한다. (@Import만 사용시 앞서 살펴보았던 에러와 동일하게, Bean 이 중복 선언된다는 Exception이 발생한다)

 

 

* Controller를 테스트 하는 경우 아래와 같이 가능하다.

1. 테스트 대상 코드

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
package com.jpp.main.controller;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
 
import com.jpp.main.service.MainService;
import com.jpp.main.vo.UserVO;
 
@Controller
public class MainController {
    
    @Autowired
    private MainService mainService;
    
    @PostMapping("/api2")
    @ResponseBody
    public String api(@RequestBody String test) throws Exception {
        String rs = "good";
        
        System.out.println("api2 called !");
        
        return "{\"good\":\"res\"}";
    }
}
 
cs

 

 

2. test class

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
package com.jpp;
 
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
 
import com.jpp.main.controller.MainController;
import com.jpp.main.service.MainService;
 
@RunWith(SpringRunner.class)
@WebMvcTest(MainController.class)
public class MainServiceTest2 {
    
    @Autowired
    private MockMvc mvc;
    
    @MockBean
    private MainService mainService;
    
    @Test
    public void api() throws Exception {
        
        
        ResultActions actions = 
                mvc.perform(post("/api2")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content("{\"test\":\"val\"}")
                        ).andDo(print());
        
        actions.andExpect(status().isOk());
    }
    
}
 
cs

 

3. junit 실행 결과

 

 

JUnit 과 관련된 트랜잭션(@Transactional) 부분, Mock 객체 등에 대한 내용은 추가 공부 및 정리 후 작성 하겠다.

 

 

Spring Boot의 Test코드에 대한 설명이 매우 잘 되어 있는 글

 

반응형

+ Recent posts