본문 바로가기
공부 기록

[TEST] 신이 인간을 만드는 과정으로 알아보는 Test Double --Mockito가 Mock이야?

by 타태 2023. 7. 27.

2023.05.07 - [공부 기록] - [SPRING] 스프링이 Event를 다루는 방법 - TransactionalEventListener

 

[SPRING] 스프링이 Event를 다루는 방법 - TransactionalEventListener

2023.05.07 - [공부 기록] - [SPRING] 스프링이 Event를 다루는 방법 - EventListener [SPRING] 스프링에서 Event를 다루는 방법 - ApplicationEventListener 2023.05.07 - [공부 기록] - [SPRING] 스프링에서 Event를 다루는 방법 -

ktae23.tistory.com

 

Mock과 Mocktio에 대해 많이들 갖고 있는 오해와 Mockito를 활용한 테스트 방식에 대해 재밌는 학습 테스트를 통해 확인해 보려 한다.

 

http://xunitpatterns.com/Test%20Double.html

이번 글을 통해 Test Stub, Test Spy, Mock Object 세 경우에 대해 알게 되길 바란다.

 

아래 학습 테스트 코드는 깃허브에 올라가 있다.

https://github.com/ktae23/StudyTest/tree/mockito

 

GitHub - ktae23/StudyTest: 학습 테스트 저장소

학습 테스트 저장소. Contribute to ktae23/StudyTest development by creating an account on GitHub.

github.com

 

 

우선 프로젝트를 생성하자, 의존성은 롬복도 사실 필요 없지만 그냥 하나쯤 추가했다.

 

이번 테스트의 콘셉트는 내가 신이 되어 인간을 만드는 심즈이다.

먼저 테스트를 생성하고 검증하고자 하는 조건을 작성한 뒤 컴파일이 가능한 정도만 후다닥 만들어 보자.

 

잘 통과하는 것을 볼 수 있다.

 


 

그런데 이 세계관에서는 내가 신이다.

내가 찜한 사람은 내 맘대로 할 수 있다.

그래서 내가 모킹 한 사람은 태어나자마자 999살이 되도록 해주려 하는데 아래 테스트를 통과시키려면 어떻게 해야 할까?

    @Test
    void 내가_모킹한_사람은_태어남과_동시에_이름이_생기며_999살이다() {
        String name = "김창식";
        Human human = new Human(name);

        assertThat(human.getName()).isEqualTo(name);
        assertThat(human.getAge()).isEqualTo(999);
    }

 

우선 모킹을 하기 위한 사전 작업을 하자, 익스텐션만 추가해도 되지만 이름을 부여하기 위해 어노테이션을 따로 만들었다.

 

그리곤 사람 한 명을 내가 찜해놓고 그 사람의 상태를 내 맘대로 지정하도록 한다.

이렇게 내가 관리하는 대상의 상태(이름, 나이))를 내가 지정한 대로 반환하도록 하는 것이 바로 Test Stubbing이다.

때문에 Test Stub은 대상의 "상태"가 검증 대상인 경우를 말한다.

 


사람이 태어났으니 나이가 들어가는 것이 당연하다.

나이를 먹도록 해보자.

잘 통과한다.

다음엔 뭘할지 생각해보니, 사람이 죽고 나서도 나이가 들면 안 되니까 죽고 나면 나이가 들지 않도록 해봐야겠다.

 


 

 

역시 난 완벽해. 죽으면 나이가 들지 않도록 잘 처리했다.

 

너무 완벽해서 재미 없는데 이번엔 내가 신의 권능으로 죽지 못하는 사람을 만들어 볼까?

이를 위해 사람을 만들건데, 이번엔 위에서 조작했던 것과 달리 실제로 나이가 드는 것을 봐야 하는 점이 다르다. 

때문에 사람을 만들고 내 권능을 부여하려 한다. 내 권능이 내린 이 사람은 죽어도 죽지 못한다.

죽었지만 죽지 못해 나이가 들었다.

코드에서 보이듯이 이때 사용한 방식이 바로 Spy Object이다.

Spy Object는 실제 객체의 동작과 상태를 모두 유지하지만, 내가 지령을 내릴 경우 해당 동작을 반드시 수행하는 스파이와 같은 객체이다.

때문에 "넌 항상 살아 있다"라는 내 지령에 따라 실제로는 죽었지만 언제나 살아 있다고 판단하여 나이가 든다.

 


이번엔 좀 더 내가 신이라는 걸 보여주기 좋게 부활을 시켜보려 한다.

나는 자비롭기 때문에 2번까지 부활을 시켜주려 한다.권능을 구현해 보자.

학습과 설명을 위해 alive라는 public 메서드를 분리했다.

음 부활을 잘한다. 3번째 부활 때 실패하는 것까지 완벽하다.

근데 정말로 권능이 잘 동작하는지 궁금해졌다. 정말 2번만 부활 되는거 맞아?

한번 확인해 보자.

 

주입받아 사용하는 객체가 없다 보니 spy를 사용했다.

테스트가 성공하는 걸 보니 진짜로 2번만 부활되는 게 맞나 보다.

