본문 바로가기
Study/단위 테스트

[단위 테스트 스터디] 02. 단위 테스트란 무엇인가

by irerin07 2023. 3. 26.
728x90

2장

2.1 ‘단위 테스트’의 정의

  • 작은 코드 조각 혹은 작은 코드 단위를 빠르고 격리된 방식으로 처리하는 자동화된 테스트
  • 고전파와 런던파를 가르는 기준은 격리가 무엇인지에 대한 정의로 갈린다.

2.1.1 격리 문제에 대한 런던파의 접근

  • 테스트 대상 클래스에만 집중해야 한다.
    • 테스트를 하고자 하는 클래스가 다른 클래스들에 의존한다면 이 의존성들은 테스트 대역으로 대체되어야한다.
  • 테스트 대상 클래스에만 집중하는 방식의 장점
    • 테스트 실패시 대상 클래스에 문제가 있다는 것을 확신할 수 있다.
      • 의존성은 모두 대역으로 대체 되었기때문에 의존 클래스는 의심할 필요가 없다.
      Untitled
    • 객체 그래프를 효과적으로 분리하여 테스트 준비를 크게 줄일 수 있다.
    • 한 번에 한 클래스만 테스트를 진행함으로 단위 테스트 스위트를 간단한 구조로 할 수 있다.
    • Untitled

2.1.2 격리 문제에 대한 고전파의 접근

  • 반드시 코드를 격리하는 방식을 따르지 않는다. 다만 단위 테스트끼리는 서로 격리하여 테스트를 실행한다.
    • 병렬이나 순차등의 순서에 구애 받지 않고 서로의 결과에 영향을 미치지 않는다.
      • 테스트 A가 준비단계에서 DB에 고객을 생성한다.
        테스트 A를 실행하기 전 테스트 B의 준비단계에서 고객을 삭제한다.
      이 테스트를 병렬로 실행시 테스트 A는 실패하게 되는데 이는 코드의 문제가 아니라 테스트 B의 간섭 때문이다.
    • 공유 의존성은 테스트간 공유되고 서로의 결과에 영향을 미치는 수단을 제공한다.
      • 정적 가변 필드와 같은 공유 의존성의 변경 사항은 동일한 프로세스에서 실행되는 모든 단위 테스트에서 볼 수 있다. 예를 들어 DB
    • 테스트 대역을 사용할 수 있지만, 테스트 간 공유 상태를 일으키는 의존성에 대해서만 사용한다.

Untitled

  • 공유 의존성은 테스트 대상 클래스 간 공유하는것이 아니라 단위 테스트 간에 공유한다.
    • 싱글턴 의존성은 각 테스트에서 새 인스턴스를 만들 수 있다면 공유되지 않는다.
    • 제품 코드에 싱글턴 인스턴스 단 하나만 존재하지만, 테스트는 이 패턴을 따르지 않고 재사용 하지 않는다.
      • 이는 비공개 의존성이라 할 수 있다.
    • 설정 클래스는 일반적으로 하나만 생성하며 모든 코드에서 이 인스턴스를 재사용한다.
      • 그러나 생성자 등을 통해 다른 모든 의존성이 SUT(System Under Test, 테스트 대상 시스템)에 주입되면 각 테스트에서 새 인스턴스를 만들 우 있다.
      • 즉, 테스트 스위트 전체에서 단일 인스턴스를 유지할 필요가 없다.
      • 하지만 새 파일 시스템이나 데이터베이스를 새로 만들 수는 없으니 테스트 간에 공유되거나 테스트 대역으로 대체되어야 한다.
    • 공유 의존성을 대체하는 또 다른 이유는 테스트 실행 속도를 높이는 데 있다.
      • 보통 공유 의존성은 실행 프로세스 외부에 있기때문에 이들에 대한 호출은 비공개 의존성에 비해 더 오래 걸린다.
      • 단위 테스트 두번째 속성으로 빨리 실행해야 하는 필요성이 있으므로, 공유 의존성을 가진 테스트는 단위 테스트 영역에서 통합 테스트 영역으로 넘어간다
    • 공유 의존성이 없는 한 여러 클래스를 묶어 단위 테스트를 할 수 있다.

2.2 단위 테스트의 런던파와 고전파

Untitled

