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

Part1 더 큰 그림 [상]

by Thinking 2024. 12. 15.

Chapter 1 : 단위 테스트의 목표

이 책에서 어떤 단위 테스트 기술이 좋은지 구별하는 데 도움이 될 것이다. 테스트에 대한 비용 편익 분석 방법을 배우고, 특정 상황에서 적절한 테스트 기술을 배울 수 있다. 공통적인 안티 패턴을  피하는 방법도 배운다. 기본부터 시작해 볼 예정이고, 테스트 작성과 유지 보수의 목표를 설명하여 테스트 스위트를 잘 작성할 수 있는 방법을 소개한다.

 

 

1.1 단위 테스트 현황

논쟁은 '단위 테스트를 작성해야 하는가'에서 '좋은 단위 테스트를 작성하는 것은 어떤 의미인가'로 바뀌었다. 이상적인 단위 테스트에 대해 정확하고 과학적인 정의를 다룬다. 기업용 애플리케이션이란 조직 내부 프로세스를 자동화하거나 지원하기 위한 응용프로그램이다. 일반적으로 아래와 같은 특성이 있다.

- 높은 비즈니스 복잡도

- 긴 프로젝트 수명

- 중간 크기의 데이터

- 낮은 수준이나 중간 수준 정도의 성능 요구

 

 

1.2 단위 테스트의 목표

단위 테스트와 코드 설계의 관계 : 코드 조각을 단위 테스트하는 것은 훌륭한 리트머스 시험이다. 비교적 높은 정확도로 저품질 코드를 가려낸다. 코드를 단위 테스트하기 어렵다면 코드 개선이 반드시 필요하다는 것을 의미한다. 보통 강결합에서 저품질이 나타나는데, 여기서 제품 코드가 서로 충분히 분리되지 않아 따로 테스트하기 어려움을 뜻한다.

 

그럼 단위 테스트의 목표는 무엇인가? 소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것이다. 지속성과 확장성이 핵심이며, 이를 통해 장기적으로 개발 속도를 유지할 수 있다.

 

단지 프로젝트에서 테스트를 더 많이 쏟아내도 단위 테스트의 목표를 달성할 수 없다. 테스트의 가치와 유지 비용을 모두 고려해야 한다.

- 기반 코드를 리팩터링 할 때 테스트도 리팩터링하라.

- 각 코드 변경 시 테스트를 실행하라

- 테스트가 잘못된 경고를 발생시킬 경우 처리하라.

- 기반 코드가 어떻게 동작하는지 이해하려고 할 때는 테스트를 읽는 데 시간을 투자하라.

 

어떤 사람들은 테스트가 많을수록 좋다고 생각한다. 코드는 자산이 아니라 책임이다. 코드가 많아질수록, 소프트웨어 내 버그에 노출되는 표면적이 넓어지고, 유지비가 증가하기에 가능한 적은 코드로 문제를 해결하는 것이 좋다.

 

 

1.3 테스트 스위트 품질 측정을 위한 커버리지 지표

커비리지 지표 : 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타낸다. 커버리지 지표는 각기 다른 유형이 있으며, 테스트 스위트의 품질을 평가하는 데 자주 사용된다. 일반적으로 커버리지 숫자가 높을수록 더 좋다.

 

예로 코드 커버리지가 적을 때는 테스트가 충분치 않다는 좋은 증거다. 반대로 100% 커버리지라고 해서 반드시 양질의 테스트 스위트라고 보장하지는 않는다.

 

가장 많이 사용하는 커버리지 지표로 코드 커버리지가 있으며, 테스트 커버리지라고도 알려져 있다. 단점으로 코드가 작을수록 테스트 커버리지 지표는 더 좋아지는데, 원래 라인 수만 처리하기 때문이다.

(코드 커버리지 = 실행 코드 라인 수 / 전체 라인 수)

 

또 다른 커버리지 지표는 분기 커버리지다. 코드 커버리지의 단점을 극복하는 데 도움이 되므로 더 정확한 결과를 제공한다. 원시 코드 라인 수를 사용하는 대신 if 문과 switch 문과 같은 제어 구조에 중점을 둔다. (분기 커버리지 = 통과 분기 / 전체 분기 수) 분기 커버리지 지표는 분기 개수만 다루며, 해당 분기를 구현하는 데 얼마나 코드가 필요한지 고려하지 않는다.

 

테스트 스위트의 품질을 결정하는 데 어떤 커버리지 지표도 의존할 수 없는 이유는 아래와 같다. 각각 이유를 알아보자.

