반응형

출처: 웹문서 What are Testing Patterns? by Anushree Chatterjee, 2025년
https://testrigor.com/blog/what-are-testing-patterns/

 

테스팅 패턴

테스팅 패턴은 깔끔하고 효과적인 테스트 케이스를 작성하는 데 도움이 되는 지침으로, 테스트 자동화 프레임워크에 꼭 필요한 구조(structure)를 제공하기 때문에 매우 유용하다.

 

테스팅 패턴은 반복되는 테스팅 문제에 대한 재사용 가능한 솔루션이다. 꼭 따라야 하는 엄격한 규칙이 아니라, 테스트를 구성하고, 테스트 데이터를 관리하고, 테스트 대상 시스템과 인터액션하는 데 도움이 되는 검증된 청사진(blueprints)이나 지침(guidelines)과 같다.

 

테스팅 패턴은 일반적으로 특정 문제를 해결한다. 테스트 데이터 설정에 끊임없이 어려움을 겪거나, 사소한 디자인 변경으로 UI 테스트가 항상 제대로 작동하지 않거나, 개별 단위 테스트가 엉망일 수 있다. 테스팅 패턴은 이러한 문제를 해결하기 위한 신중하고 일반화된 접근 방식을 제공한다. 테스팅 패턴은 무엇을 테스트해야 하는지보다(이것도 테스터의 중요 임무이지만!)는 효과적이고 효율적으로 테스트하는 방법에 더 중점을 둔다.

 

다음과 같은 여러 방법이 있다.

  • Arrange-Act-Assert(AAA)
  • Page Object Model(POM)
  • Test Data Builder/Factory
  • Given-When-Then
  • Singleton
  • Facade
  • Mocking
  • Stubbing
  • Parametrization

 

 

자동화 테스팅의 디자인 패턴

Arrange-Act-Assert(AAA)

특히 단위 테스트에서 가장 기본적이고 널리 사용되는 테스팅 패턴이라고 할 수 있다. 아주 간단하지만 테스트를 명확하고 체계적으로 만드는 데 매우 효과적이다.

 

AAA는 모든 테스트 케이스를 세 가지 단계로 구분한다.

  • Arrange: 테스트에 필요한 모든 것을 셋업한다(예: 객체 생성, 변수 초기화, 디펜던시 모킹).
  • Act: 실제로 테스트할 동작을 수행한다(예: 함수 호출, 이벤트 트리거).
  • Assert: 동작이 예상한 결과를 생성했는지 확인한다.

 

이는 명확성과 가독성을 높여주는 장점이 있다. 테스트를 보는 사람이라면 누구나 무엇을 설정하고, 무엇을 수행하며, 무엇을 검사하는지 즉시 이해할 수 있다.

 

예시: 두 숫자를 더하는 함수를 테스트한다고 상상해 보라.

 

 

Page Object Model (POM)

이 패턴은 UI 자동화 테스트에 있어 중요한 역할을 한다. 취약하고 유지 관리가 어려운 UI 테스트 문제를 해결한다.

 

POM UI 엘리먼트와 해당 엘리먼트에서 수행할 수 있는 액션을 실제 테스트 로직과 분리한다. 애플리케이션의 각 "페이지" 또는 개별 컴포넌트는 상응하는 전용 "Page Object" 클래스를 갖는다. 이 클래스는 인터액션을 나타내는 메서드(: login(), clickAddToCart())와 엘리먼트를 나타내는 속성(: usernameField, loginButton)을 포함한다.

 

웹사이트에서 어떤 버튼의 ID가 변경되면 잠재적인 수백 개의 테스트 스크립트 대신 한 곳(Page Object)에서만 업데이트하면 된다는 장점을 준다. 이를 통해 UI 테스트가 훨씬 더 견고해지고 유지 관리가 쉬워진다.

 

예시: 로그인 페이지를 테스트하는 경우 LoginPage 클래스를 생성한다.

 

 

Test Data Builder/Factory

현실적이고 다양한 테스트 데이터를 생성하는 것이 골치 아픈 일일 수 있는데(특히 복잡한 객체의 경우), 이 패턴은 세련된 해결책을 제공한다.

 