2.2.1 고전파와 런던파가 의존성을 다루는 방법

  • 런던파는 테스트에서 일부 의존성을 그대로 사용할 수 있도록 하고 있다.
    • 불변 객체는 대체할 필요가 없다.
      • 예제 2.2에서 나온 C# 열거형 Product 인스턴스가 그 예이다.
      • 변경 가능성이 있는 의존성 만 대체한다.

Untitled

고전파에서는 공유 의존성을 테스트 대역으로 교체하지만 런던파에서는 변경 가능한 한 비공개 의존성도 테스트 대역으로 교체할 수 있다.

  • 협력자
    • 공유하거나 변경 가능한 의존성
  • 모든 프로세스 외부 의존성이 공유 의존성의 범주에 속하지 않는다.
    • 공유 의존성은 거의 항상 프로세스 외부에 있지만, 프로세스 외부에 있다고 반드시 공유 의존성은 아니다.
    • 공유 의존성이지만 프로세스 외부가 아닌 의존성의 예시는 싱글턴 혹은 클래스의 정적 필드가 있다.
    • 공유 의존성이며 프로세스 외부에 존재하는 의존성의 예시는 데이터 베이스가 있다. 주 프로세스 외부에 상주하며 변경이 가능하다.

Untitled

2.3 고전파와 런던파의 비교

  • 둘의 차이는 단위 테스트의 정의에서 격리 문제를 어떻게 다루는지에 있으며 이는 결국 테스트해야 할 단위의 처리와 의존성 취급에 대한 방법으로 넘어간다.
  • 저자가 고전파를 선호하는 이유는 다음과 같다.
    • 고품질의 테스트를 만들고 지속 가능한 성장을 달성하는 데 더 적합하다고 판단함.
      • 목을 사용하는 테스트는 불안정한 경향이 있다. 예를 들면 취약성
  • 하지만 런던파 역시 이점을 제공한다.
    • 세밀한 테스트는 한 번에 한 클래스만 확인한다. 즉 입자성이 좋다.
    • 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다. 모든 협력자는 대역으로 대체되기 때문이다.
    • 테스트 실패시 어떤 기능에서 문제가 발생했는지 확실히 알 수 있다.
      • 테스트 대상 시스템이 값 객체를 사용하는 상황이 있을 수 있고 이 값 객체의 변경으로 인해 테스트에 영향이 있을 수 있지만, 이러한 경우는 흔하지 않다.

2.3.1 한 번에 한 클래스만 테스트하기

  • 런던파는 클래스를 단위로 간주한다.
  • 객체지향 개발자라면 클래스는 테스트에서 검증한 원자 단위로 취급하게 되지만 이는 오해의 소지가 있다.
    • 테스트는 코드의 단위가 아니라 동작의 단위, 문제 영역에 의미가 있는것. 비즈니스 담당자가 유용하다 인식할 수 있는 것을 검증해야한다.
  • 테스트가 단일 동작단위를 검증하는 한 좋은 테스트이며 이보다 작은 것을 목표로 삼는다면 되려 해당 테스트가 무엇을 검증하려는지 이해하기가 어려워지기 때문에 단위 테스트를 훼손시킨다.
    • 응집도 높은 이야기 → 우리집 강아지를 부르면, 바로 나에게 온다.
    • 응집도 낮은 이야기 → 우리집 강아지를 부르면 오른쪽 앞 다리가 어쩌고 왼쪽 앞 다리는 어쩌고 머리가 어떻게 되며 꼬리는….
    • 강아지가 나에게 오는가(실제 동작)에 대한 검증이 아니라 각각의 부위(개별 클래스)에 대한 검증을 목표로 하면 테스트의 응집도가 낮아진다.

2.3.2 상호 연결된 클래스의 큰 그래프를 단위 테스트하기

  • 테스트 대상 클래스에 의존성이 있고 이 의존성이 다시 각각의 의존성이 있는 식으로 여러 계층에 걸쳐 계속되는 식의 복잡한 의존성 그래프가 생성되는 경우 목을 사용하면 쉽게 테스트가 가능하다.
  • 다만 이런 상황에서 크고 복잡한 그래프를 테스트할 방법을 찾기보다 애초에 이런 클래스 그래프를 갖지 않도록 하는데 집중해야한다.
    • 보통 이런 경우는 코드 설계 문제의 결과이다.