- 테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.

- 외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.

 

1. 가능한 모든 결과를 검증한다고 보증할 수 없음 -> 테스트 대상 시스템이 낸 결과가 정확히 예상하는 결과인지 확인해야 한다. 따라서 커버리지 지표가 의미 있으려면, 모든 측정 지표를 검증해야 한다. 그러면 테스트 대상 코드에 대해 각각의 결과를 철저히 검증한다고 가정하자. 이렇게 하면 분기 커버리지 지표와 함께 신뢰할 수 있는 구조라고 할 수 있는가? 또 테스트 스위트 품질을 결정하는 데 사용할 수 있는가? 안타깝게도 아니다.

 

2. 외부 라이브러리의 코드 경로를 고려할 수 없음.

수많은 예외 상황에 빠질 수 있지만, 테스트에서 모든 예외 상황을 다루는지 확인할 방법이 없다. 예로 문자를 정수로 변환하는 parse 과정에서 들어오는 문자가 너무 길다거나, null이라면? 이는 커버리지 지표가 외부 라이브러리의 코드 경로를 고려해야 한다는 것이 아니라, 해당 지표로 단위 테스트가 얼마나 좋은지 나쁜지를 판단할 수 없다는 것을 보여준다.

 

위를 통해 테스트 스위트 품질을 결정하기에 커버리지 지표만으로는 충분치 않다는 것을 알았다. 커버리지 지표를 보는 가장 좋은 방법지표 그 자체로 보는 것이며, 목표로 여겨서는 안 된다. 특정 커버리지 숫자를 목표로 하는 것은 단위 테스트의 목표와 반대되는 그릇된 동기 부여가 된다. 시스템의 핵심 부분은 커버리지를 높게 두는 것이 좋다. 하지만 이 높은 수준을 요구 사항으로 삼는 것은 좋지 않다.

 

다시 말해 커버리지 지표는 좋은 부정 지표이지만 나쁜 긍정 지표이다. 커버리지 숫자가 낮으면 문제 징후라 할 수 있고, 코드베이스에 테스트되지 않은 코드가 많다는 뜻이다. 그러나 높은 숫자도 별 의미는 없다. 그러므로 코드 커버리지를 측정하는 것은 품질 테스트 스위트로 가는 첫걸음일 뿐이다.

 

 

1.4 무엇이 성공적인 테스트 스위트를 만드는가?

이 장에서는 커버리지 지표를 사용하는 등 부적절한 테스트 스위트 품질 측정법을 다뤘다. 믿을 만한 방법은 스위트 내 각 테스트를 하나씩 따로 평가하는 것뿐이다. 평가를 하나씩 늘려가며 수행할 수도 있다. 요점은 테스트 스위트가 얼마나 좋은지 자동으로 확인할 수 없고, 개인 판단에 맡겨야 한다. 전체적으로 어떻게 테스트 스위트를 성공할 수 있는지 넓게 살펴보자. 성공적인 테스트 스위트는 아래 같은 특성이 있다.

 

1) 개발 주기에 통합돼 있다.

-> 모든 테스트는 개발 주기에 통합돼야 한다. 이상적으로는 코드가 변경될 때마다 작은 것이라도 실행해야 한다.

 

2) 코드베이스에서 가장 중요한 부분만을 대상으로 한다.

-> 단위 테스트 측면에서 모든 부분에 똑같이 주목할 필요는 없다. 시스템의 가장 중요한 부분에 단위 테스트 노력을 기울이고, 다른 부분은 간략하게 또는 간접적으로 검증하는 것이 좋다. 비즈니스 로직 테스트가 시간 투자 대비 최고의 수익을 낼 수 있다. 다른 모든 부분은 세 가지 범주로 나눌 수 있다.

 

[1] 인프라 코드

[2] 데이터베이스나 서드파티 시스템과 같은 외부 서비스 및 종속성

[3] 모든 것을 하나로 묶는 코드

 

이 중 일부는 단위 테스트를 철저히 해야 할 수 있다. 예로 인프라 코드에 중요 알고리즘이 있을 수 있기에 테스트를 많이 하는 것이 좋다. 그러나 일반적으로 도메인 모델에 관심을 더 많이 갖는 것이 옳다. 통합 테스트와 같이 일부 테스트는 도메인 모델을 넘어 시스템이 전체적으로 어떻게 작동하는지 확인할 수 있다. 이것도 괜찮지만, 초점은 도메인 모델에 머물러 있어야 한다. 이는 도메인 모델을 다른 애플리케이션 문제와 분리해야 단위 테스트에 대한 노력을 도메인 모델에만 집중할 수 있다.

 

