반응형

출처: 2022, Full Stack Testing - A Practical Guide for Delivering High Quality Software, Gayathri Mohan, Chapter 3. Automated Functional Testing

 

웹 애플리케이션의 모든 애플리케이션 계층에 걸쳐 효과적인 자동 기능 테스트를 수행하는 것에 대해 다룬다.

 


 

 

자동화된 테스팅에 대한 간략한 히스토리

자동화된 테스트는 사람 대신 도구를 사용하여 애플리케이션에서 사용자와 유사한 액션을 수행하고 그에 따른 예상 동작을 확인하는 방식이다. 이것이 1970년대부터 이어져 왔으며 이 분야의 기술과 도구는 소프트웨어와 함께 지속적으로 발전해 왔다. 몇 가지 예를 들자면, 1970년대 소프트웨어 애플리케이션은 주로 FORTRAN으로 작성되었으며 RXVP 도구가 자동화된 테스트를 수행하는 데 사용되었다. 1980년대 PC가 발전하면서 자동화된 테스트를 위해 AutoTester가 도입되었다. 1990년대 월드 와이드 웹(World Wide Web)이 붐을 이루면서 Mercury Interactive QuickTest와 같은 테스트 자동화 도구가 인기를 얻었고 자동화된 부하(load) 테스트 도구인 Apache JMeter가 발명되었다. 웹의 지속적인 발전과 함께 2000년대에는 Selenium이 탄생했으며, 이후 자동화된 테스트 도구의 수가 계속해서 증가하고 있다. 현재는 전반적인 테스트 자동화 경험을 풍부하게 해주는 AI/ML 기반 자동화 테스트 도구도 등장하였다.

 

 

미시적 테스트와 거시적 테스트

아래 그림과 같은 가상의 전자상거래 애플리케이션을 예로 설명한다. 이 애플리케이션의 가장 일반적인 사용자 흐름은 사용자가 전자상거래 UI에 자격 증명(로그인 정보)을 입력하고, 이 자격 증명이 확인을 위해 인증 서비스(Auth service)로 전송되며, 로그인에 성공하면 사용자가 전자상거래 UI를 통해 제품을 검색하고 주문을 한다. 주문 서비스(Order service)는 사용자의 주문을 받고, 외부 공급업체 PIM 서비스를 기준으로 제품 정보를 검증하고, 이를 창고 관리 시스템에 전달하여 배송 프로세스를 시작한다.

그림 3-1. 전자상거래 애플리케이션의 각 계층에서 미시적 및 거시적 테스트

 

단위 테스트(Unit tests)

이 테스트는 미시적 수준에서 안전망을 만드는 것을 목표로 한다. 애플리케이션 기능의 가장 작은 부분을 검증한다. 예를 들어 클래스의 메서드 동작(behavior)을 확인하는 단위 테스트를 작성할 수 있다. 전자상거래 애플리케이션의 주문 서비스에 총 주문 금액을 반환하는 return_order_total(item_prices)라는 메서드가 있다고 가정해 보자. 다음은 해당 동작을 확인하기 위해 추가할 수 있는 몇 가지 단위 테스트이다.

 

  • 할인으로 인해 item_prices가 음수 값을 가질 때의 총 금액을 반환
  • item_prices가 비어 있을 때의 총 금액을 반환
  • item_prices에 유효하지 않은 값(예: 문자, 기호 등을 포함하는 값)이 포함된 경우의 총 금액을 반환
  • 애플리케이션이 현지화를 지원하는 경우 item_prices가 다른 통화 기호 및 구분 기호와 함께 전송될 때의 총 금액을 반환
  • 고정된 소수 값(fixed decimal values)을 사용하여 적절하게 반올림된 총 금액을 반환

 

JUnit, TestNG NUnit은 백엔드에서 흔히 채택되는 단위 테스팅 프레임워크이다. Jest, Mocha Jasmine은 널리 사용되는 프런트엔드 단위 테스팅 프레임워크이다.

 

 

통합 테스트(Integration tests)

대부분의 중대형 웹 애플리케이션에는 내부 또는 외부 컴포넌트와의 통합 지점이 꽤 많이 있다(: 네트워크 및 인프라 경계를 가로질러 분산된 서비스, UI, 데이터베이스, 캐시, 파일 시스템 등). 이러한 모든 통합 지점이 예상대로 작동하는지 테스트하려면 실제 통합 시스템에 대해 실행되는 통합 테스트를 작성해야 한다. 이러한 테스트의 초점은 상세한 엔드투엔드 기능이 아니라 긍정 또는 부정적인 통합 흐름을 확인하는 것이어야 한다. 따라서 단위 테스트만큼 작게 만드는 것이 이상적이다.

 

