TDD(Test Driven Development) - Mock (4)

아래 내용은 ‘테스트 주도 개발: TDD 실천법과 도구’(https://repo.yona.io/doortts/blog/issue/1)를 보고 주요 내용을 개인적으로 정리하여 작성했습니다.

Mock ?


자동차를 만들기전에 나무를 사용해서 실제 자동차와 1:1 비율로 만들때가 있다. 실제 자동차를 제작하려면 비싼 재료가 들어가기도 하고 또 그런 상황이 발생하면 비용적인 측면에서도 많은 수고를 들이기 때문이다. 그래서 이러한 수고를 덜고자 값이 비교적 싼 나무를 사용해서 실제 자동차와 같은 비율로 만들어 테스트를 한다. 이떄 나무로 만들어진 자동차 모형을 Mock 이라고 한다. 쉽게 말해 가짜 객체이다.

Mock 객체는 실제 객체를 만드는데 시간이 오래 걸리거나 여러 객체가 의존성으로 묶여 있어 구현이 힘들때 사용한다.

그렇다면 Mock 객체가 필요한 때는 언제일까?

  • 환경설정이 필요할 떄
    • 일반적으로 DB, WAS 등의 설정이 필요할 떄 사용한다.
  • 특정 모듈이 “아직” 존재하지 않을 때
    • 예를들어 A 라는 메소드가 아직 구현이 안된 상태에서 어떤 값이 출력될지 예상이 되면 Mock 객체를 만들고 결과값으로 임시적인 예상 값만 추가해서 사용할 수 있다.
  • 다른 팀 간의 협의 및 정책이 필요할 때
    • 팀 간 이용하는 네트워크가 다를 때 접근이 불가능한 모듈의 경우 Mock 객체를 사용해서 테스트를 할 수 있다.

이외에도 테스트시간이 너무 오래 걸리거나, 특정 순간이나 경우에 의존적일 경우에도 사용한다.

Mock 객체는 메스자로스 제라드라는 사람이 테스트 더블 이라는 용어를 사용하여 테스트 더블의 하위 분류로 나누었다.


Test Double


그렇다면 테스트 더블의 하위 분류들을 알아보도록 하자.

(1) Dummy Object

더미 객체는 말 그대로 모조품, 단순한 껍데기에 해당한다. 인터페이스를 오로지 인스턴스화 시키기 위한 이유로만 생성되는 객체이다. 더미 객체는 기능적인 면은 구현하지 않고 객체의 인스턴스 확인에만 사용되므로 테스트 코드 작성 시에 추가적인 예외처리가 필요하다. 하지만 이렇게 단순히 인스턴스만 가지고는 기능적인 테스트를 할 수 없다. 그래서 더미 객체에서 조금 더 발전되어 메소드에 기능을 추가한 객체를 Test Stub 이라고 한다.

class Dummy implements User {

  public void getDummy() {}

}

class DummyTest {

  @Test
  public void getDummy_더미객체() {
    // User 인터페이스의 가짜 구현 객체 인 Dummy 객체를 사용
    User user = new Dummy();
  }
}

(2) Test Stub

더미 객체가 마치 실제처럼 동작하는 것처럼 보이게 만든 객체이다. 더미 객체와의 차이점은 더미 객체는 단순 인스턴스만 반환하고 스텁 객체는 인스턴스화된 객체가 특정 상태 값을 가지는 것이다. 다음은 로직이 실제 구현된 것처럼 보이는 Fake Object 이다.

class Stub implements User {
  
  public void getUser() {
    return "stub";
  }

}

class StubTest {

  @Test
  public void getUser_스텁객체() {
    User user = new Stub();
    // 임의의 값을 넣어 실제처럼 동작하게 만든 stub 메소드 호출
    assertThat(user.getUser(), is("stub"));
  }
}

(3) Fake Object

Stub 객체와 조금은 비슷하지만 인스턴스화 된 객체가 여러개인 경우 또는 복잡한 구현이 들어가 있는 형태 stub 의 경우에는 하드코딩된 값이 들어가 있어서 하나의 인스턴스만을 대표했다. 즉 하나의 인스턴스화 된 객체로만 역할을 수행했다. 하지만 어떤 상황이나 조건에 의해 값이 변하거나 할 경우에는 여러개의 인스턴스에 대응할 수 있어야 한다. 이때 사용되는게 Fake Object 이다. 예를들어 DB 를 조회하는 로직의 경우 실제 DB 를 조회하는 것이 아닌 DB 를 조회하는 것처럼 List 또는 Map 등의 객체를 사용하는 것이다. 특징으로는 외부 객체의 의존성을 단순화 하거나 복잡한 로직을 단순화 한것이라고 볼 수 있다.

class Fake implements User {

  public void getUser (String userName) {
    List<String> userList = new ArrayList<String>();
    userList.add("a");
    userList.add("b");
    userList.add("c");

    String targetUser = null;

    for (String user : userList)  {
      if (user.equals(userName))  {
        targetUser = user;
      }
    }

    return targetUser;

  }
}

class FakeTest {

  @Test
  public void getUser_페이크객체() {
    User user = new Fake();

    assertThat(user.getUser("a"), is("b"));

  }

}

(4) Test Spy

특정 객체 또는 메소드의 정상 호출 여부를 확인 테스트 더블 하위 분류 전체에 사용이 가능하다. 예를들어 Fake 객체에서 만든 메소드가 몇번 불렸나를 확인하기 위해 Fake 객체에 메소드 호출 횟수를 반환하는 스파이 메소드를 만들곤 한다. 여기서 실제 객체에는 없는 스파이 메소드를 만들어도 되냐하는 의문이 드는데 어짜피 가짜 객체이기 때문에 상관 없다고 한다.

테스트 스파이는 굳이 애써서 만들 필요는 없으며 요즘 있는 Mock 라이브러리에서는 왠만해서는 지원을 한다고 한다.

(5) Mock Object

들어가기 앞서 “상태 기반 테스트” 와 “행위 기반 테스트” 에 대해 알아보도록 하자. 상태 기반 테스트의 경우 말 그대로 객체의 그 당시의 상태를 가지고 테스트하는 것이다. 만약 Setter 로 상태를 변경했으면 Getter 로 변경된 상태를 테스트해야한다. 반면 행위 기반 테스트의 경우 A 메소드에서 B 메소드를 호출하는 행위에 집중해서 테스트한다.

일반적인 테스트 더블은 상태를 기반으로 테스트 하지만 Mock 의 경우 행위를 기반으로 테스트 한다.

이렇게 Mock 객체에 대한 간략한 내용과 Test Double 에 대해서 알아보았다. 다음에는 Mokito 를 활용한 예제를 알아보도록 하자.

댓글남기기