3) 최소한의 유지비로 최대의 가치를 끌어낸다.

-> 단위 테스트에서 가장 어려운 부분은 최소 유지비로 최대 가치를 달성하는 것이다. 

 

[1] 가치 있는 테스트(더 나아가, 가치가 낮은 테스트) 식별하기

[2] 가치 있는 테스트 작성하기

 

가치고 높은 테스트를 식별하려면 기준틀이 필요하다. 반면이 가치 있는 테스트를 작성하려면 코드 설계 기술도 알아야 한다. 단위 테스트와 기반 코드는 얽혀 있으므로 코드베이스에 노력을 많이 기울여야 가치 있는 테스트를 만들 수 있다.

 

 

1.5 이 책을 통해 배우는 것

테스트 스위트 내의 모든 테스트를 분석하는 데 사용할 수 있는 기준틀을 설명한다. 이 기준틀이 기초이며, 새로운 관점에서 많은 테스트를 볼 수 있으며, 어떤 것이 프로젝트에 기여하고 어떤 것을 리팩터링해야 하거나 완전히 제거해야 하는지 알 수 있을 것이다.

 

 

Chapter 2 : 단위 테스트란 무엇인가

앞을 통해 해석의 차이, 단위 테스트에 접근하는 방법이 두 가지 뚜렷한 견해로 나뉘었다. 이 두 가지 견해는 '고전파'와 '런던파'로 알려져 있다. 고전파는 모든 사람이 단위 테스트와 테스트 주도 개발에 원론적으로 접근하는 방식이기 때문에 '고전'이라고 한다. 런던파는 런던의 프로그래밍 커뮤니티에서 시작됐다. 이는 뒤에서 다루겠다.

 

단위 테스트는 중요한 세 가지 속성이 있다.

1) 작은 코드 조각을 검증하고

2) 빠르게 수행하고

3) 격리된 방식으로 처리하는 자동화된 테스트다.

 

주의해서 볼 것은 3번이다. 격리 문제는 단위 테스트의 고전파와 런던파를 구분할 수 있게 해주는 근원적 차이에 속한다.

 

 

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

코드 조각을 격리된 방식으로 검증한다는 것을 무엇을 의미할까? 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역으로 대체해야 한다. 이런 식으로 동작을 외부 영향과 분리해서 테스트 대상 클래스에만 집중할 수 있다.

 

의존성이 포함된 테스트 대상 시스템을 검증하는 단위 테스트는 이제 해당 의존성과 별개로 수행할 수 있다. 이점으로 테스트가 실패하면 코드베이스의 어느 부분이 고장났는지 확실히 알 수 있다. 또한, 객체 그래프(같은 문제를 해결하는 클래스의 통신망)를 분할할 수 있는 것이다. 모든 클래스가 각각 직접적인 의존성을 갖고 있으며 또 그 의존성이 또 다른 의존성을 갖고 있기에 그래프가 복잡해질 수 있다.

 

[고전 스타일]

using Xunit;

namespace Book.Chapter2.Listing1
{
    public class CustomerTests
    {
        [Fact]
        public void Purchase_succeeds_when_enough_inventory()
        {
            // Arrange
            var store = new Store();
            store.AddInventory(Product.Shampoo, 10);
            var customer = new Customer();

            // Act
            bool success = customer.Purchase(store, Product.Shampoo, 5);

            // Assert
            Assert.True(success);
            Assert.Equal(5, store.GetInventory(Product.Shampoo));
        }

        [Fact]
        public void Purchase_fails_when_not_enough_inventory()
        {
            // Arrange
            var store = new Store();
            store.AddInventory(Product.Shampoo, 10);
            var customer = new Customer();

            // Act
            bool success = customer.Purchase(store, Product.Shampoo, 15);

            // Assert
            Assert.False(success);
            Assert.Equal(10, store.GetInventory(Product.Shampoo));
        }
    }
}

 

일반적인 3단 구성인 준비, 실행, 검증 패턴(AAA : Arrange, Act, Assert)을 사용한다. 준비 단계에서 테스트 대상 시스템(System under test, SUT)과 하나의 협력자를 준비한다. 위 예제는 고객(Customer)이 SUT, 상점(Store)이 협력자에 해당한다. 아래와 같은 2가지 이유로 협력자가 필요하다.