모든 테스트에서 복잡한 데이터 객체를 수동으로 생성하는 대신, 전용 "빌더" 또는 "팩토리"를 사용하여 생성한다. 이러한 빌더는 종종 유창한 인터페이스를 제공하는데, 예를 들면 new UserBuilder().WithName("Alice").WithEmail("alice@example.com").Build();와 같은 것이다. 이를 통해 특정 테스트에서 필요한 필드만 사용자가 쉽게 정의하고 나머지 모든 필드에는 합리적인 기본값(defaults)을 제공할 수 있다.

 

이는 반복 작업을 줄이고, 테스트 데이터 생성을 읽기 쉽게 만들며, 잊혀진 누락 필드로 인한 에러를 방지한다.

 

예시: 이름, 가격, 재고, 설명 등 여러 속성을 가진 Product 객체를 생성하는 상황을 상상해 보라.

 

 

Given-When-Then (GWT)

이 패턴은 "어떻게 테스트해야 하는지"에서 "시스템이 어떤 동작을 보여야 하는지"로 초점을 옮겨, 기술적인 지식이 없는 이해 관계자도 테스트를 더 쉽게 이해할 수 있도록 한다.

 

GWT는 세 가지 핵심 문구를 사용하여 (종종 평범한 언어로 작성된) 테스트 시나리오를 구성한다.

  • Given: 초기 상황 또는 전제 조건
  • When: 사용자 또는 시스템이 수행하는 액션
  • Then: 예상 결과

 

이것의 장점은 비즈니스 요구사항과 코드 간의 간극을 메운다는 점이다. 테스트는 단순한 기술적 세부 사항이 아닌, 동작을 설명하는 실행 가능한 명세로 변모하여 협업에 매우 효과적이다. 참고: Behavior-Driven Development (BDD)

  

예시: 쇼핑 카트에 상품을 추가하는 것을 테스트한다.

 

 

Singleton (테스트 셋업용)

엄격히 말해 이 패턴은 테스트 자체를 위한 것은 아니지만, 공유 리소스를 효율적으로 관리하기 위해 테스트 셋업 내에서 자주 사용된다.

 

이 디자인 패턴은 클래스가 인스턴스를 오직 하나만 가지도록 하고 해당 인스턴스에 대한 전역 액세스 지점을 제공한다. 테스팅에서는 생성 비용이 많이 들고 여러 테스트에서 재사용할 수 있는 리소스를 위해 유용하다.

 

장점은 반복된 셋업 과정을 피할 수 있다는 것이다. 예를 들어 데이터베이스 연결이나 웹 브라우저 인스턴스를 초기화하는 데 시간이 오래 걸리는 경우, 한 번 생성하여 여러 테스트에서 재사용할 수 있다.

 

예시: UI 테스트를 위한 웹 브라우저 드라이버의 단일 인스턴스 관리하기

 

 

Facade (복잡한 시스템 테스트용)

테스트 중인 시스템이 상호 연결된 부분이 많아 매우 복잡할 때 Facade를 사용하면 테스트 작업을 간소화할 수 있다.

 

Facade 패턴은 더 크고 복잡한 서브시스템에 대한 간소화되고 통합된 인터페이스를 제공한다. 테스텡에서는 여러 내부 서비스 또는 API를 래핑하여 테스트가 테스트 중인 시스템과 인터액션하는 데 필요한 메서드만 노출하는 "Test Facade"를 만들 수 있다.

 

장점은 테스트 복잡성이 감소되는데 있다. 시스템 내부 작동 방식의 모든 복잡한 세부 사항을 알 필요 없이, 우리의 테스트는 간소화된 Facade와만 인터액션한다. 따라서 테스트가 더욱 깔끔하고 내부 리팩터링에 덜 취약하다.

 

예시: 재고, 결제 및 배송 서비스를 포함하는 전자상거래 주문 처리 시스템 테스트하기

 

 

Mocking

코드의 일부를 테스트할 때 다른 부분(: 데이터베이스, 외부 API, 다른 서비스)에 의존해야 하는 경우가 많다. 모킹은 이러한 의존성(디펜던시)의 대체/모의 버전인 "mock"을 만들어 그 동작을 제어하고 코드가 그것과 예상대로 인터액션하는지 확인하는 것이다.

 