이처럼, 대상의 동작을 확인하기 위한 테스트 더블을 Mocking이라 한다.

 

여기서 중요한 것은 반드시 Mock을 해야만 Mocking인 것도 아니고 Mockito라이브러리를 써야만 Mocking인것도 아니라는 것이다.

행위의 실행과 호출 횟수, 그리고 인자 등을 검증하는 것 Mocking이다

 

728x90

자비로운 신인 나는 이제 사람의 생존을 위해 밥을 먹도록 허락할 것이다.

음식을 먹으면 입에서 목을 지나 배로 이동하도록 해주려 한다.

사람이 음식을 먹을 수 있도록 구현해 보자.

 

이번에도 역시 먼저 검증 사항을 먼저 작성한 뒤 구현을 한다.

isEqualTo인데 오타가 났다.

테스트의 최소 조건을 통과시키기 위해 사람에게 입, 목, 배를 부여한다.

그리고 먹는다는 행위를 할 수 있도록 해주고 음식의 위치를 확인하여 어느 신체부위인지 반환하는 메서드를 추가해 준다.

이후 BodyPart 인터페이스를 입, 목, 배가 구현하도록 하고 Rice는 Food 인터페이스를 구현하도록 한다.

그럼 테스트 수행 가능 상태가 되고, 수행 시 성공한다.

 

이제 한 사이클을 돌렸으니 내부 구현을 시작하자.

먼저 각 신체 부위가 음식을 보관하는 내부 공간을 만들어 주자.

public class InnerPlace {

    private Food food;

    public void put(Food food) {
        this.food = food;
    }

    public Food pop() {
        return food;
    }

    public void clean() {
        this.food = null;
    }
}
public class Mouse implements BodyPart {

    private InnerPlace innerPlace = new InnerPlace();

    public Food swallow(Food food) throws InterruptedException {
        innerPlace.put(food);
        Thread.sleep(5);
        Food swallowedFood = innerPlace.pop();
        innerPlace.clean();
        return swallowedFood;
    }
}

음식을 삼키면 아주 잠깐 동안 해당 부위에 머무르고 다시 지나가는 식의 설계를 했다.

이 경우 음식을 먹는다는 행위는 아래와 같이 입 -> 목 -> 배 순으로 음식이 이동하는 것이다.

 

음식의 위치를 확인하기 위해서 각 신체부위에 음식이 있는지를 확인하는 메서드도 추가해 준다.

 

public class Throat implements BodyPart {

    private InnerPlace innerPlace = new InnerPlace();

    public Food swallow(Food food) throws InterruptedException {
        innerPlace.put(food);
        Thread.sleep(5);
        Food swallowedFood = innerPlace.pop();
        innerPlace.clean();
        return swallowedFood;
    }
}
public class Stomach implements BodyPart {
    private InnerPlace innerPlace = new InnerPlace();

    public void arrive(Food food) {
        innerPlace.put(food);
    }
}
public interface BodyPart {
    boolean hasFood();
}

BodyPart에 hasFood를, InnerPlace에 isFull 메서드를 구현한다.

 

모든 구현을 마친 뒤 다시 테스트를 돌렸을 때 여전히 성공해야 한다.

 


테스트도 통과했고 모든건 완벽했다.

난 좋은 신이 될것이다.

 

하지만 나는 꼼꼼한 신이니까 정말 내가 만든 사람이 입 -> 목 -> 배 순서로 음식이 이동하는지 확인하고 싶어 졌다.

그래서 테스트를 작성하고 내가 만든 가짜 장기를 넣어서 음식을 먹여보기로 했다.

테스트 결과 정말로 입 -> 목 -> 배 순서로 음식이 이동하는 걸 볼 수 있다.

이렇게 호출 순서에 대한 확인 역시 Mocking이다.

 

이 외에도 가짜 사람을 만들어서 사람인척 시키는 Fake Object도 있지만, 사람의 예시로는 설명하기 어렵기도 하고 Fake 자체가 어려운 개념은 아니어서 굳이 따로 다루지는 않겠다.

테스트 더블과 위에 나온 Mock, Stub, Spy 등에 대해서는 너무나 많은 블로그에서 설명하고 있으니 한 번만 읽어봐도 이해가 될 것이다.

https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/

 

Test Double을 알아보자

테스트 더블(Test Double)이란? xUnit Test Patterns의 저자인 제라드 메스자로스(Gerard Meszaros…

tecoble.techcourse.co.kr

 

허술하고 딱히 이어지는 스토리가 있는것도 아니지만 앉은자리에서 1시간 정도 테스트 코드를 짜고 객체를 구현하며 해본 재밌는 학습 테스트였다.

이 글을 읽고 Mockito != Mock, @Mock == Mocking이 아니라는 것을 이해했다면 그걸로 이 글의 목적은 달성했다.

 

내가 검증하고자 하는 대상이 무엇인지 즉, 어떤 걸 확인하고 싶은지에 따라 Stub, Mock, Spy, Fake 등의 테스트 기법 중 적절한 것을 선택 또는 조합하면 된다. 무조건 해야 하는 방법도 없고 항상 더 좋은 방식도 없다.

 

 

반응형

댓글