- 테스트 대상 메서드를 컴파일하려면 customer.Purchase()가 Store 인스턴스를 인수로 필요로 하기 때문

- 검증 단계에서 customer.Purchase()의 결과 중 하나로 상점 제품 수량이 감소할 가능성이 있기 때문

 

위 코드는 단위 테스트 고전 스타일 예로, 테스트는 협력자를 대체하지 않고 운영용 인스턴스를 사용한다. Customer와 Store 둘 다 효과적으로 검증하지만, Customer가 올바르게 작동하더라도 Customer에 미치는 Store 내부에 버그가 있으면 단위 테스트에 실패할 수 있다. 테스트에서 두 클래스는 서로 격리돼 있지 않다. 

 

 

[런던 스타일]

동일한 테스트에서 Store 인스턴스는 테스트 대역, 구체적으로 목으로 교체해본다.

목(Mock) -> 테스트 대상 시스템과 협력자 간의 상호 작용을 검사할 수 있는 특별한 테스트 대역이다.

 

- 테스트 대역은 실행과 관련 없이 모든 종류의 가짜 의존성을 설명하는 포괄적인 용어이다.

- 목은 그러한 의존성의 한 종류일 뿐이다.

 

using Moq;
using Xunit;

namespace Book.Chapter2.Listing2
{
    public class CustomerTests
    {
        [Fact]
        public void Purchase_succeeds_when_enough_inventory()
        {
            // Arrange
            var storeMock = new Mock<IStore>();
            storeMock
                .Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
                .Returns(true);
            var customer = new Customer();

            // Act
            bool success = customer.Purchase(storeMock.Object, Product.Shampoo, 5);

            // Assert
            Assert.True(success);
            storeMock.Verify(x => x.RemoveInventory(Product.Shampoo, 5), Times.Once);
        }

        [Fact]
        public void Purchase_fails_when_not_enough_inventory()
        {
            // Arrange
            var storeMock = new Mock<IStore>();
            storeMock
                .Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
                .Returns(false);
            var customer = new Customer();

            // Act
            bool success = customer.Purchase(storeMock.Object, Product.Shampoo, 5);

            // Assert
            Assert.False(success);
            storeMock.Verify(x => x.RemoveInventory(Product.Shampoo, 5), Times.Never);
        }
    }
}

 

다른 점을 살펴보자. 준비 단계에서 Store의 실제 인스턴스를 생성하지 않고, Moq의 내장 클래스인 Mock<T>를 사용해 대체한다. 또한 샴푸 재고를 추가해 Store 상태를 수정하는 대신 HasEnoughInventory() 메서드 호출에 어떻게 응답하는지 목에 직접 정의한다.

 

Store의 실제 상태와 관계없이 테스트가 요구하는 방식으로 요청에 응답한다. 검증 단계도 바뀌었고, 중요한 차이점이 여기에 있다. 여전히 이전과 같이 customer.Purchase 호출 결과를 확인하지만, 고객이 상점에서 올바르게 했는지 확인하는 방법이 다르다

 

 

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

격리 특성을 해석하는 또 다른 방법으로 고전적인 방법이 있다. 고전적인 방법에서 코드를 꼭 격리하는 방식으로 테스트해야 하는 것은 아니다. 대신 단위 테스트는 서로 격리해서 실행해야 한다. 이렇게 하면 테스트를 어떤 순서로든 가장 적합한 방식으로 실행할 수 있으며 서로의 결과에 영향을 미치지 않는다.

 

각각의 테스트를 격리하는 것은 여러 클래스가 모두 메모리에 상주하고 공유 상태에 도달하지 않는 한, 여러 클래스를 한 번에 테스트해도 괜찮다는 뜻이다. 이를 통해 테스트가 서로 소통하고 실행 컨텍스트에 영향을 줄 수 있다.

 

1) 공유 의존성 : 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성이다. 예로 정적 가변 필드(static mutable field)다. 데이터베이스도 예로 들 수 있다.

 

2) 비공개 의존성 : 공유하지 않는 의존성이다. 프로세스 외부 의존성은 애플리케이션 실행 프로세스 외부에서 실행되는 의존성이며, 아직 메모리에 없는 데이터에 대한 프록시다. 프로세스 외부 의존성은 대부분 공유 의존성에 해당하지만 모두 그렇지는 않다. 예로 데이터베이스는 프로세스 외부이면서 공유 의존성이다. 하지만 각 테스트 실행 전에 도커 컨테이너로 데이터베이스를 시작하면, 동일한 인스턴스로 작동하지 않기 때문에 프로세스 외부이면서 공유하지 않는 의존성이 된다.

 

