무럭무럭 Test Code 스터디 내용을 정리하는 글.
Test의 중요성에 대해서는 느껴왔었지만, 프로젝트를 하면서 Test Code에 많이 소홀했다. 여러가지가 이유가 있지만 그 중에 하나로 Test Code 짜는 습관이 잡혀있지 않은 것 때문이라고 생각했다. 조금 더 관성처럼 Test하는 습관을 기르는 것이 중요하다고 생각했다.
스터디 목표
쉬운 Test Code를 많이 짜보면서, Test Code를 짜는 습관을 만들어가기.
수능 수학으로 치면, 2점, 3점 문제를 많이 풀어보면서 기초적인 문제풀이 속도와 습관을 잡아보자라는 마인드로 Test를 진행하기로 했다.
추가로 만든 기능만을 Test 하는 것이 아닌, 기능을 추가해보면서 어떠한 Test를 짜야할지 고민해보고, 적절한 Test Code를 짜보는 연습을 하기로 했다.
Test Code 과제
주 마다 Test Code 10개 짜기
JUnit
Java 진영의 Test용 Framework.
김영한님 강의를 들을 때는 JUnit4도 썼었는데, 최근에는 거의 JUnit5로 작성하는 것 같다.
https://junit.org/junit5/docs/current/user-guide/#overview-what-is-junit-5
보통 JUnit으로 Test를 작성하는 경우에는 ThirdParty를 활용할 수 있다. 대표적으로 AssertJ를 사용하여 작성할 수 있다. AssertJ 같은 경우에는 Spring Framework를 사용하면, 기본적으로 사용할 수 있다.
JUnit 5 User Guide
Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo
junit.org
https://assertj.github.io/doc/#assertj-core
따라서 Java 프로젝트로 하는 경우에는 따로 추가해야함.
testImplementation("org.assertj:assertj-core:3.25.1")
Test 관련 IDE 세팅
- 인텔리제이 축약어 설정
- 자주 쓰는 Test Code template이 있다면, 이를 축약어로 설정하여 쉽게 template을 사용할 수 있다.

- 실행 단축키 설정
- Test를 자주 실행하는 단축키를 익숙한 단축키로 등록하여, Test를 즉각 즉각 실행해볼 수 있다.