2.3.3 버그 위치 정확히 찾아내기

  • 런던 스타일은 보통 SUT에 버그가 포함된 테스트만 실패한다.
  • 고전적인 방식은 하나의 버그가 전체 시스템에 걸쳐 테스트 실패를 야기할 수 있다.
    • 이는 문제의 원인을 파악하기 힘들게 한다.
  • 하지만 큰 문제는 아니다.
    • 테스트를 정기적으로, 소스 코드가 변경 될 때마다 테스트를 실행하면 버그의 원인을 쉽게 찾을 수 있다.
    • 마지막으로 가한 수정이 무엇인지 알기 때문에 쉽게 발견이 가능하다.
    • 테스트 스위트 전체에 걸쳐 계단식으로 실패한다면 방금 고장 난 코드 조각이 큰 가치가 있다는 것을 보여준다.

2.3.4 고전파와 런던파 사이의 차이점

  • TDD를 통한 시스템 설계 방식의 차이
    • 런던파는 하향식 TDD로 이어지며 전체 시스템에 대한 기대치를 설정하는 상위 레벨 테스트부터 시작.
      • 예상 결과를 달성하기 위해 시스템이 통신해야 하는 협력자를 지정하고, 모든 클래스를 구현할 때까지 클래스 그래프를 다져나간다.
      • SUT의 모든 협력자를 차단해 해당 협력자의 구현을 나중으로 미룰 수 있다.
    • 고전파는 상향식 TDD로 이어진다.
      • 도메인 모델을 시작으로 최종 사용자가 소프트웨어를 사용할 수 있을 때까지 계층을 그 위에 더 둔다.
  • 테스트가 SUT의 구현 세부 사항에 결합 여부
    • 두 분파간 가장 중요한 차이점
      • 런던파는 테스트가 구현에 더 자주 결합된다.
        • 이로 인해 런던 스타일과 목을 전반적으로 아무 데나 쓰는것에 대해 주로 이의가 제기된다.

2.4 두 분파의 통합 테스트

  • 통합 테스트의 정의에도 차이가 있다.
    • 이는 격리 문제에 대한 견해에서 차이가 나기 때문에 자연스럽게 다른 의견으로 이어진다.
  • 런던파
    • 실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주
    • 고전 스타일로 작성된 대부분의 테스트는 런던파에게 있어서는 통합 테스트로 느껴진다.
      • 예제 1.4는 고전 스타일에선 전형적인 단위 테스트지만 런던파에겐 통합 테스트로 보인다.
  • 고전파 관점의 단위 테스트
    • 단일 동작 단위를 검증
    • 빠른 수행시간
    • 다른 테스트와 별도로 처리
  • 통합 테스트는 이러한 기준 중 하나를 충족하지 않는다.
    • 데이터베이스와 같은 공유 의존성에 접근하는 테스트는 다른 테스트와 분리하여 실행이 불가능하다. 즉 단일 동작 단위 검증이 어렵다
    • 프로세스 외부 의존성에 접근하면 테스트가 느려진다. 빠른 수행시간을 확신할 수 있다.
    • 둘 이상의 동작 단위를 검증할때는 통합 테스트다.
      • 비슷한 단계를 따르지만 다른 동작 단위를 검증하는 느린 테스트가 두 개 있을 때, 하나로 합치는 것이 타당할 수 있다.

2.4.1 통합 테스트의 인부인 엔드 투 엔드 테스트

  • 쉽게 말하자면 통합 테스트는 공유 의존성, 외부 의존성, 다른 팀이 개발한 코드등화 통합하여 정상 작동하는지 검증하는 테스트.
  • 일반적으로 통합 테스트는 프로세스 외부 의존성을 한두 개 갖고 작동한다
  • 반면 엔드 투 엔드 테스트는 프로세스 외부 의존성을 전부 또는 대다수 갖고 작동한다.
    • 모든 외부 애플리케이션을 포함해 시스템을 최종 사용자의 관점에서 검증하는 것.

Untitled

  • 다만 아무리 엔드 투 엔드 테스트를 하더라도 모든 외부 의존성을 처리하지 못할 수 있다.
    • 일부 의존성의 테스트 버전이 없거나 해당 의존성을 필요한 상태로 자동으로 가져오는게 불가능 할 수 있다.
    • 이런 경우 여전히 테스트 대역을 사용할 필요가 있으며 사실 통합 테스트와 엔드 투 엔드 테스트 사이에는 뚜렷한 경계가 없다고 할 수 있다.
728x90

'Study > 단위 테스트' 카테고리의 다른 글

[단위 테스트 스터디] 03. 단위 테스트 구조  (0) 2023.03.28