공유 의존성은 테스트 대상 클래스(단위) 간이 아니라 단위 테스트 간에 공유한다. 그런 의미에서 싱글톤 의존성은 각 테스트에서 새 인스턴스를 만들 수 있기만 하면 공유되지 않는다. 제품 코드에는 싱글턴 인스턴스가 하나만 있지만, 테스트는 이 패턴을 따르지 않고 재사용하지도 않는다. 의존성은 비공개인 것이다.

 

 

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

런던파와 고전파로 나누어진 원인격리 특성에 있다. 런던파테스트 대상 시스템에서 협력자를 격리하는 것으로 보는 반면, 고전파단위 테스트끼리 격리하는 것으로 본다. 종합하면 세 가지 주요 주제에 대한 의견 차이가 있다.

-> 격리 요구 사항, 테스트 대상 코드 조각의 구성 요소, 의존성 처리

 

고전파의 격리 주체는 단위 테스트이다. 단위의 크기는 단일 클래스 or 클래스 세트이며, 테스트 대역 사용 대상은 공유 의존성이다.

런던파의 격리 주체는 단위이다. 단위의 크기는 단일 클래스이며, 테스트 대역 사용 대상은 불변 의존성 외 모든 의존성이다.

 

조금 더 자세히

고전파 격리 주체: 단위 테스트
→ 고전파에서는 테스트 코드 자체를 통해 시스템의 외부 요소와의 상호작용을 격리하려고 합니다.
→ 즉, 시스템 내에서 협력 객체를 직접 테스트하며, 종속성 주입 없이 실제 구현을 사용합니다

 

런던파 격리 주체: 단위
→ 런던파에서는 테스트 중인 단위(클래스, 함수)를 격리하며, 단위가 의존하는 모든 객체를 Mock으로 대체합니다.
→ Mock을 통해 객체 간의 협력을 명시적으로 테스트합니다.

 

 

테스트 대역 사용 대상: 런던파 vs 고전파

테스트 대역(Test Double)은 Mock, Stub, Fake 등 실제 객체를 대신하여 테스트에 사용되는 대체 객체를 말합니다. 런던파와 고전파는 테스트 대역을 사용하는 대상이 서로 다릅니다. 이를 알기 쉽게 설명하기 위해 불변 의존성공유 의존성의 개념을 먼저 정의하고, 두 접근 방식의 차이를 예시와 함께 살펴보겠습니다.


불변 의존성 (Immutable Dependency)

  • 코드 실행 중에 상태가 변하지 않는 의존성입니다.
  • 예를 들어, 수학 계산 클래스상수 값을 제공하는 서비스처럼 입력값에 따라 항상 동일한 결과를 반환하는 객체를 말합니다.
  • 예시:
    class Calculator:
        def add(self, a, b):
            return a + b
    

공유 의존성 (Shared Dependency)

  • 여러 단위(객체)에서 공유되며, 상태가 변할 수 있는 의존성입니다.
  • 예를 들어, 데이터베이스, 캐시, 또는 네트워크 통신 객체처럼 상태가 변경될 가능성이 있는 객체를 말합니다.
  • 예시:
    class Database:
        def save(self, data):
            # 데이터 저장 로직
            pass
    
        def fetch(self, query):
            # 데이터 조회 로직
            return {"result": "some_data"}
    

런던파의 접근: 불변 의존성 외 모든 의존성에 테스트 대역 사용

  • 런던파는 테스트 중인 단위(클래스, 함수)를 완전히 격리하기 위해 불변 의존성을 제외한 모든 의존성을 테스트 대역(Mock, Stub 등)으로 대체합니다.
  • 이는 단위 간의 상호작용을 명확히 검증하고, 테스트 중 의존성의 상태 변화가 테스트 결과에 영향을 주지 않도록 하기 위함입니다.
  • 불변 의존성(Calculator): 실제 객체 사용.
  • 공유 의존성(Database): Mock으로 대체하여 상태 변화나 부작용을 테스트 대역으로 격리.

고전파의 접근: 공유 의존성에만 테스트 대역 사용

  • 고전파는 테스트에서 협력 객체를 실제로 사용하되, 상태를 공유하거나 외부 시스템에 영향을 미칠 가능성이 있는 공유 의존성만 테스트 대역(Mock, Stub 등)으로 대체합니다.
  • 이는 실제 객체의 동작을 테스트하며, 필요할 때만 테스트 대역을 사용해 외부 부작용을 차단하려는 목적입니다.
  • 불변 의존성(Calculator): 실제 객체 사용.
  • 공유 의존성(Database): 런던파와 동일하게 Mock으로 대체.

 

