포스팅에서 다룰 내용
본 게시글은 Unit Testing 도서 중 2장을 정리하였습니다.
- 단위 테스트란?
- 공유 의존성, 비공개 의존성, 휘발성 의존성 간의 차이점
- 단위 테스트의 두 분파 : 고전파 vs 런던파
- 단위 테스트, 통합 테스트, 엔드 투 엔드 테스트의 차이점
단위 테스트의 정의
단위 테스트는
- 작은 코드 조각을 검증하고
- 빠르게 수행하고
- 격리된 방식으로 처리하는 자동화된
테스트이다. 이때 작은 코드 조각은 단일 클래스 또는 클래스 내의 메소드를 가리킨다. 다음 정의 중 3번째 정의에서 고전파와 런던파를 구분할 수 있다.
격리 문제에 대한 런던파의 접근
코드 조각을 격리된 방식으로 검증한다는 것은 런던파에서 테스트 대상 시스템을 협력자에게서 격리하는 것을 일컫는다. 즉 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역으로 대체해야 한다. 이런 방식으로 동작을 외부 영향과 분리하여 테스트 대상 클래스에만 집중할 수 있도록 하는 것이다.
이 방법의 장점은 테스트가 실패하면 어떤 코드베이스가 문제인지 확실히 알 수 있다는 점이다.
또한 객체 그래프를 분할할 수 있다. 의존성을 가진 코드베이스를 테스트하려면 전체 객체 그래프를 다시 만들어야 하기 때문에 테스트 대역을 사용하면 객체 그래프를 만들지 않아도 되기 때문에 용이하다.
만약 고객이 가게에서 샴푸를 5개 구매하는 프로그램이 있다고 가정해보자.
고전적인 스타일로 테스트를 하게 되면 SUT는 고객이, 협력자는 가게가 되며 실제 가게 인스턴스를 생성하고 값을 변경하며 테스트를 진행해야 한다.
고전적인 스타일의 테스트
AAA 패턴을 사용하는데 준비, 실행, 검증 패턴이다.
준비 단계에서는 SUT(테스트 대상 시스템)과 협력자가 필요하다. MUT(테스트 대상 메소드)는 테스트에서 호출한 SUT의 메소드를 가리키는 말로 MUT는 메소드를, SUT는 클래스 전체를 가리킨다.
하지만, 고객이 가게와 격리된 후에 테스트를 하게 되면, 준비 단계에서 협력자 인스턴스를 실제 생성하지 않고 Moq의 내장 클래스를 사용하여 대체할 수 있다. 이렇게 되면 실제 협력자의 실제 상태와 관계 없이 테스트가 요구하는 방식으로 요청에 응답할 수 있다. 검증 단계에서도 이전에는 가게 상태를 검증하는 방식에서 가게와 고객 간의 상호 작용을 검사하는 방식으로 바뀐다. 즉 고객이 가게에서 호출을 올바르게 했는지(1번이면 성공, 0번이면 실패)를 확인하면 된다.
Mock
목은 테스트 대상 시스템과 협력자 간의 상호 작용을 검사할 수 있는 특별한 테스트 대역이다. 테스트 대역의 부분집합이라 볼 수 있다.
런던파의 접근 방식의 장점은 아래와 같다.
- 입자성이 좋다.
- 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다.
- 테스트가 실패하면 어떤 기능이 실패했는지 확실히 알 수 있다.
격리 문제에 대한 고전파의 접근
고전파에서 생각하는 격리는 테스트를 어떤 순서로든 가장 적합한 방식으로 실행할 수 있으며 서로의 결과에 영향을 미치지 않는 방식이다. 여러 클래스를 한 번에 테스트해도 괜찮다는 의미이기도 하다. 이렇게 되면 DB, 파일 시스템 등 프로세스 외부 의존성에 의해 영향을 받을 수 있다.
공유 의존성(shared dependency)
테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성이다.
대표적인 예시로는 정적 가변 필드나 데이터베이스가 있다. 필드의 변경 사하이 동일한 프로세스 내에서 실행되는 모든 단위 테스트에서 볼 수 있기 때문이다.
++ 추가 설명
여러 단위 테스트가 동일한 의존성 객체를 공유하는 것을 의미한다. 이 경우, 하나의 테스트에서 이 객체를 변경하거나 상태를 수정하면 다른 테스트에 영향을 미칠 수 있다. 이는 테스트 간의 독립성을 해치게 되어, 테스트가 서로 간섭하거나 의도하지 않은 오류를 발생시킬 수 있다.
비공개 의존성(private dependency)
공유하지 않는 의존성을 이야기 한다.
프로세스 외부 의존성(out-of-process dependency)
어플리케이션 실행 프로세스 외부에서 실행되는 의존성이며 아직 메모리에 없는 데이터에 대한 프록시이다.
프로세스 외부 의존성은 대부분 공유 의존성에 해당한다.
따라서 고전파는 테스트 대역을 테스트 간에 공유 상태를 일으키는 의존성에 대해서만 사용한다. 비공개 의존성은 그대로 둔다. 공유 의존성은 테스트 대상 클래스 간이 아니라 단위 테스트 간에 공유한다. 그런 의미에서 싱글톤 의존성은 각 테스트에서 새 인스턴스를 만들 수 있기만 하면 공유되지 않는다. 제품코드에는 싱글톤 인스턴스가 단 하나만 있지만 테스트는 이 패턴을 따르지 않고 재사용하지도 않는다 따라서 이러한 의존성은 비공개인 것이다.
++ 추가 설명
- 싱글톤 패턴(Singleton Pattern)은 클래스의 인스턴스를 단 하나만 생성하여, 이를 전역적으로 공유하도록 하는 디자인 패턴이다. 제품 코드에서는 이 패턴이 유용할 수 있지만, 단위 테스트에서는 문제가 될 수 있다. 테스트 간에 상태가 공유되기 때문에 테스트의 독립성이 보장되지 않을 수 있기 때문이다.
- "싱글톤 의존성은 각 테스트에서 새 인스턴스를 만들 수 있기만 하면 공유되지 않는다"
테스트 코드에서 싱글톤 패턴을 따르지 않고 매 테스트마다 새로운 인스턴스를 생성한다면, 각 테스트가 서로 독립적이게 되고, 상태를 공유하지 않는다는 의미이다. 즉, 테스트 코드에서는 싱글톤 객체를 사용하지 않고, 테스트의 독립성을 유지하기 위해 새 인스턴스를 매번 생성하는 방식을 사용한다는 이야기이다. - "제품코드에는 싱글톤 인스턴스가 단 하나만 있지만 테스트는 이 패턴을 따르지 않고 재사용하지도 않는다 따라서 이러한 의존성은 비공개인 것이다"
제품 코드에서 싱글톤 인스턴스가 단 하나 존재하지만, 테스트 코드에서는 그 싱글톤 인스턴스를 공유하지 않고 매번 새 인스턴스를 생성하여 테스트의 독립성을 유지한다는 뜻이다. 여기서 "비공개"란, 이 싱글톤 객체가 테스트 내에서 공유되지 않으며, 각 테스트마다 독립적인 인스턴스를 사용한다는 의미이다.
휘발성 의존성
휘발성 의존성이란 다음 속성 중 하나를 나타내는 의존성이다.
개발자 머신에 기본 설치된 환경 외에 런타임 환경의 설정 및 구성을 요구한다.
비결정적 동작을 포함한다. (예: 난수생성기, 현재시간/날짜 반환기)
공유 의존성과 휘바성 의존성은 겹치는 부분이 있다.
공유 의존성을 대체하면 테스트 실행 속도도 높일 수 있다. 공유 의존성은 거의 실행 프로세스 외부에 있기 때문에 비공개 의존성보다 호출이 오래 걸리기 때문이다.
단위 테스트의 런던파와 고전파
결국 런던파는 테스트 대상 시스템에서 협력자를 격리 하는 것으로, 고전파는 단위 테스트끼리 격리하는 것으로 본다. 요약하면 아래와 같다.
격리 주체 | 단위의 크기 | 테스트 대역 사용 대상 | |
런던파 | 단위 | 단일 클래스 | 불변 의존성 외 모든 의존성 |
고전파 | 단위 테스트 | 단일 클래스 또는 클래스 세트 | 공유 의존성 |
고전파와 런던파가 의존성을 다루는 방법
런던파는 테스트에서 일부 의존성을 그대로 사용할 수 있도록 한다. 절대 변하지 않는 불변(값객체, 값) 객체는 동일한 내용을 가지고 있다면 어떤 객체를 사용하든 상관없기 때문에 교체하지 않는다.
협력자
협력자는 공유하거나 변경 가능한 의존성이다.
고전파와 런던파의 비교
한 번에 한 클래스만 테스트하기
런던파는 클래스 단위로 테스트를 한다. 하지만 동작 단위로 테스트를 하는 것이 좋다.
상호 연결된 클래스의 큰 그래프를 단위 테스트하기
고전파는 테스트 대상 시스템을 설정하려면 전체 객체 그래프를 생성해야 하기 때문에 작업량이 많을 수 있다.
버그 위치 정확히 찾아내기
런던파는 특정 테스트만 실패하기 때문에 정확한 위치를 찾아낼 수 있다.
고전파와 런던파 사이의 다른 차이점
- 테스트 주도 개발(TDD)을 통한 시스템 설계 방식
테스트 주도 개발
1. 추가해야 할 기능과 어떻게 동작해야 하는지를 나타내는 실패 테스트를 작성한다.
2. 테스트가 통과할 만큼 충분히 코드를 작성한다. 이 단계에서 코드가 깨끗하거나 명쾌할 필요는 없다.
3. 코드를 리팩토링한다.
런던파는 하향식 TDD로 이루어진다.
- 목을 사용해 예상 결과를 달성하고자 시스템이 통신해야 하는 협력자를 지정한다.
- 모든 클래스를 구현할 때까지 클래스 그래프를 그려나간다.
고전파는 상향식 TDD로 이루어진다. 도메인 모델을 시작으로 최종 사용자가 소프트웨어를 사용할 수 있을 때까지 계층을 그 위에 더 둔다.
- 과도한 명세 문제 (테스트가 SUT의 구현 세부 사항에 결합되는 것)
런던파는 고전파보다 테스트가 구현에 더 자주 결합된다.
두 분파의 통합 테스트
통합 테스트란 공유 의존성, 프로세스 의존성뿐 아니라 조직 내 다른 팀이 개발한 코드 등과 통합해 작동하는지도 검증하는 테스트이다.
런던파는 실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다. 고전파가 작성한 대부분의 테스트가 런던파네엑는 통합 테스트로 느껴질 것이다.
고전파에서 생각하는 통합 테스트란
- 단일 동작 단위를 검증하고
- 빠르게 수행하고
- 다른 테스트와 별도로 처리하는
기준 중 하나를 충족하지 않는 테스트이다.
통합 테스트의 일부인 엔드 투 엔드 테스트
통합 테스트의 일부로 코드가 프로세스 외부 종속성과 함께 어떻게 작동하는지 검증한다. 일반적으로 통합 테스트보다 의존성을 더 많이 포함한다. 따라서 모든 외부 어플리케이션을 포함해 시스템을 최종 사용자의 관점에서 검증하는 것을 의미한다. 엔드 투 엔드 테스트는 유지 보수 측면에서 비용이 가장 많이 들기 때문에 모든 단위 테스트와 통합 테스트를 통과한 후 빌드 프로세스 후반에 실행하는 것이 권장된다.
'Test' 카테고리의 다른 글
[Unit Testing] 단위 테스트 구성 방법 (0) | 2024.08.16 |
---|---|
[Unit Testing] 단위 테스트 소개 (목표, 커버리지 지표) (0) | 2024.08.13 |