전자상거래 애플리케이션 예에서 주문 서비스(Order service)는 전자상거래 UI, 데이터베이스, 기타 서비스 등의 내부 컴포넌트와 통합되어 정보를 교환한다. 또한 외부 공급업체 PIM 서비스 및 다운스트림 시스템과도 통합된다. 이 서비스가 다른 종속 서비스 및 DB와 제대로 통신할 수 있는지 검증하는 통합 테스트를 작성할 필요가 있고, 특히 해당 외부 시스템 및 서비스와의 통합을 확인하기 위해 주문 서비스 통합 테스트를 추가해야 하는지 확인해야 한다.

 

통합 테스트는 통합을 시뮬레이션하기 위한 특정 도구와 함께 이전 섹션에서 언급한 단위 테스트 프레임워크를 사용하여 작성할 수 있다. 예를 들어 JUnitSpring Data JPA와 함께 사용하여 DB 통합 테스트를 작성할 수 있다. 이러한 테스트의 신속성은 외부 시스템이 응답하는 데 걸리는 시간에 따라 달라진다. 따라서 완전히 분리되어 실행되는 단위 테스트보다 속도가 느릴 수 있다.

 

 

계약 테스트(Contract tests)

통합되는 서비스가 개발 중인 경우 통합 테스트가 실행 가능하지 않을 수 있다. 여러 팀이 서로 다른 서비스에서 독립적으로 작업하는 대규모 애플리케이션 개발에서 이런 경우가 흔하다. 이러한 프로젝트에서 팀은 모든 서비스에 대한 표준 계약(standard contract)에 동의하고, 종속 서비스가 준비될 때까지 종속 서비스의 스텁(stubs)을 사용하여 작업한다. 하지만 스텁을 사용할 경우 주의사항이 있는데 실제 통합 서비스의 계약이 변경되었는지 알 수 없다는 점이다! 이런 일이 발생하면 개발자는 깨진 계약 위에 새로운 기능을 계속 구축하게 되고, 개발 주기가 끝날 때쯤 실제 서비스와의 실제 통합 테스트 중에 뒤늦게 이를 파악하게 된다. 이것이 계약 테스트를 수행하는 주요 이유 중 하나이다.

 

계약 테스트는 통합 서비스의 실제 계약과 비교하여 스텁을 검증하고 개발이 진행되는 동안 양쪽 팀 모두에게 지속적으로 피드백을 제공하기 위해 작성된다. 계약 테스트는 통합 서비스에서 반환된 정확한 데이터를 반드시 확인하는 것이 아니라 계약 구조 자체에 중점을 둔다. 예로 든 전자상거래 애플리케이션에서는 외부 공급업체 PIM 서비스의 계약을 검증하기 위해 계약 테스트를 추가하여 계약이 변경될 때마다 그에 따라 주문 서비스 기능을 변경할 수 있다. 개발이 동시에 진행되는 경우 전자상거래 UI와 서비스 간의 통합을 위한 계약 테스트를 작성할 수도 있다. 계약 테스트의 엔드투엔드 워크플로에는 팀 간의 협업이 포함되며, Postman Pact와 같은 도구를 사용하면 이 워크플로를 자동화할 수 있다. 일반적으로 계약 테스트는 범위가 작기 때문에 매우 빠르게 실행된다(간단히 계약 구조만 확인함). 추가적인 복잡성은 팀 간의 협업이 필요한 엔드투엔드 설정 때문에 생긴다.

 

 

서비스 테스트(Service tests)

2장에서 설명한 것처럼 API는 제품 자체로 취급되어야 하며 UI 동작과 관계없이 철저하게 테스트되어야 한다. 이것이 서비스 테스트의 초점이다.

 

서비스는 기본적으로 비즈니스 규칙, 에러 기준, 재시도 메커니즘, 데이터 저장 등과 같은 모든 도메인에 특정한 로직을 처리한다. 서비스는 요청(requests)의 구조와 값 형식을 확인한 후 유효하지 않은 요청은 거부한다. 서비스 테스트는 통합, 도메인 워크플로우 등을 커버하기 때문에 거시적 수준의 테스트가 시작되는 곳이다. 예를 들어 주문 서비스를 위해 전자상거래 애플리케이션에 추가할 수 있는 몇 가지 서비스 테스트가 다음과 같다.

 

  • 인증된 사용자만 새 주문을 생성할 수 있는지 확인
  • 주문 생성 시점에 품목이 가용한 경우에만 주문이 생성되는지 확인
  • 긍정적 및 부정적 입력에 대해 올바른 HTTP 상태 코드가 반환되는지 확인

 

DB에 실제 테스트 데이터 설정을 해야 하므로 단위 테스트보다 생성 및 유지 관리가 약간 더 복잡하다. UI 기반 엔드투엔드 테스트보다 빠르게 실행되고 이전 세 가지 마이크로 수준 테스트(단위, 통합 및 계약 테스트)보다 약간 느리다. REST Assured, Karate, Postman과 같은 도구를 사용하여 API 테스트를 자동화할 수 있다.

 

 