mock은 실제 디펜던시의 동작을 모방하는 시뮬레이션된 객체이다. 이것을 특정 방식으로 응답하도록 구성한 다음, 코드 실행 후 mock을 검사하여 특정 메서드가 호출되었는지, 몇 번 호출되었는지, 어떤 인수(arguments)로 호출되었는지 등을 확인할 수 있다. 모킹은 인터액션을 검증하는 것이 관심사이다.

 

이것의 장점은 실제 데이터베이스, 네트워크 연결 또는 기타 서비스를 사용할 수 없거나 특정 상태에 있지 않아도 코드를 완전히 독립된 상태에서 테스트할 수 있다는 것이다. 이를 통해 테스트가 훨씬 빠르고 안정적이며(더 이상 느린 API를 기다릴 필요가 없다!) 버그가 발생할 수 있는 정확한 위치를 파악하는 데 도움이 된다.

 

: 사용자를 데이터베이스에 저장하고 환영 이메일을 보내는 UserService가 있다고 가정하자. UserService.SaveUser()를 테스트할 때 실제 이메일을 보내고 싶지는 않을 것이다.

 

 

Stubbing

스터빙은 종종 모킹과 혼동되는데, 둘 다 가짜 디펜던시(fake dependencies) 생성과 관련 있기 때문이다. 핵심적인 차이는 그들의 주요 목적에 있다: 모킹은 인터액션을 검증(verifying interactions)하는 데 목적이 있는 반면, 스터빙은 코드가 디펜던시로부터 받는 입력(controlling the input)을 제어하는 데 목적이 있다.

 

stub은 메서드 호출에 대해 "미리 만들어진" 응답을 제공하는 가짜 객체이다. 이는 디펜던시에게 "이 호출을 받으면 이 특정 값을 반환하라"고 지시하는 것이다. 핵심은 코드가 필요로하는 데이터를 제공하여 실행이 가능하게 만드는 데 있으며, 반드시 코드가 stub과 어떻게 인터액션했는지 확인하는 데 있지 않다.

 

이는 코드를 예측할 수 없는 외부 요인으로부터 격리한다. 다양한 시나리오(: 데이터베이스에서 데이터를 반환하지 않거나 API에서 에러를 반환하는 경우 어떻게 되는가)를 실제로 해당 조건이 발생하지 않고도 시뮬레이션할 수 있다.

 

예시: 데이터베이스와 통신하는 ProductRepository에서 제품 정보를 가져오는 ProductService가 있다고 가정하자. ProductService.GetProductDetails()를 테스트할 때 실제 데이터베이스를 사용하고 싶지 않을 수 있다.

 

 

참고: Mockito, Jest와 같은 많은 현대적인 테스트 프레임워크와 라이브러리가 동일한 신택스로 모킹과 스터빙을 모두 수행할 수 있도록 하여, 실제로는 그 경계를 모호하게 한다. 하지만 개념적 차이를 이해하면 더 나은, 더 집중된 테스트를 작성하는 데 도움이 된다.

 

 

Parameterization

매개변수화는 테스트 프레임워크 내에서 데이터 주도 테스트(data-driven testing)를 수행하는 방법으로 자주 사용된다. 중복 코드 작성 없이 여러 다른 입력 데이터 세트를 사용하여 동일한 테스트 로직을 여러 번 효율적으로 실행하는 것이다.

 

각 데이터 조합에 대해 별도의 테스트 메서드를 작성하는 대신, 하나의 테스트 메서드를 작성하고 여러 다른 매개변수를 수용하도록 구성한다. 그러면 테스터가 제공하는 모든 매개변수 세트에 대해 테스트 프레임워크가 해당 단일 테스트 메서드를 자동으로 실행한다.

 

이는 코드 중복을 크게 줄이고, 테스트 스위트를 간결하게 만들며, 최소한의 노력으로 테스트 커버리지를 획기적으로 향상시키는 장점이 있다. 새로운 테스트 케이스를 추가해야 하는 경우, 완전히 새로운 테스트 메서드를 작성할 필요 없이 데이터 행만 추가하면 된다.

 

예시: 숫자가 짝수인지 확인하는 함수를 테스트한다고 가정하자. 여러 개의 짝수와 여러 개의 홀수를 테스트해 보고자 한다. 테스트 러너는 TestIsEven을 데이터의 각 쌍(숫자, 예상 결과) 마다 한 번씩 총 5번 실행한다.

 

 

 

반응형

+ Recent posts