차이점 요약

테스트 대역 사용 대상 불변 의존성을 제외한 모든 의존성 상태를 공유하거나 외부 시스템에 영향을 주는 공유 의존성만
목적 단위의 완전한 격리와 상호작용 검증 실제 객체를 활용한 자연스러운 테스트
의존성 관리 방식 내부 협력 객체도 Mock으로 대체 가능 내부 협력 객체는 실제 객체 사용

 

결론

  • 런던파: 불변 의존성 외 모든 의존성을 테스트 대역으로 대체해 단위 간 상호작용을 검증하며, 객체 간 협력에 집중합니다.
  • 고전파: 공유 의존성만 테스트 대역으로 대체하며, 객체의 실제 동작을 테스트하여 전체적인 동작 검증에 더 중점을 둡니다.

 

2.4 고전파와 런던파의 비교

두 주요 차이는 단위 테스트의 정의에서 격리 문제를 어떻게 다루는지에 있다. 이는 결국 테스트해야 할 단위의 처리와 의존성 취급에 대한 방법으로 넘어간다. 목을 사용하는 테스트는 고전적인 테스트보다 불안정한 경향이 있다.

 

테스트는 코드의 단위를 검증해서는 안 된다. 동작의 단위, 즉 문제 영역에 의미가 있는 것, 비즈니스 담당자가 유용하다고 인식할 수 있는 것을 검증해야 한다. 테스트가 단일 동작 단위를 검증하는 한 좋은 테스트다. 이보다 적은 것을 목표로 삼는다면 단위 테스트를 훼손하는 결과를 가져온다. 

 

런던 스타일 테스트가 있는 시스템에 버그가 생기면, 보통 SUT에 버그가 포함된 테스트만 실패한다.

고전적인 방식이면, 하나의 버그가 전체 시스템에 걸쳐 테스트 실패를 야기하는 파급 효과를 초래한다.

 

 

2.5 고전파와 런던파 사이의 다른 차이점

아직 두 가지 차이점이 더 있다.

- 테스트 주도 개발을 통한 시스템 설계 방식

- 과도한 명세 문제

 

런던 스타일의 단위 테스트는 하향식 TDD로 이어지며, 목을 사용해 예상 결과를 달성하고자 시스템에서 통신해야 하는 협력자를 지정한다. 테스트할 때 SUT의 모든 협력자를 차단해 해당 협력자의 구현을 나중으로 미룰 수 있다.

 

고전파는 실제 객체를 다뤄야 하기 때문에 일반적으로 상향식으로 한다. 도메인 모델을 시작으로 최종 사용자가 소프트웨어를 사용할 수 있을 때까지 계층을 그 위에 더 둔다.

 

하지만 가장 중요한 차이점은 과도한 명세 문제, 테스트가 SUT의 구현 세부 사항에 결합되는 것이다. 런던 스타일은 고전 스타일보다 테스트가 구현에 더 자주 결합되는 편이다.

 

통합 테스트의 정의에도 차이가 있다. 런던파실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다. 고전파 스타일로 작성된 대부분의 테스트는 런던파 지지자들에게 통합 테스트로 느껴질 것이다.

 

둘 이상의 동작 단위를 검증할 때 테스트통합 테스트다. 테스트 스위트의 실행 속도를 최적화하려는 노력의 결과이고, 다른 동작 단위를 검증하는 느린 테스트가 두 개 있을 때, 하나로 합치는 것이 타당할 수 있다. 또한, 다른 팀이 개발한 모듈이 둘 이상 있을 때 어떻게 작동하는지 검증할 수 있다. 이는 또한 여러 동작 단위를 한 번에 검증하는 제3의 테스트 유형에 해당한다.

 

간단히 말해 통합 테스트는 공유 의존성, 프로세스 외부 의존성뿐 아니라 조직 내 다른 팀이 개발한 코드 등과 통합돼 작동하는지도 검증하는 테스트다. 엔드 투 엔드 테스트는 통합 테스트의 일부다. 이 또한 코드가 프로세스 외부 종속성과 함께 어떻게 작동하는지 검증한다. 엔드 투 엔드와 통합 테스트의 차이는 엔드 투 엔드 테스트가 일반적으로 의존성을 더 많이 포함한다.

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

Part1 더 큰 그림 [하]  (0) 2024.12.17