테스트 코드의 기초와 중요성을 알아보자
📌 서론
스프링부트로 백엔드 개발을 하다 보면 테스트 코드를 작성해야 한다는 말을 정말 많이 듣게 된다. 그래서 테스트 코드가 뭐길래 이렇게까지 얘기를 하는 걸까? 테스트 코드를 작성하기 전에 테스트 코드가 무엇인지와 어떤 tool을 사용해서 작성하는지에 대해서 간단히 이해해 보자
1. 테스트 코드의 중요성
테스트 코드는 개발 과정에서 매우 중요하다. 이를 통해 버그를 조기에 발견하고 소프트웨어의 안정성을 확보할 수 있다. 특히 스프링 부트에서는 다양한 도구와 라이브러리를 제공하여 테스트 코드 작성을 쉽게 해 준다.
이런 도구들을 사용함으로써, 개발자는 애플리케이션의 다양한 부분을 효율적으로 검증할 수 있고, 결과적으로 코드의 품질을 높일 수 있다. 또한, 테스트 코드는 리팩토링과 기능 추가 시 안정성을 제공해 주기 때문에, 유지보수성을 크게 향상시켜준다.
2. JUnit 5
JUnit 5는 스프링 부트에서 가장 많이 사용되는 테스트 프레임워크 중 하나다. 이 프레임워크는 @Test 어노테이션을 사용해 테스트 메서드를 정의하고, 테스트 실행 전과 후에 특정 작업을 수행할 수 있도록 @BeforeEach, @AfterEach, @BeforeAll, @AfterAll 같은 어노테이션을 제공한다.
JUnit 5의 큰 장점 중 하나는 태그, 필터링, 맞춤형 테스트를 지원한다는 것이다. 이를 통해 복잡한 테스트 환경을 관리하고, 더 효율적인 테스트 실행 전략을 구성할 수 있다. 또한, 람다를 사용한 강력한 어설션 기능을 제공하여, 예외 테스트와 같은 고급 테스트 케이스를 쉽게 작성할 수 있다.
[코드 예시]
이 코드는 JUnit 5를 사용한 기본적인 테스트 클래스의 구조를 보여준다. @BeforeEach는 각 테스트 실행 전에 초기화 작업을, @AfterEach는 테스트 후 정리 작업을 수행한다. @Test 어노테이션으로 표시된 메서드는 실제 테스트를 실행하는 메서드다.
class SampleTest {
private SomeClass someClass;
@BeforeEach
void setUp() {
someClass = new SomeClass();
}
@Test
void testSomeMethod() {
assertEquals("expected", someClass.someMethod());
}
@AfterEach
void tearDown() {
someClass = null;
}
}
3. Mockito
Mockito는 단위 테스트를 위한 목 객체 생성 프레임워크다. 실제 의존성을 가짜 객체로 대체하는 기능을 제공함으로써, 의존성이 복잡한 클래스를 단순화시켜 테스트할 수 있도록 도와준다.
예를 들어, 데이터베이스 연결이나 외부 서비스 호출과 같은 부분을 목 객체로 대체하면, 이러한 외부 요소들의 영향 없이 클래스의 기능을 순수하게 테스트할 수 있다. 정말 편리하다.
Mockito는 가짜 객체 생성, 메서드 호출에 대한 스텁 설정, 호출 검증 및 메서드 호출에 대한 모의 행동을 정의하는 등의 기능을 제공한다. 이러한 기능들은 복잡한 애플리케이션의 단위 테스트를 간소화하고, 테스트 코드의 가독성과 유지보수성을 크게 향상시켜준다.
[코드 예시]
이 예시에서는 Mockito를 사용해 Dependency 클래스의 목 객체를 생성하고 있다. when().thenReturn() 구문을 사용해 목 객체의 행동을 정의한다. 이렇게 하면 실제 의존성 대신 목 객체가 적용되어 테스트를 수행한다. 이 코드에서는 dependency.method()가 동작하면 "mocked value"라는 string값을 return 하게 설정했다.
class MockTest {
@Mock
private Dependency dependency;
private SomeClass someClass;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
someClass = new SomeClass(dependency);
}
@Test
void testMethodUsingMock() {
when(dependency.method()).thenReturn("mocked value");
assertEquals("mocked value", someClass.useDependency());
}
}
4. AssertJ
AssertJ는 테스트 코드의 assertion(프로그램 안에 추가하는 참·거짓을 미리 가정)을 위한 강력한 라이브러리다. JUnit의 기본 assertion보다 훨씬 풍부한 기능을 제공해서, 테스트 코드를 더 가독성 있고 유지보수하기 쉽게 만들어준다.
AssertJ의 주요 특징 중 하나는 체인 형식의 API를 제공한다는 것이다. 이 API는 다양한 타입에 대한 광범위한 assertion을 지원해서, 개발자가 보다 명확하고 자연스러운 방식으로 테스트 조건을 표현할 수 있도록 해준다.
예를 들어, 문자열, 컬렉션, 숫자 등 다양한 타입에 대해 세밀한 테스트를 수행할 수 있다. 이런 기능들 덕분에 AssertJ는 테스트 코드의 표현력과 가독성을 크게 향상시키는 데 큰 도움을 준다.
[코드 예시]
AssertJ는 체인 형식의 API를 제공하여, 테스트 조건을 보다 자연스럽고 가독성 높게 표현할 수 있게 해 준다. 아래 코드처럼 assertThat()메서드를 사용하면 다양한 어서션을 체인 형식으로 연결할 수 있다.
class AssertJTest {
@Test
void testWithAssertJ() {
assertThat("someString").isEqualTo("someString");
assertThat(Arrays.asList("one", "two")).contains("one");
}
}
5. @SpringBootTest
@SpringBootTest 어노테이션은 스프링 부트 애플리케이션의 전체 컨텍스트를 로드하여 통합 테스트를 수행하는 데 사용된다. 이는 실제 애플리케이션을 실행하는 것과 유사한 환경에서 테스트를 할 수 있게 해 줘서, 애플리케이션의 다양한 부분들이 서로 어떻게 상호작용하는지를 효과적으로 테스트할 수 있게 해 준다. (이걸로 테스트를 진행하면 실제와 가장 비슷하다.)
하지만, 전체 스프링 컨텍스트를 로드하는 것은 테스트 실행 시간을 길게 만들 수 있다. 그래서 때로는 필요한 컴포넌트만 선택적으로 로드하는 방식을 적용하면 테스트의 실행 속도를 향상시키고, 더 효율적인 테스트 전략을 구성할 수 있다.
[코드 예시]
@SpringBootTest 어노테이션은 스프링 부트의 전체 애플리케이션 컨텍스트를 테스트 동작시에 로드한다. 이를 통해 실제 애플리케이션 환경에서 서비스나 컴포넌트가 올바르게 로드되고 작동하는지 테스트할 수 있다. (최적화를 안 하면 테스트를 할 때 너무 많은 SpringBoot 서버가 동작하니 주의하자)
@SpringBootTest
class SpringBootTestExample {
@Autowired
private SomeService someService;
@Test
void contextLoads() {
assertNotNull(someService);
}
}
6. TDD (Test-Driven Development)
TDD, 즉 테스트 주도 개발은 테스트 코드를 먼저 작성하고, 이를 통과하는 실제 코드를 그다음에 작성하는 개발 방법론이다. 이 접근 방식의 주요 장점은 설계 개선, 코드 품질 향상, 그리고 리팩토링의 용이성에 있다. TDD를 통해 개발자는 더 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있고, 또한 테스트 코드 자체가 문서화의 역할을 하게 된다. 이 방법론은 개발 초기 단계부터 버그를 줄이고, 지속적인 피드백을 통해 소프트웨어의 품질을 지속적으로 개선할 수 있게 해 준다.
TDD를 통한 개발은 다들 프로젝트에 적용해야 한다고 생각은 하지만 적용시키지 못한다. 왜냐하면 기업의 특성 때문인 것 같다. 특히 SI기업에서는 빠르게 찍어내는 것을 목표로 하다 보면 경험해 볼 기회가 더 적어진다. TDD는 익숙해지려면 꾸준한 연습이 필요하니 요즘같이 AI를 통한 학습이 가능한 시기에는 ChatGPT에게 질문해 가면서 조금씩 작성해 보면서 늘려가는 것을 추천한다.
주니어 개발자 시리즈1 API 이해하기👇🏻👇🏻
'Spring 테스트 코드' 카테고리의 다른 글
[Spring] 테스트 코드: @MockBean으로 @EventListener 검증하기 (4) | 2023.12.23 |
---|---|
[Spring] 통합 테스트와 단위 테스트 비교하기 (58) | 2023.12.23 |
[스프링, 스프링 부트] Spring test - when()에서 발생한 에러 (0) | 2023.08.22 |
[스프링, 스프링부트] Spring test - 스프링 시큐리티의 authentication객체를 어떻게 사용해야 할까? (0) | 2023.08.22 |
Spring 서비스 테스트 중 발견한 NPE 해결기 (0) | 2023.08.17 |