0. 들어가기 전
프로젝트 당시, 백엔드 개발자는 저를 포함해 두 명이었습니다. 각자 역할을 분담하여 API를 개발했지만, 작업을 진행하다 보면 서로의 코드를 수정해야 하는 경우가 종종 발생했습니다. 이런 경우, 기능별로 URL에 데이터를 보내 직접 테스트해야 했습니다.
이를 해결하기 위해 단위 테스트를 도입하자는 의견을 제시했고, 그 과정에서 학습한 내용을 정리하려 합니다.
단위 테스트의 가장 큰 장점은 내가 개발한 코드를 즉시 검증할 수 있다는 것이라 생각합니다. 테스트를 통과하지 못하면 코드를 수정하고, 다시 빠르게 검증할 수 있어 반복적인 오류를 줄일 수 있습니다. 실제로 테스트를 작성하고 수시로 실행하면서 짧은 시간 내에 버그를 발견하고 수정하는 과정에서 단위 테스트의 가치를 크게 실감할 수 있었습니다.
1. 단위 테스트 vs 통합 테스트
a. 단위 테스트 (Unit Test)
단위 테스트는 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트입니다. 여기서 모듈은 애플리케이션에서 작동하는 하나의 기능 또는 메서드로 이해할 수 있습니다. 예를 들어 웹 애플리케이션에서 로그인 메서드에 대한 독립적인 테스트가 1개의 단위 테스트가 될 수 있습니다.
즉, 단위 테스트는 애플리케이션을 구성하는 하나의 기능이 올바르게 작동하는지를 독립적으로 테스트하는 것으로, "어떤 기능이 실행되면 어떤 결과가 나온다"정도로 테스트를 진행합니다.
b. 통합 테스트 (Integration Teset)
통합 테스트는 모듈을 통합하는 과정에서 모듈 간의 호환성을 확인하기 위해 수행되는 테스트입니다. 일반적으로 애플리케이션은 여러 개의 모듈들로 구성이 되고, 모듈들끼리 메세지를 주고받으면서(함수 호출) 기능을 수행합니다. 그렇기에 통합된 모듈들이 올바르게 연계되어 동작하는지 검증이 필요합니다. 이러한 목적으로 진행되는 테스트가 통합 테스트입니다. 그렇기에 통합 테스트는 독립적인 기능에 대한 테스트가 아니라 웹 페이지로부터 API를 호출하여 올바르게 동작하는 지를 확인하는 것입니다.
2. 단위 테스트의 필요성
a. 단위 테스트의 필요성
일반적으로 실무에서 테스트 코드를 작성한다고 하면 거의 단위 테스트를 의미하는걸로 알고 있습니다. 통합 테스트는 실제 여러 컴포넌트들 간의 상호작용을 테스트하기 때문에 모든 컴포넌트들이 구동된 상태에서 테스트를 하게 됩니다. 그렇기에 통합 테스트를 위해서는 캐시나 데이터베이스 등 다른 컴포넌트들과 실제 연결을 해야 하고, 시스템을 구동하는 컴포넌트들이 많아질수록 테스트를 위한 비용이 상당히 커집니다. 반면에 단위 테스트는 해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링 하여도 빠르게 문제 여부를 확인할 수 있습니다.
💡 요약
1. 테스팅에 대한 시간과 비용을 절감할 수 있습니다.
2. 새로운 기능 추가 시에 수시로 빠르게 테스트할 수 있습니다.
3. 리팩토링 시에 안정성을 확보할 수 있습니다.
4. 코드에 대한 문서가 될 수 있습니다
b. 단위 테스트의 문제점과 Stub
어떤 객체가 자체적으로 모든 일을 처리한다면 문제가 없겠지만, 일반적인 애플리케이션에서는 1개의 기능을 처리하기 위해 다른 객체들과 메세지를 주고받아야 합니다. 하지만 앞서 설명하였듯 단위 테스트는 해당 모듈에 대한 독립적인 테스트이기 때문에 다른 객체와 메세지를 주고받는 경우에 문제가 발생합니다. 그렇기 때문에 다른 객체 대신에 가짜 객체(Mock Object)를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 하는데 이를 stub이라고 합니다.
예를 들어 데이터베이스에 새로운 데이터를 추가하는 코드를 테스트한다고 하면, 가짜 데이터베이스(Mock Database)를 주입하여 insert 처리 시에 반드시 1을 반환하도록 해주는 것입니다.
c. 좋은 단위 테스트의 특징
일반적으로 요구 사항은 계속해서 변하고, 그에 맞춰 우리의 코드 역시 변경되어야 합니다. 하지만 실제 코드를 변경한다는 것은 잠재적인 버그가 발생할 수 있음을 내포하는데, 좋은 테스트 코드가 있다면 변경된 코드를 검증함으로써 이를 해결할 수 있습니다. 또한 실제 코드가 변경되면 테스트 코드 역시 변경이 필요할 수 있는데, 이러한 이유로 우리는 테스트 코드 역시 가독성 있게 작성할 필요가 있습니다.
그렇기에 테스트를 작성하는 경우에는 다음을 준수하는 것이 좋습니다.
- 1개의 테스트 함수에 대해 assert를 최소화합니다.
- 1개의 테스트 함수는 1가지 개념만을 테스트합니다.
🤔 왜 assert를 최소화해야 하는 이유는 뭐지?
1. 단일 책임 원칙(SRP) 적용
- 하나의 테스트 함수는 하나의 동작(기능)만 검증해야 합니다.
- 너무 많은 assert가 있으면 어떤 부분이 실패했는지 명확하게 파악하기 어렵습니다.
2. 테스트 실패 원인 파악이 어려움
- 여러 개의 assert가 존재할 때, 첫 번째 assert에서 실패하면 나머지는 실행되지 않습니다.
- 즉, 어떤 부분이 문제인지 한 번에 파악하기 어렵습니다.
3. 테스트 유지보수 어려움
- 여러 개의 assert가 한 테스트 함수에 몰려 있으면 테스트 코드가 길어지고 복잡해집니다.
- 이후 코드가 변경될 때, 필요 없는 assert도 수정해야 해서 유지보수가 어려워집니다.
또한, 좋고 깨끗한 코드는 FIRST라는 5가지 규칙을 따라야 합니다.
- Fast : 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 합니다.
- Independent : 각각의 테스트는 독립적이며 서로 의존해서는 안됩니다.
- Repeatable : 어느 환경에서도 반복 가능해야 합니다.
- Self-Validating : 테스트는 성공 또는 실패인 bool 값으로 결과를 내어 자체적으로 검증되어야 합니다.
- Timely : 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 합니다.
'Programming > etc' 카테고리의 다른 글
[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 작성 (2) (0) | 2024.12.08 |
---|