지난 글(우리는 왜 테스트코드를 작성하나? ( 테스트 코드(1) - 개념 ))에서
테스트 코드 작성 시 사용하는 Stub, Mock, Spy의 개념과
간단한 사용 예시를 소개했었다.
하지만 당시에는 개념과 코드 설명이 조금 간략하게 넘어갔던 터라,
이번 글에서는 조금 더 자세히 어떨 때, 왜, 어떻게 사용하는지
정확하게 짚고 넘어가려 한다.
개인적인 생각
테스트를 하기 위해 테스트 코드를 작성하고... 그걸 또 테스트한다? 이거 너무 번거롭고 시간 낭비 아닌가?
과연 어떤 경우에 테스트 코드를 작성하는 지 알아보자.
조건이 여러개, 분기도 여러개 계산도 들어가는 복잡한 로직일 경우
모든 경우의 수를 테스트하기 어렵기 때문에 테스트 코드로 자동화 하는것이 좋다.사용자 등급 - ( VIP / Regular / 기타 ) 금액 - 금액별 조건 ( Y / N ) 날짜 - 블랙프라이데이 여부 ( Y / N ) 모든 상황은 12가지가 생긴다. POSTMAN으로는 이 12가지 상황을 모두 수동으로 만들어서 테스트해야 한다. 하지만 테스트 코드를 작성하면 자동으로 이 12가지 케이스를 반복 실행할 수 있다.
기능을 자주 수정하거나 리펙터링이 필요한 경우가 있다.
테스트 코드가 있으면기존 기능이 깨졌는지바로 확인이 가능하다.
혹여나 여러명이 같은 코드를 수정했을 때,
기존 기능에 영향을 주지 않았는지 자동 검증이 된다.
오랫동안 수정/보완이 필요한 서비스라면 테스트 코드는 안전망 역할을 해준다.
정리하면 다음과 같다.
모든 것을 테스트코드로 작성할 필요없고, 위에 해당한다면 작성하는 것이 좋다.
단위 테스트의 목표는 내가 테스트하려는 코드만 정확하게 검증하는 것이다.
하지만 현실에서 클래스는 혼자 동작하지 않고, 다른 클래스(의존성)에 의존한다.
예를 들어, signUp() 메서드만 테스트하고 싶다고 하자.
그런데 이 메서드가 실제로 Repository에 데이터를 저장하게 된다면?
그때는 이미 Repository까지 연관된 테스트가 되어버린다.
즉, 순수하게 signUp() 메서드만 검증하려는 단위 테스트 목적에서 벗어나게 되는 것이다.
자동차 충돌 테스트를 한다면 더미를 사용해서 사람 대신 테스트하는 것처럼,
프로그래머는 다른 객체를 테스트할 때 Mock Object를 사용한다.
자동차 회사가 진짜 사람 태우고 사고 테스트 못 하기 때문에
그래서 인형(더미)를 넣고 테스트하듯이,
프로그래머도 진짜 객체 쓰면 위험하거나 번거로우니 Mock으로 테스트한다
생각해보면 간단하다.
단위 테스트의 목적은 "딱 테스트하려는 코드만 정확하게 검증" 하는 것이다.
그래서 불필요한 컨테이너 기동 없이,
Mock 객체를 활용해서 테스트 대상 코드를 격리하여 테스트하는 것이다.
멤버 서비스 내부에 signup() 즉 회원가입을 테스트 해보려고 한다.
원본 signup() 을 보면 이메일이 중복된다면 예외를 던지고 있다.
위 코드에서 signup() 테스트를 진행할 때,
Member 객체의 id 값이 null 인 문제가 발생했다.

우리는 원인을 알고 있다.
테스트 시에는 스프링 컨테이너가 실행되지 않는다실제 서비스에서는 save()가 호출되면 -> JPA가 자동으로 id를 생성해준다.
하지만 테스트에서는 memberRepository가 Mock 객체다.즉 -> save() 메서드도 Stub 으로 우리가 직접 설정한 결과를 반환
JPA의 실제 동작(= id 자동 생성)이 일어나지 않는다
우리가 직접 id 에 값을 넣어주면 된다.
테스트 코드를 작성하면서 늘 궁금했던 부분이 있다.
나는 보통// Given이라고 주석을 달고 시작하는데,
그 아래 보면 대부분when()을 사용하는 경우가 많았다.
왜 그럴까?
그 이유는 바로 "초기 상태를 만들어주기 위해서"다.
단위 테스트에서는 우리가 사용하는 객체 중 많은 부분이
Mock(가짜 객체) 이다.
이 Mock 객체는 기본적으로 아무런 동작도 하지 않는다.
그래서 테스트를 실행하기 전에 → "이 메서드가 호출되면 어떤 결과를 줄지" 를 직접 설정해줘야 한다.
이 과정을 Stubbing 이라고 부른다.
테스트해보니?
너무 잘 된다.
그럼 자연스럽게 드는 의문
when() 과 given() 의 차이점은 뭘까?
차이는 없다. 그냥 코드 스타일 차이이다.
when() 은 Mockito 기본 API
given() 은 BDDMockito API
BDDMockito는
BDD(Behavior Driven Development) 스타일로 테스트를 쓰고 싶을 때 사용하는 API다.
즉 -> “Given - When - Then” 흐름으로 자연스럽게 테스트 코드를 작성하고 싶을 때 given() 을 쓰는 것이다.
이번에 Mockito를 사용해서 테스트를 진행해보았다.
그 과정에서 느낀 건, 가장 중요한 건 “내가 이 테스트 코드로 무엇을 얻을 수 있느냐” 라는 점이다.
단순히 테스트 코드를 작성하는 것이 목적이 아니라,
“이 테스트를 통해 어떤 리스크를 줄이고, 어떤 부분을 더 신뢰할 수 있는가” 를 고민하는 것이 더 중요하다고 느꼈다.
What's the difference between a mock & stub?
참고 (Reference) Martin Fowler의 글에 따르면 Dummy 객체: - 테스트 중에 단순히 파라미터 자리를 채우기 위해 전달되지만, 실제로 사용되지는 않는다. Fake 객체: - 실제로 동작하는 구현체를 가지지만, 일반적으로 프로덕션(실제 서비스)에 적합하지 않은 단축된 구현을 사용한다. Stub: - 테스트 중에 발생하는 호출에 대해 미리 준비된(canned) 응답을 제공한다. - 보통 테스트에 필요한 범위 외의 요청에는 아무 응답도 하지 않는다. 또한 Stub은 호출 정보를 기록하기도 한다. Mock: - 테스트 대상 객체가 어떤 메서드를 호출해야 하는지에 대한 기대(expectation)를 사전에 정의한 객체. - 이 기대는 일종의 명세(specification) 역할을 한다. - 즉, "이 메서드는 이런 식으로 호출돼야 한다" 를 검증하는 것. 스타일 (Style) Mocks vs Stubs = 행동 기반 테스트(Behavioral testing) vs 상태 기반 테스트(State testing) Mock: "어떤 메서드가 어떻게 호출됐는지"에 관심 → 행동 검증 Stub: "호출 후 결과 상태가 어떤지"에 관심 → 상태 검증