언제 Test를 짜야할까?
1. 구현한 기능이 의도한 상황대로 동작하는지 Test Code를 작성하는 방법.
2. 실패하는 Test를 작성한 후에, 실패하는 Test를 성공할 수 있도록 변경하기.
3. 기능의 의도에 맞춰서 Test.
4. 기술 검증을 위한 Test.
등등 Test를 할 수 있는 상황은 다양한 것 같다.
어떻게 Test를 짜야할까?
실패할 수 있는 상황에 대해 생각해보기
성공하는 경우에 대해 기본적으로 작성하고, 실패하는 상황들을 Test 하는 것이 중요한 것 같다.
실패할 수 있는 상황은 기획에 의해서도, 주어질 수 있지만… 개발 하면서도 이를 찾아내서 Test 해보면 좋을 것 같다.
상황이라는 것이 서비스적으로도 생길 수 있지만, 개발 관점으로도 생길 수 있기 때문이다. 그러한 상황을 떠올렸다면 → 코드로 옮겨서 잘 동작하는지 검증해보는 것이다.
경계 조건 검사하기
문제가 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 Test하자.
예를 들어, 유저의 닉네임을 10자 이하로 제한한다고 가정하면 11자로 입력했을 때 처리 가능한지를 Test 해보는 것이다.
경계조건을 꼼꼼하게 검사하면, Test가 최대한 기능을 커버할 수 있는 것 같다.
BDD 스타일
BDD(Behavior Driven Development, 행위 주도 개발)
- Feature : 테스트에 대상의 기능/책임을 명시한다.
- Scenario : 테스트 목적에 대한 상황을 설명한다.
- Given : 시나리오 진행에 필요한 값을 설정한다.
- When : 시나리오를 진행하는데 필요한 조건을 명시한다.
- Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다.
김영한 님의 Inflearn 강의에서도 다음과 같이 template을 만들어서 사용하고 있다.
@Test
@DisplayName()
void test() {
// given
// when
// then
}
1주차 요구사항
1. 더하기, 곱하기, 나누기, 빼기, 를 지원한다.
2. 결과가 0이상의 정수여야 하고, 최댓값은 100000이다.
3. 나누는 숫자가 0이 되면 안된다.
4. 빈 값을 입력으로 준 경우에 계산이 되면 안되고, 사용자에게 경고메세지를 보내야 한다.
5. 계산기 Type에는 공학용 계산기와 디지털 계산기가 있다.
6. 공학용 계산기는 직각 삼각형의 넓이를 구해주는 기능이 있다.
7. 입력은 정수로 제한.
8. 연산자와 숫자를 계산기에 잘못 입력하는 경우도 없음.
Fixture
Test를 하기 위해서는 Test 할 값과 객체가 필요하다. 고정된 값으로 만들어둔 객체를 Fixture로 이해하면 될 것 같다.
public class Pet {
private String name;
private String type;
}
위와 같은 class가 있다면, 아래와 같이 fixture를 만들 수 있습니다.
class PetTest {
private static final String NAME = "두부";
private static final String TYPE = "강아지";
@Test
void sampleTest() {
Pet pet = new Pet(NAME, TYPE);
}
}
또는 별도로 class를 만들어서 Fixture class를 만들 수 있습니다.
@DisplayName
Test에 대한 설명을 작성하는 어노테이션이다. 최대한 자세하고 섬세하게 작성하자.
실제 서비스에서 동작하는 것을 담을 수 있도록 작성하자.
ex) 가격이 0원인 Product 객체 테스트 (X) -> 상품가격이 0원일 경우 상품 등록을 할 수 없다.
Kotest
코틀린 진영에서 사용할 수 있는 Test 프레임워크.
Kotest | Kotest
Flexible, powerful and elegant kotlin test framework with multiplatform support
kotest.io
Test Setting
Test code를 짜다보면, Test Code를 짜기 위한 반복적인 세팅을 해야하는 경우가 있는데, 이 때 각 method에서 작성하는 것은 비효율적이기 때문에, 아래와 같이 어노테이션을 활용하여 미리 세팅해줄 수 있다.
@AfterEach @BeforeAll @BeforeEach @AfterAll
Domain 영역 Test
관계형 DB를 이용하여 Spring Boot 어플리케이션을 개발하는 경우 대부분 JPA를 ORM으로 선택하고, JPA에서는 Table에 Mapping되는 Domain 객체를 Entity로 부른다.
Entity는 따라서 애플리케이션을 개발할 경우 Data에 직결되는 영역이기 때문에 Test Code를 꼼꼼하게 짜야한다고 생각한다. 기본적인 Nullability 부터, 값 제한, CRUD시에 상태 변화 등등 …에 대한 Test Code를 연습해보자.
Spring에는 Assert라는 class가 있는데, 이를 활용해서 검증하는 method들을 작성해 볼 수 있지만, /해당 class가 터트리는 예외는 한정적이다. (IllegalArgumentException, IllegalStateException)
실제로 method를 작성하여 검증해주는 방향으로 작성하는 것이 서비스를 만들어가는데 더욱 용이한 것 같다.
비슷하게 Validation 라이브러리를 사용하게 될 경우도 해당 라이브러리에서 제공해주는 어노테이션을 DTO, Entity에서 사용할 때 발생하는 예외가 크게 두 가지 이기 때문에, 직접 예외코드를 작성하는 것이 좋을 것 같다.
Repository Test
Repository에서 작성되는 로직은 실제 Database에 CRUD와 같은 연산을 하기 때문에, H2 Database 기반으로 Test Code를 작성해봤다.
@DataJpaTest
JPA 영속성 테스트를 하는 경우, @DataJpaTest를 활용할 수 있다.
https://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/html/boot-features-testing.html
40. Testing
A few test utility classes are packaged as part of spring-boot that are generally useful when testing your application. TestRestTemplate is a convenience alternative to Spring’s RestTemplate that is useful in integration tests. You can get a vanilla temp
docs.spring.io
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
@OverrideAutoConfiguration(
enabled = false
)
@TypeExcludeFilters({DataJpaTypeExcludeFilter.class})
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
String[] properties() default {};
@PropertyMapping("spring.jpa.show-sql")
boolean showSql() default true;
@PropertyMapping("spring.data.jpa.repositories.bootstrap-mode")
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
@AliasFor(
annotation = ImportAutoConfiguration.class,
attribute = "exclude"
)
Class<?>[] excludeAutoConfiguration() default {};
}
DataJpaTest 어노테이션 코드인데, @Transaction이 붙어있는 것을 인지하고 TestCode를 작성해야 할 것 같다.
@ActiveProfiles
@ActiveProfiles를 이용하여, Test Code를 실행하는 실행환경을 설정할 수 있다. 아래와 같이 application-test.yaml 파일을 작성해준뒤에
spring:
config:
activate:
on-profile: test
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/test
username: sa
password:
jpa:
show-sql: true
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
show_sql: true
위와 같이 작성하고, 아래와 같이 사용할 수 있다.
@DataJpaTest
@ActiveProfiles("test")
public class MemberJpaRepositoryTest {
}
private method는 어떻게 Test를 해야할까?
Service의 method 길이가 길어지는 경우 보통 private method를 만들어서 분리를 한다. 이 때 private method는 당연히 test package에서 호출하지 못하므로, Test하지 않아도 된다!
private method가 Test를 해야하는 반드시 상황이라면, 설계에 대한 고민을 해야 한다.
Jacoco(Java Code Coverage)
JaCoCo는 Java Code Coverage를 확인할 수 있는 Tool이다. Code Coverage 세팅도 해줄 수 있고, 또한 GUI로 확인도 가능하기 때문에 유용하다.
https://www.eclemma.org/jacoco
plugins {
id 'jacoco'
id 'java'
id 'org.springframework.boot' version '3.0.9'
id 'io.spring.dependency-management' version '1.1.2'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
jacoco {
toolVersion = "0.8.11"
}
jacocoTestReport {
reports {
html.required = true
xml.required = true
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.8
}
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//h2
runtimeOnly 'com.h2database:h2'
}
tasks.named('test') {
useJUnitPlatform()
finalizedBy('jacocoTestReport')
}
어디까지 Test 해야 할까?
가능한 부분을 모두 작성하는 것이 좋은 것 같다.
특히
private method는 어떻게 Test를 해야할까?
Service의 method 길이가 길어지는 경우 보통 private method를 만들어서 분리를 한다. 이 때 private method는 당연히 test package에서 호출하지 못하므로, Test하지 않아도 된다!
private method가 Test를 해야하는 반드시 상황이라면, 설계에 대한 고민을 해야 한다.
References
'Spring' 카테고리의 다른 글
| [Smeem] Presentation Layer <-> Application Layer DTO 리팩토링 (0) | 2024.03.22 |
|---|---|
| [Spring] RestDocs 기반으로 API 문서 작성해보기 (0) | 2024.03.16 |
| [Spring] AWS EC2에 Spring Boot Project 배포하기. (0) | 2023.06.05 |
| [Spring Boot] Spring Boot로 HTTP API 설계하기 (2) | 2023.05.10 |
| [Spring] Spring Boot 프로젝트에서 Swagger로 API 문서 관리 (0) | 2023.05.01 |