UI 기능 테스트(UI functional tests)

UI 기반 기능 테스트는 실제 브라우저 상에서 실행되며 애플리케이션의 사용자 액션을 모방한다. 이 테스트는 서비스, UI, DB 등 여러 컴포넌트 간의 통합에 대한 피드백을 제공한다. 이러한 매크로 수준 테스트는 모든 중요한 사용자 흐름을 검증하는 데 중점을 두어야 한다. 전자상거래 애플리케이션에서 중요한 사용자 흐름의 한 가지 예는 제품을 검색하고, 장바구니에 제품을 추가하고, 제품 비용을 지불하고, 주문 확인을 받는 것이다. 이는 UI 기능 테스트로 추가될 수 있다. 이러한 테스트를 작성할 때는 하위 수준 마이크로 테스트에서 커버된 동일한 세부 사항을 다시 검증하는 것을 피한다. 이는 중복되고 실행 시간만 늘리기 때문이다. 예를 들어 다양한 품목 가격 조합에 대한 주문 총액을 확인하는 것은 단위 테스트에서 다루어야 하며 UI 기능 테스트의 일부로 다시 검증할 필요가 없다.

 

이러한 테스트는 인프라, 네트워크 등을 포함한 전체 애플리케이션 스택의 동작이 안정적인지에 의존하므로 실행하는 데 시간이 더 오래 걸리고 불안정한 경향이 있다. 또한 애플리케이션 전체 어디에서든 실패(, 엘리먼트 ID가 변경됨, 페이지 로드가 지연됨, 환경 문제로 인한 서비스 사용 불가 등)가 발생할 수 있으므로 다른 유형의 테스트에 비해 상당한 유지 관리 노력이 필요하다. Selenium Cypress와 같은 도구는 자동화된 UI 테스트를 작성하는 데 널리 사용된다.

  

팁!!
UI 기능 테스트를 추가할 생각을 할 때마다 먼저 테스트의 의도를 묻고(예: 입력 검증, 서비스 수준 비즈니스 규칙 검증 등) 동일한 목표를 달성하기 위해 하위 수준의 마이크로 테스트를 작성할 수 있는지 확인한다.

  

 

엔드투엔드 테스트(End-to-end tests)

이름에서 알 수 있듯이 엔드투엔드 테스트는 다운스트림 시스템을 포함하여 도메인 워크플로의 전체 범위를 검증해야 한다. 전자상거래 애플리케이션에서는 웹사이트에서 주문이 접수된 후 다운스트림 시스템(: 창고 관리 시스템, 3자 배송 파트너 서비스 등)이 실제로 주문을 이행한다. 이러한 엔드투엔드 도메인 흐름이 적절한 통합을 위해 테스트되어야 한다.

 

애플리케이션 컨텍스트에 따라 UI 기능 테스트는 종종 엔드투엔드 테스트가 되는 경향이 있다. 그렇지 않은 경우 전체 통합 흐름을 커버하도록 UI, 서비스 및 DB 테스트 도구의 조합을 사용하여 별도의 엔드 투 엔드 테스트를 만든다. 당연히 이러한 테스트는 실행하는 데 가장 오랜 시간이 걸리고, 다양한 시스템에 걸쳐 안정적인 환경과 테스트 데이터 설정이 필요하기 때문에 유지 관리에 더 많은 주의가 필요하다. 이러한 테스트의 목적은 모든 컴포넌트가 처음부터 끝까지 적절하게 통합되었는지 확인하는 것이지 컴포넌트의 기능을 테스트하는 것은 아니다. 따라서 모든 컴포넌트를 활성화하는 몇 가지 테스트만 수행하면 된다.

 

 

 

자동화된 기능 테스팅 전략: 테스트 피라미드

테스트 피라미드는 광범위한 미시적 수준 테스트 버킷을 보유하고 범위가 증가함에 따라 거시적 수준 테스트의 수를 점진적으로 줄이는 것을 권장한다. 예를 들어 단위 및 통합 테스트가 10x개라면 서비스 테스트는 5x, UI 기반 테스트는 x개만 작성한다. 맨 아래에 단위 테스트를 두고 테스트를 다른 테스트 위에 겹겹이 쌓으면 피라미드가 형성된다. 이렇게 권장하는 이유는 테스트 범위가 증가함에 따라 실행하는 데 더 많은 시간이 걸리고 작성 및 유지 관리에 더 많은 비용이 들기 때문이다.

 

그림 3-2. 서비스 지향 웹 애플리케이션(예: 전자상거래)을 위한 테스트 피라미드

 

 

반응형

+ Recent posts