테스트코드는 왜 필요할까?
-
SW가 개발자의 의도한대로 정확하게 동작하는지 확인하는것이 제일 중요.
-
규모가 이미 있는 프로젝트내에서 추가한 기능에 대한 검증만 요구
- 신규기능 개발시 테스트에 대한 비용을 아낄수 있다.
-
기존 요구 사항에서 변동을 주는 부분을 빠르게 발견가능하다.
- 기존기능에 대한 변경의 전파에 대한 안정감을 얻을 수 있다.
-
주기를 짧게 자주한다면 결함을 조기에 발견이 가능하다.

-
테스트코드를 작성함으로써 개발속도가 느려지지만 장기적 으로 본다면 굉장히 많은 비용이 아껴진다.
테스트코드의 작성 중요성
시나리오
- 테스트를 위해선 시나리오(흐름) 이 중점이 되어야한다.
- 코드를 떠나서 먼저 내가 어떤 상황에 대한 테스트를 하고싶은지 정리가 필요하다.
- 가능한 효과적이고 꼼꼼한 시나리오를 생각해보자.
- 테스트코드 또한 관리의 영역에 포함된다. 필요한 시나리오의 경우 자연스럽게 채워지게 됄것.
- 어차피 SW 는 터진다!
FIRST 원칙
- F(Fast) : 테스트코드는 빠르게 실행되어야한다. 1시간 씩 걸린다면 굳이 테스트코드로?
- I(Isolated) : 테스트는 독립적이어야 한다.
- 다른 테스트나 외부시스템을 의존해서는 안된다. (테스트시점에 따라 성공 실패가 갈림)
- 테스트코드의 중점은 외부시스템의 연결상태가 아닌 내 SW의 정상흐름이다!
- Repeatable 원칙 또한 같이 위배될 확률이 높다.
- R(Repeatable) : 테스트코드는 반복해도 항상 동일한 결과여야한다.
- 변경사항이 없더라도 테스트코드가 실패한다면 (
Random, LocalDateTime) 실패한 케이스
- S(Self-validating) : 테스트 코드는 그 자체로도 검증의 수단이 되어야한다.
- 개발자가 로그등을 확인해서 검증을 하면 안된다.
- 테스트 코드를 CI/CD 배포 파이프라인의 구성요소로 추가하기도 하는데, 이런 상황에서 Self-validating 원칙이 깨진다면 충분히 검증이 안되는 상황이 생길 수 있다.
- T(Timely) : 테스트 코드는 즉시 작성되어야한다.
그 외 사항
-
테스트코드 방법론이 매우 많지만 일단 테스트코드를 작성해보고 이후 나에게 맞게 찾아가자.
-
DomainEntity -> Service -> Client -> POJO -> Repository -> Controller 순으로 작성을 해보자. Jacoco 같은걸로 테스트 커버리지 확인가능.
-
재사용성 보단 시나리오 가독성 이 더 중요하다.
(중복코드가 발생하더라도 가독성을 우선시 하자)
SpringBootTest VS POJO 테스트
@SpringBootTest 는 편의성이 좋지만 실행속도가 매우 느리게되기 때문에 비용의 규모와 FAST 원칙을 위배하기 때문에 최대한 지양하는것이 좋다.
- @WebMvcTest, @DataJpaTest, @Import 등 지원하는것이 많다.
- E2E 테스트, 인수 테스트는 어플리케이션 전체의 흐름이 필요하기에 @SpringBootTest 가 필요하다.
단위 테스트
public 메소드 자체를 독립적으로 테스트하기위한 테스트 코드
- 객체의 의존성을 배제하고 해당 객체(메소드) 자체만 테스트해야하기 때문에
Test Double 이 필수적으로 사용된다.
- 단위테스트 작성 순서는 의존성의
안 쪽 부터 작성하는것이 좋다.
- Mock 객체를 만들고 Stubbing 한다는 것은 의존성을 가지는 그 객체가 예상한대로 잘 동작한다는
보장이 되어있어야 하기 때문이다.
구성
- 테스트 코드와 메소드는
1:1 의 관계
- Given - When - Then
- Given
- 테스트를 위해 필요한 준비를 하는단계.
- Object Mother, Test Data Builder, Test Fixtures(Fixture Monkey), Easy Random
- 테스트에 필요한 객체를 생성하거나, Mock 객체에 대한 Stubbing 단계.
- When
- 테스트하고자하는 메소드 호출단계
- 일반적으론 1줄의 코드를 가짐. 여러줄 일경우 높은확률로 나눠져야하는 테스트
- 예외 검증시 Then 과 합쳐지기도함.
- Then
- When 절에 호출한 메소드의 반한값, Spy 를 이용한 호출횟수검증, 예외검증 등 테스트에 대한 결과 확인.
우선순위 가이드
- Domain Entity & POJO
- 테스트코드 작성 리소스에 비해 가치가 매우 높다.
- 의존객체가 많지않기에 Test Double 이 필요없는 케이스가 많다.
- Service
- 비즈니스로직음 담고있기에 단위테스트의 가치가높다
- 객체에 대한 의존성이 높기에 Test Double 이 겅의 항상 필요하다 (
Mockito)
@Service
class OAuth2LoginService(
private val oAuth2ClientService: OAuth2ClientService,
private val socialMemberService: SocialMemberService,
private val jwtHelper: JwtHelper
) {
fun login(provider: OAuth2Provider, authorizationCode: String): String {
return oAuth2ClientService.login(provider, authorizationCode)
.let { socialMemberService.registerIfAbsent(it) }
.let { jwtHelper.generateAccessToken(it.id!!) }
}
}
통합 테스트
- 2개이상의 객체를 합쳐서
협력하는 맥락을 테스트 혹은 외부환경을 함께 테스트하고싶을때 고려

시나리오 관점 에서 고민을 하여 테스트를 작성해보자.
- 되도록이면
@DataJpaTest , @Import 등을 활용하여 최소한의 환경을 만들자.
E2E (End to End Test)
- 하나의 API를 호출 했을 때 동작하는 APP 전체의 흐름
- APP이 정말로 실행중인지 가정한 테스트.
@SpringBootTest 가 필요한순간.

인수 테스트
- E2E 테스틑 각각의 API 기준으로 테스트를 했다면 인수테스트는
사용자의 시나리오 기준으로 테스트코드를 작성
- 회원가입 -> 로그인 -> 게시글 작성 등의 흐름.
- 기본적인 개념은 요구사항이지만, 최근 많이 사용하는 애자일(Agile) 에서는 요구사항의 단위를 사용자 스토리로 사용하기 때문에 이를 기준으로 표현하기도한다.
- Ex : 사용자는 카카오 소셜 로그인을 할 수 있다.
- OAuth 2.0 을 이용한 소셜 로그인은 2번의 흐름으로 전체 프로세스가 구성되어 있다.
- (1) 로그인 페이지로 리다이렉트하기 위한 API 호출
- (2) 로그인 성공 후 인증 프로세스를 위한 Callback API 호출