개요
안녕하세요. J4J입니다.
이번 포스팅은 단위 테스트에 대해 적어보는 시간을 가져보려고 합니다.
단위 테스트란?
단위 테스트는 개발자가 개발한 모듈들이 정상적으로 동작하고 원하는 결과를 만들어내는지 확인하는 테스트를 말합니다.
대표적으로 스프링에는 JUnit Test가 존재하고 클래스를 생성하는 것처럼 JUnit Test Case를 생성하여 단위 테스트를 수행할 수 있습니다.
사용 목적
단위 테스트를 사용하는 목적은 내가 작성한 코드가 내가 의도한 대로 동작하는가? 에 대한 답변을 확인하기 위해서입니다.
물론 코드를 테스트하는 방법은 메인함수를 작성한다거나 Swagger를 이용한다거나 클라이언트 단의 테스트 코드를 작성하는 등의 다양한 방법들이 존재합니다.
그럼에도 불구하고 단위 테스트를 사용하는 이유는 단위 테스트만이 가지고 있는 특징들이 존재하기 때문입니다.
특징
1. 작성된 코드를 작은 단위들로 나눈 뒤 테스트가 필요한 단위들만 묶어 한 번에 테스트가 가능
2. 묶여진 단위들이 문제가 없을 시 더 큰 단위로 묶으며 테스트하는 상향식 테스트가 가능
3. 작성해둔 단위 테스트를 통해 리팩토링 된 코드의 신뢰도를 높임
4. 개발 코드와 테스트 코드가 분리되어 있어 코드 관리가 용이
5. 트랜잭션처리가 정상적으로 이루어져도 롤백되어 데이터베이스에 반영되지 않도록 설정 가능
사용 방법 - 기본적인 테스트
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
[ 1. 가장 먼저 테스트를 위한 프로젝트를 생성합니다. (Spring Legacy Project) ]
[ 2. 프로젝트 명은 자유롭게 입력하고 templates은 Spring MVC Project를 선택 후 Next ]
[ 3. 패키지 명도 자유롭게 입력 후 Finish ]
[ 4. 단위 테스트 사용을 위해 pom.xml에 dependency 2개를 추가합니다. ]
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.11.RELEASE</version>
<scope>test</scope>
</dependency>
여기서 주의할 점은 junit의 버전이 4.12보다 낮을 경우 아래와 같은 에러를 만날 수 있습니다.
Caused by: java.lang.IllegalStateException: SpringJUnit4ClassRunner requires JUnit 4.12 or higher.
말 그대로 junit의 버전을 4.12이상으로 설정이 되어 있어야 한다는 뜻이고 4.12이상의 버전으로 설정해주시면 문제없이 동작됩니다.
[ 5. src/test/java에 JUnitTest라는 이름으로 JUnit Test Case를 생성해줍니다. ]
[ 6. 테스트 코드를 작성합니다. ]
package com.spring.junit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import org.junit.Test;
public class JUnitTest {
@Test
public void unitTest() {
assertEquals("1", "1"); // 첫 번째 값과 두 번째 값이 같은지 확인
assertNotNull(new ArrayList<String>()); // 객체가 null이 아닌지 확인
}
}
[ 7. 우 클릭 → Run as → JUnit Test를 클릭하여 테스트합니다. ]
assert메서드
위의 코드에서 사용된 assertEquals, assertNotNull과 같이 단위 테스트에서는 값 비교를 위한 다양한 assert메서드들이 있습니다.
1. assertEquals(a, b): a와 b의 값이 동일한지 확인
2. assertSame(a, b): a와 b의 객체가 동일한지 확인
3. assertNull(a): a가 null인지 확인
4. assertNotNull(a): a가 null이 아닌지 확인
5. assertTrue(a): a가 true인지 확인
6. assertFalse(a): a가 false인지 확인
이 뿐만 아니라 더 많은 assert메서드가 존재하기 때문에 사용될 만한 메서드를 추가적으로 찾아보시면 될 것 같습니다.
사용 방법 - @Autowired
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
[ 1. 테스트를 위한 Person클래스를 src/main/java에 생성해줍니다. ]
package com.spring.junit;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
[ 2. servlet-context.xml에 Person클래스를 빈으로 2개 등록해줍니다. ]
<beans:bean id="person1" class="com.spring.junit.Person">
<beans:constructor-arg name="name" value="first Person"></beans:constructor-arg>
<beans:constructor-arg name="age" value="123"></beans:constructor-arg>
</beans:bean>
<beans:bean id="person2" class="com.spring.junit.Person">
<beans:constructor-arg name="name" value="second Person"></beans:constructor-arg>
<beans:constructor-arg name="age" value="234"></beans:constructor-arg>
</beans:bean>
[ 3. 위에서 만들어 둔 JUnitTest클래스에 @RunWith(SpringRunner.class)와 @ContextConfiguration을 추가한 뒤 @Autowired를 통해 주입된 빈을 테스트하는 코드를 작성합니다. ]
package com.spring.junit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
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.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
// @ContextConfiguration(classes=ServletContext.class) 자바로 작성했을 경우
public class JUnitTest {
@Autowired
Person person1;
@Autowired
Person person2;
@Test
public void autowiredTest() {
assertEquals(person1.getName(), "first Person");
assertEquals(person2.getName(), "second Person");
assertEquals(person1.getAge(), 123);
assertEquals(person2.getAge(), 234);
}
@Test
public void unitTest() {
assertEquals("1", "1"); // 첫 번째 값과 두 번째 값이 같은지 확인
assertNotNull(new ArrayList<String>()); // 객체가 null이 아닌지 확인
}
}
사용 방법 - log출력
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
[ 1-1. log출력을 위한 dependency 1개를 pom.xml에 추가합니다. ]
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<scope>runtime</scope>
</dependency>
[ 1-2. (필수아님) lombok의 log를 사용할 경우 아래 dependency도 pom.xml에 추가합니다. ]
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
[ 2. 위에서 만들어 둔 JUnitTest클래스에 logger를 추가한 뒤 person1객체의 이름을 출력하는 코드를 작성합니다. 만약 lombok의 log를 사용한다면 @Slf4j만 추가해주시면 됩니다. ]
package com.spring.junit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import lombok.extern.slf4j.Slf4j;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
// @ContextConfiguration(classes=ServletContext.class) 자바로 작성했을 경우
// @Slf4j lombok의 로그를 사용할 경우
public class JUnitTest {
private static Logger logger = LoggerFactory.getLogger(JUnitTest.class);
@Autowired
Person person1;
@Autowired
Person person2;
@Test
public void logTest() {
logger.trace("trace: " + person1.getName()); // trace: first Person
logger.debug("debug: " + person1.getName()); // debug: first Person
logger.info("info: " + person1.getName()); // info: first Person
logger.warn("warn: " + person1.getName()); // warn: first Person
logger.error("error: " + person1.getName()); // error: first Person
// lombok의 로그를 사용할 경우
/*
log.trace("trace: " + person1.getName());
log.debug("debug: " + person1.getName());
log.info("info: " + person1.getName());
log.warn("warn: " + person1.getName());
log.error("error: " + person1.getName());
*/
}
@Test
public void autowiredTest() {
assertEquals(person1.getName(), "first Person");
assertEquals(person2.getName(), "second Person");
assertEquals(person1.getAge(), 123);
assertEquals(person2.getAge(), 234);
}
@Test
public void unitTest() {
assertEquals("1", "1"); // 첫 번째 값과 두 번째 값이 같은지 확인
assertNotNull(new ArrayList<String>()); // 객체가 null이 아닌지 확인
}
}
해당 코드를 테스트해보면 모든 로그가 출력이 안되셨을 수도 있는데 그 이유는 로그레벨이 설정되어 있기 때문입니다.
로그 레벨
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
로그를 출력할 때 무분별한 로그 출력을 방지하기 위해 다음과 같은 로그 레벨이 정해져있습니다.
1. trace
2. debug
3. info
4. warn
5. error
만약 로그 레벨이 info로 설정되어 있다면 info를 포함한 더 높은 레벨의 로그들(warn, error)만 출력이 됩니다.
다른 경우로 만약 로그 레벨이 trace로 설정되어 있다면 위의 로그 레벨에 상관없이 모두 출력이 되는 겁니다.
로그 레벨 설정은 Legacy 프로젝트를 생성하면 기본적으로 src/test/resources의 위치에 다음과 같은 내용이 포함된 log4j.xml이 같이 생성됩니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c - %m%n" />
</layout>
</appender>
<!-- Application Loggers -->
<logger name="com.spring.junit">
<level value="info" />
</logger>
<!-- 3rdparty Loggers -->
<logger name="org.springframework.core">
<level value="info" />
</logger>
<logger name="org.springframework.beans">
<level value="info" />
</logger>
<logger name="org.springframework.context">
<level value="info" />
</logger>
<logger name="org.springframework.web">
<level value="info" />
</logger>
<!-- Root Logger -->
<root>
<priority value="info" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
위에서 단위 테스트를 진행하던 클래스의 패키지명이 com.spring.junit이고 해당 패키지의 이름으로 설정된 로그 레벨이 info로 되어있습니다.
이렇게 설정되어 있는 경우 단위 테스트를 통해 로그를 출력하면 info, warn, error에 속하는 로그들만 출력이 되고 실제로 테스트해보면 trace와 debug레벨에 해당하는 로그는 출력되지 않는 것을 확인하실 수 있습니다.
트랜잭션 처리 롤백
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
단위 테스트를 통해 수행된 트랜잭션이 정상적으로 이루어졌지만 롤백되어 데이터베이스에 반영되지 않도록 설정할 수 있습니다.
이를 설정 하는 이유는 이전의 수행된 테스트들이 다른 테스트들에 영향을 주지 않기 위해서입니다.
설정을 하는 방법은 다음과 같습니다.
[ 1. 트랜잭션 처리를 위한 dependency 1개를 추가합니다. ]
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
[ 2. 단위 테스트 클래스에 @Transactional을 추가합니다. ]
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
// @ContextConfiguration(classes=ServletContext.class) 자바로 작성했을 경우
// @Slf4j lombok의 로그를 사용할 경우
@Transactional
public class JUnitTest {
...
}
참조 사이트
[JUnit] JUnit을 이용한 단위 테스트하기+단정(assert)메소드 정리
단위 테스트란 무엇인가? 왜 단위 테스트를 해야하는가? 왜 pytest를 사용해야 하는가?
log4j - Logging Levels(로그 레벨) 정리
이상으로 단위 테스트에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > Spring' 카테고리의 다른 글
[Spring] 스프링을 이용한 MVC패턴 구현(4) - @Service 구성 (0) | 2021.02.16 |
---|---|
[Spring] 스프링을 이용한 MVC패턴 구현(3) - MySQL, MyBatis, @Repository 구성 (0) | 2021.02.13 |
[Spring] 스프링을 이용한 MVC패턴 구현(2) - 프로젝트 초기 설정 방법 (0) | 2021.02.11 |
[Spring] 스프링을 이용한 MVC패턴 구현(1) - Spring MVC (0) | 2021.02.09 |
[Spring] 제어의 역전(IoC), 의존성 주입(DI) (4) | 2021.02.07 |
댓글