Be-Developer

UnitTesting 05 목과 테스트 취약성

05 목과 테스트 취약성

Mock, Stub 구분

  • Mock
    • mock, spy
    • 외부로 나가는 상호작용을 모방하고 검사하는데 도움이 된다.
      • 상호작용 : SUT가 상태를 변경하기 위한 의존성을 호출하는것.
    • 관련 의존성간의 상호작용을 모방하고 검사한다.
  • Stub
    • stub, dummy, fake
    • 내부로 들어오는 상호작용을 모방하는데 도움이 된다.
      • 상호작용 : SUT가 입력 데이터를 얻기 위한 의존성을 호추하는 것.
    • dummy : null이나 하드코딩된 값. sut인터페이스 만족이 목적이므로, 최종결과에 영향을 주지 않는다.
    • stub : 시나리오마다 다른 값을 반환하게끔 구성
    • fake : stub과 같지만, fake는 아직 존재하지않는 의존성을 대체하고자 구현한다. (?)
    • 관련 의존성을 모방만 하고, 검증하지 않는다.
    • stub과의 상호작용을 검증하는 것은 취약한 테스트를 야기하는 일반적인 안티패턴 ```java // mock public this_is_mock(){ var mock = new Mock(Some.class); var sut = new Sut(mock);

      sut.doSomething();

      then(mock).should(only()).innerMethod(); }

    //stub public this_is_stub(){ var stub = new Mock(Some.class); when(stub.innerMethod()).thenReturn(10); var sut = new Sut(mock);

    var result = sut.createSomething();

    assertThat(result).isEqualTo(10) //then(mock).should(only()).innerMethod(); <- over specification, 깨지기 쉬운 테스트. } ```

    도구로서의 mock, 테스트로서의 mock

  • 도구로서의 mock = Mock framework
  • 테스트로서의 mock = mocking된 클래스의 인스턴스

    mock이면서 stub인것

  • stub 처럼 준비된 응답을 설정하고, mock처럼 검증을 하는데, 각각 가정,검증하는 메서드는 다른 경우.

    mock과 stub은 CQRS처럼 Command,Query 와 관련이 있다.

  • mock = Command = void = 부작용 초래(내부 상태 변경)
  • stub = Query = return result = 부작용 없음 = 멱등성 보장.

    식별할 수 있는 동작과 구현 세부사항

  • 공개 API가 식별할 수 있는 동작의 범위를 넘어서면 시스템은 구현 세부사항을 유출한다.
  • 클라이언트가 목표를 달성하는데 도움이 되는 작업(method)와 상태(field)를 노출하라
  • 일반적으로 단일 목표를 달성하고자 클래스에서 호출해야하는 메서드가 1개이상인 경우, 세부사항 유출가능성이 있다. ```java // 유출 String normalizedName = user.normalize(newName); user.name = normalizedName;

//숨김 user.name = newName; // setter 내에서 normalize 메서드 호출. ```

잘 설계된 API와 캡슐화

  • 캡슐화는 궁극적으로 단위테스트와 동일한 목표를 달성한다.
  • 구현 세부사항을 숨기면 클래스 내부를 가릴 수 있기때문에, 손상시킬 위험이 적다.
  • 데이터와 연산을 결합하면 해당 연산이 클래스의 불변성을 위반하지 않도록 할 수 있다.

    Mock 과 테스트 취약성 간의 관계

    육각형 아키텍처 (hexagonal architecture)

  • 도메인(buisness logic) + 애플리케이션 서비스(db조회및 도메인로직 호출 ,db저장) = 육각형 = 하나의 애플리케이션
  • 도메인 계층과 애플리케이션 서비스 계층간의 관심사 분리
    • 도메인계층은 해당 비즈니스 로직에 대해서만 책임을 져야하며, 나머지는 제외해야한다.
    • 애플리케이션 서비스에는 어떤 비즈니스로직도 있어선 안된다.
  • 애플리케이션 내부 통신
    • 서비스계층에서 도메인 계층으로 단방향 의존성 흐름을 규정한다.
    • 도메인계층은 서비스계층에 의존해선 안된다.
  • 애플리케이션 간의 통신
    • 외부 애플리케이션은 애플리케이션 서비스 계층에 있는 공통 인터페이스를 통해 해당 애플리케이션에 연결되며, 도메인에 직접 접근 불가.
  • 캡슐화를 잘 하면 테스트도 fractal 구조를 갖는다.

    시스템 내부 통신과 시스템간의 통신

  • 내부통신 : 애플리케이션 내 클래스간의 통신
    • 클라이언트의 목표와 직접적인 연관이 없고, 구현 세부사항에 해당.
    • mock이 사용되면 리팩터링 내성이 낮아진다.
  • 시스템간의 통신 : 다른 애플리케이션과의 통신
    • 애플리케이션에 항상 있어야하는 인터페이스이며,(리팩터링 범주가 아님) 해당 시스템을 식별할수있는 동작이다.
    • mock을 사용하면 애플리케이션간의 통신패턴을 확인할때 좋다.

      런던파와 고전파의 재고

  • 런던파를 따라 목을 무분별하게 사용하면 구현 세부사항에 결합되어 리팩터링 내성이 낮아진다.
  • 고전파는 테스트간에 공유하는 의존성(시스템간의 통신)만 mocking하므로 리팩터링내성에 유리하다.

    모든 프로세스 외부 의존성을 목으로 해야하는것은 아니다

    의존성 유형

    1. 공유 의존성 : 테스트간에 공유하는 의존성
    • 고전파에서는 피하라고 한다. 테스트 병렬처리할수없기때문.
    • 일반적으로는 이걸 Mock, stub으로 교체한다.
      1. 프로세스 외부 의존성 : 프로그램의 실행 프로세스 외에 다른 프로세스를 점유하는 의존성
    • 완전 통제권을 가진 프로세스 외부 의존성에 mock을 사용하면 깨지기 쉬운 테스트가 된다. (ex DB)
      1. 비공개 의존성 : 공유하지 않는 모든 의존성

Mocks aren’t stubs 나중에 읽어보기