출처: 2022년, Full Stack Testing - A Practical Guide for Delivering High Quality Software by Gayathri Mohan, 5장 Data Testing
데이터가 어떻게 저장(stored), 처리(processed), 표시되는지(presented) 테스트하는 것은 애플리케이션의 성공을 보장하는 데 매우 중요하다. 이 장에서는 애플리케이션이 데이터를 저장하고 처리하는 네 가지 방식(데이터베이스, 캐시, 이벤트 스트리밍 및 배치 프로세싱)을 소개하고 각각의 주요 테스트 케이스를 논한다.
데이터 테스팅 vs. 기능 테스팅
데이터 테스팅이 애플리케이션 기능 테스팅의 일부로 커버가 된다고 주장할 수도 있는데 이는 부분적으로 사실이다. 그러나 동일한 기능을 데이터 흐름을 따라 생각해 보면 새로운 테스트 케이스를 발견하게 될 것이고 이 장에서는 여기에 중점을 둔다. 또한 UI와 API를 통한 기능 테스트만으로는 항상 충분하지 않을 수 있다. 기능이 완전한지 확인하기 위해 저장 및 프로세싱 시스템에서 데이터 무결성(integrity)을 별도로 테스트해야 할 수도 있다. 그리고 이를 위해서는 특정 도구와 방법을 배울 필요가 있다. 또한 이 장에서 알게 되겠지만, 저장 및 프로세싱 시스템의 특성으로 인한 새로운 테스트 케이스가 등장하기 때문에 해당 데이터 시스템에 대한 전문 지식이 필요하다. 데이터 테스팅 기술은 이런 모든 것을 커버한다. 즉, 기능을 완전하게 테스트하려면 데이터 테스팅 기술도 필요하다.
데이터베이스(Databases)
데이터베이스는 소개가 필요 없는 거의 모든 애플리케이션에서 사용되는 컴포넌트로 비교적 잘 확립된 데이터 저장 시스템이다. 이것이 널리 채택되는 이유 중 하나는 데이터가 하드 디스크에 저장되고 하드웨어 실패의 경우에만 손실이 발생하는 강력한 데이터 내구성(durability)을 제공하기 때문이다.
데이터베이스에 대한 몇몇 기본적인 테스트 케이스가 다음과 같다.
- UI를 통해 사용자로부터 수집된 정보가 DB에 저장되고 적절하게 연관되는지 확인한다(포지티브 테스트 케이스).
- 컬럼 데이터 타입 및 입력 길이를 기반으로 경계 값을 테스트한다. 예를 들어 DB에서 고객 이름 필드를 20자 미만으로 제한하는 경우 UI에도 동일한 제한이 적용되어야 하며, 길이를 초과하면 사용자에게 적절한 에러 메시지가 표시되어야 한다.
- SQL 신택스를 포함하는 입력으로 테스트한다. 예를 들어 이름에 아포스트로피가 포함된 Bob D'arcy라는 이름이 DB에 제대로 저장될 수 있는가? 아니면 코드에 이를 정리하는 로직이 필요한가?
- 갑작스러운 네트워크 장애가 발생하면 진행 중인 쓰기 오퍼레이션(write operation)은 어떻게 되는가? 데이터가 일부 테이블에는 부분적으로 기록되지만 모든 관련 테이블에 기록되지 못하는 것은 아닌가? 이 시나리오는 오퍼레이션이 여러 서비스에 걸쳐 분할되어 있을 때 더욱 증폭된다.
- 이러한 경우에 재시도 오퍼레이션(retry operation)은 어떤 영향을 미치는가?
- 애플리케이션이 데이터베이스 오퍼레이션을 재시도하는 제한 시간(timeout period)은 얼마나 되며, 이 때의 사용자 경험은 어떠한가?
동시성(concurrency)도 포함한다면(즉, 여러 사용자와 시스템이 동시에 읽기 및 쓰기를 위해 데이터베이스에 액세스하는 경우) 몇 가지 테스트 케이스를 더 생각해야 하는데, 특히 경합 조건(race conditions)과 관련된 고려가 필요하다.
- 한 사용자의 액션이 다른 사용자의 액션과 충돌하여 업데이트가 손실될 수 있다. 예를 들어, 두 명의 사용자가 동일한 아이템을 동시에 구매하는 경우, 아이템 수량이 두 개가 아닌 한 개만 감소할 수 있다.
- 마찬가지로, 애플리케이션이 부분적인(불완전한) 업데이트를 읽는 경우 사용자가 일치하지 않는 데이터를 볼 수 있다. 예를 들어, 현재 가용하지 않은 일부 품목의 재고가 보충되고 있으며, 이 프로세스가 먼저 한 테이블에서 품목 가용성 플래그를 true로 변경한 다음 다른 테이블에서 가용한 품목 수를 업데이트한다고 가정해 보자. 이 두 오퍼레이션 사이에서 최종 사용자는 품목이 가용하지만 수량이 0인 상황을 볼 수도 있다.
- 동시성은 공유 리소스(shared resources)에 예상치 못한 영향을 미칠 수 있다. 예를 들어, 두 명의 사용자가 착불결제옵션을 사용하여 마지막 구매 가능한 품목을 동시에 구매하는 경우 한 사용자에게는 해당 품목이 할당되고 다른 사용자에게는 송장이 생성될 수 있다.
- 동시성이 데이터베이스 성능에 제한을 가할 수 있다. 따라서 예상되는 실시간 데이터 볼륨을 사용한 성능 테스트는 필수적인 테스트 케이스이다.
여기서 강조할 점은 동시성 관련 테스트 케이스는 시뮬레이션하기 어렵고 해당 내용이 주로 분석 단계에서 유용하므로 개발 중에 선제적으로 다루어질 수 있다는 것이다.
데이터베이스는 단일 인스턴스에 대한 동시 액세스 외에도 복제(replication)를 통해 확장성을 제공한다. 복제는 동일한 데이터의 중복 인스턴스를 생성하는 것을 의미한다. 인스턴스는 서로 다른 위치(예: 미국 동부 및 서부 해안, 북미 및 유럽)에 있는 사용자의 성능을 향상시키기 위해 대개 지리적으로 분리되어 유지된다. 이러한 경우 모든 복제본을 최신 업데이트와 동기화된 상태로 유지하는 메커니즘이 필요하다. 일반적으로 복제본 중 하나에 업데이트를 다른 복제본(팔로어)으로 보내는 리더 역할을 할당함으로써 이를 수행한다. 이러한 상황에서 팔로어가 업데이트를 받고 리더와 동일한 상태에 도달하기까지 시간이 걸리는 복제 지연(replication lag)이 생긴다. 이 지연은 네트워크 지연 시간, 해당 인스턴스에 대한 트래픽 등에 따라 몇 초에서 몇 분 정도 발생할 수 있다. 일정 기간이 지난 후 일관된 상태에 도달하는 이러한 모델을 최종 일관성(eventual consistency)이라고 한다. 최종 일관성 모델은 약간 오래된 게시물을 표시해도 사용자에게 큰 영향을 미치지 않는 Twitter 또는 Facebook과 같은 애플리케이션에 적합하다. 그러나 특정 애플리케이션에서는 제대로 처리되지 않으면 지연이 사용자에게 혼란을 줄 수 있으며 신뢰를 깨트릴 수 있다.
캐시(Caches)
캐시는 데이터가 키/값 쌍으로 유지되는 메모리 내 데이터 저장소이다. 데이터를 메모리에 저장하면 애플리케이션이 기존 관계형 데이터베이스와 같은 중량급 백엔드 스토리지 시스템을 호출할 필요가 없으므로 성능이 몇 배나 향상된다. Memcached 및 Redis와 같은 오늘날 널리 사용되는 캐싱 도구는 테라바이트 단위의 데이터를 저장하고 밀리초 미만의 응답을 제공할 수 있다. 그러나 내구성 측면에서는 디스크에 데이터가 확실히 기록되는 데이터베이스가 우위를 점한다.
캐시의 이러한 장단점을 고려할 때 일시적이고 애플리케이션에 자주 필요한 데이터만 캐시하는 것이 권장된다. 예를 들어 전자 상거래 애플리케이션에서 액세스 토큰은 짧은 기간(사용자가 로그인한) 동안만 유지될 것으로 예상되며 모든 서비스 요청의 진위 여부를 확인하기 위해 내부적으로 애플리케이션에서 자주 액세스해야 하므로 캐시된다. 또한 캐시 오류와 같은 상황에서 모든 사용자의 액세스 토큰이 손실되어도 영향이 미미하다. 복구하려면 현재 로그인된 사용자 집합을 로그아웃했다가 다시 로그인하면 된다(즉 사소한 불편이지 귀중한 개인 데이터나 고객 기록의 손실과 맞먹는 문제는 아님). 데이터베이스 같은 견고한 내구성이 요구되지 않기 때문에 캐시는 이 시나리오에 완벽하게 들어맞는다.
또는 자주 액세스하는 애플리케이션 데이터를 캐시와 데이터베이스 모두에 복제하는 방법도 흔히 사용된다. 이러한 경우 애플리케이션 코드는 캐시된 데이터의 생명 주기를 유지 관리하는 책임을 져야 한다. 즉, 데이터베이스와 동기화를 유지하고, 오래된 데이터를 삭제하고, 캐시 실패 시 데이터베이스로 폴백하는 등의 작업을 수행해야 한다. 이러한 시나리오는 데이터가 데이터베이스와 캐시에 복제될 때의 테스트 케이스가 될 수 있다.
캐시에 대한 몇 가지 일반적인 테스트 케이스가 다음과 같다.
- 캐시의 데이터는 만료 시기를 정한 TTL(Time to Live) 값으로 구성된다. 예를 들어 액세스 토큰은 30초 동안 유지되도록 구성될 수 있다. 30초가 지나면 인증 서비스가 새 토큰을 생성하고 이를 캐시에 다시 저장하는지 확인해야 한다.
- 캐시 오류로 인해 모든 사용자가 로그아웃했다가 다시 로그인해야 하는 경우처럼 캐시가 애플리케이션의 단일 실패 지점(a single point of failure)이 되는 경우 사용자 리디렉션 흐름을 테스트해야 한다.
- 서비스 인스턴스가 복제되면 해당 캐시도 복제되어 분산 캐시 스토리지가 된다. 이 경우 기능이 여전히 올바르게 작동하는지 확인해야 할 수도 있다(Redis Cluster와 같은 대부분의 분산 캐시 구현은 기본적으로 올바른 캐시 인스턴스로의 리디렉션을 지원하므로 이는 기능 흐름을 확인하는 문제이다).
- 최대 로드로 애플리케이션 성능을 테스트하는 것이 다시 중요해진다.
배치 처리 시스템(Batch Processing Systems)
배치 처리 시스템은 일련의 입력 데이터를 실시간이 아닌 일정 기간 동안 일괄 수집하여 원하는 출력으로 변환하기 위해 프로그램(주로 Job이라고 함)을 작성하는 시스템이다. 이러한 배치 Job은 Spring Batch 또는 Apache Spark와 같은 프레임워크 및 라이브러리를 사용하여 작성할 수 있으며 사용자 개입 없이 자율적으로 실행될 수 있다. 배치 Job의 입력 데이터는 파일, 데이터베이스 레코드, 이미지 등의 형태일 수 있다. 입력 데이터의 양이 방대하여 Job을 완료하는 데 몇 시간 또는 며칠이 걸릴 수도 있다. 실제로 배치 Job의 성능은 데이터베이스나 캐시와 같은 응답 시간이 아니라 처리할 수 있는 입력 파일의 크기와 시간에 따라 측정된다.
배치 처리 시스템의 사용 사례로는 보고서 생성, 청구서 생성, 월별 급여명세서 생성, 기계 학습 모델 교육 전 데이터 클리닝 등이 있다. 이러한 사용 사례에서 관찰할 수 있는 몇 가지 패턴은 배치 Job이 구조화되지 않았거나 희박한 데이터를 의미 있는 데이터 구조로 변환하며, 애플리케이션이 원활하게 실행되기 위해 이러한 변환이 실시간으로 발생할 필요가 없다는 것이다.
배치 처리 시스템의 특성을 고려하여 테스트하는 동안 고려해야 할 테스트 케이스 일부가 다음과 같다.
- 입력 파일이 모두 처리되었는지, 도중에 오퍼레이션이 중단되지 않았는지 확인
- 예상치 못한 null 값, 큰 정수, 기타 변칙(anomalies)을 가진 손상된 입력을 처리
- 필요한 구조로 변환할 수 없는 불완전한 레코드를 표시하고(flagging) 격리하는지(isolating) 확인
- 재시도 메커니즘이 실패한 런(run)의 데이터를 정리하거나 덮어쓰는지 확인
- 상당한 프로세싱 용량을 차지할 수 있는 배치 Job이 애플리케이션 성능에 부정적인 영향을 미치지 않는지 확인
이벤트 스트림(Event Streams)
이벤트는 액션을 의미하는 반면, 스트림은 흐르는(즉 본질적으로 연속적인) 개체를 나타낸다. 이벤트 스트림은 애플리케이션에 특정한 이벤트가 스트림에 지속적으로 게시되고, 다른 관련 시스템이 추가 처리를 위한 시간이 있을 때마다 해당 데이터를 소비하는 시스템이다. 예를 들어 전자상거래 애플리케이션에서 고객이 주문할 때 주문 세부 정보가 포함된 주문 이벤트가 즉시 이벤트 스트림에 게시되고, 다운스트림 시스템들은 이 이벤트를 읽고 주문 이행을 위한 각 조치를 취한다. 데이터 흐름 관점에서 주문 데이터는 일정 기간 동안 이벤트 스트림에 저장되고, 관련 시스템은 그때까지 이벤트 스트림에서 읽는다.
여기서 주문 서비스는 이벤트를 게시하므로 게시자(publisher)라고 하며, 이벤트를 소비하는 다운스트림 시스템은 구독자(subscribers)라고 한다. 모든 이벤트는 구독자가 자신과 관련된 이벤트를 식별할 수 있도록 특정 주제 이름으로 게시된다. Google Cloud Pub/Sub 및 RabbitMQ와 같은 일부 이벤트 스트림 시스템은 모든 의도된 구독자가 이벤트를 소비한 후에 각 이벤트를 삭제한다. Apache Kafka와 같은 다른 시스템에서는 설정된 시간이 지나면 이벤트가 삭제된다. 이 보존 기능을 통해 가입자는 일시적인 오류가 발생한 후에도 이를 따라잡을 수 있다. 이벤트 스트림은 또한 데이터베이스와 같이 이벤트를 디스크에 쓸 때 내구성을 제공한다.
배치 처리 시스템과 이벤트 스트림 처리 시스템은 주로 시간 제한적 특성에서 차이가 있다. 즉, 배치 Job은 미리 설정된 시간이나 그 이후에 입력을 처리하는 반면, 이벤트 스트림 처리는 거의 실시간으로 발생한다. 이를 더 자세히 설명하면 전자상거래 애플리케이션의 주문 서비스는 주문이 생성된 직후 이벤트를 게시하지만 다운스트림 시스템이 이를 바로 받도록 요구하지는 않는다. 즉, 비동기식(asynchronous)이다. 따라서 이벤트 스트림에 주문을 넣으면 배치 처리에서처럼 주문 처리가 몇 시간 이상 지연되지 않지만 웹 서비스 요청처럼 주문이 동기적으로 처리되지도 않는다. 결과적으로 이벤트가 구독자에 의해 몇 초 내에 소비될 수 있더라도 이는 실시간 처리가 아닌 실시간에 가까운 처리(near real-time processing)라고 한다. 이 비동기 모델은 병렬 처리(parallel processing) 및 확장(scaling)에 적합하다. 오늘날 웹 및 모바일 애플리케이션 개발에 상당히 많이 사용되고 있다.
이벤트 스트리밍 시스템에서 고려해야 할 몇 가지 테스트 케이스가 다음과 같다.
- 이벤트 구조는 게시자와 구독자 간의 계약(합의)이므로 구조가 변경될 때마다 전체 기능 흐름을 다시 테스트해야 한다.
- 때로는 이전 이벤트 구조와 새 이벤트 구조를 모두 지원하기 위해 이전 버전과의 호환성(backward compatibility)을 테스트해야 한다.
- 특정 순서로 이벤트를 처리해야 하는 요구사항이 있을 수 있다. 예를 들어, 품목 배송(an item’s shipment)은 창고에서 해당 품목의 가용성을 확인할 때까지 처리될 수 없다. 이벤트 처리가 비동기적으로 발생하므로 이 흐름을 테스트해야 한다.
- 구독자는 실패 시 올바른 순서로 새로운 이벤트를 따라잡을 수 있어야 한다.
- 여러 번 재시도한 후에도 이벤트 처리에 에러가 있는 경우 해당 이벤트는 데드 레터 큐라는 별도의 큐로 이동된다(디버깅에 도움이 되도록 에러 세부 정보도 첨부됨). 데드 레터 큐로 가는 이벤트 흐름을 테스트해야 한다.
- 이벤트 스트림이 다운되면 어떻게 되는가? 게시자와 구독자는 실패를 어떻게 처리하는가? 게시자와 구독자는 언제, 어떻게 재시도를 하는가?
- 구독자의 실적이 게시자의 실적보다 느려지면 스트림이 부풀어오른다. 따라서 적시에 이벤트를 소비하는 구독자의 능력을 테스트해야 한다.
'테스팅타입별 > 데이터 품질' 카테고리의 다른 글
데이터 품질 평가 by umar shariff gani (1) | 2024.09.16 |
---|---|
페이퍼요약 – 데이터 집중 소프트웨어 시스템 테스팅 by Felderer (0) | 2024.03.18 |
책 발췌 – Table 테스팅 by Lewis (2) | 2023.12.04 |
페이퍼 요약 – SQL 화이트박스 테스팅에 대한 실용적인 지침 by Tuya (0) | 2021.12.13 |
문서요약 - 데이터 프로파일링 by HCL Technologies (0) | 2019.03.11 |