Equivalence Partitioning

동등 분할(Equivalence Partitioning) 동등 분할은 입력 또는 출력 데이터를 의미 있는 그룹으로 나누어 테스트하는 기법. 이 방법의 핵심 아이디어는 같은 그룹에 속한 데이터는 프로그램에서 동일한 방식으로 처리될 것이라는 가정에 기반한다. 따라서 각 그룹에서 대표값만 테스트함으로써 효율적으로 테스트를 수행할 수 있다. 예를 들어, 학생의 시험 점수(0-100점)를 등급(A, B, C, D, F)으로 변환하는 프로그램을 생각해보자. 이 경우 점수 범위를 다음과 같이 분할할 수 있다: 유효 분할: 90-100점: A등급 80-89점: B등급 70-79점: C등급 60-69점: D등급 0-59점: F등급 무효 분할: ...

November 2, 2024 · 3 min · Me

Scenario Testing

시나리오 테스팅 (Scenario Testing) 시나리오 테스팅은 실제 상황을 시뮬레이션하여 소프트웨어를 검증하는 강력한 기술이다. 개별 기능에 초점을 맞춘 기존 테스트 케이스와 달리 시나리오 테스트에서는 일련의 이벤트나 상호 작용이 발생할 때 시스템이 어떻게 작동하는지 검사한다. 주요 특징 사용자 중심 접근: 실제 사용자의 관점에서 소프트웨어를 테스트한다. 전체 흐름 검증: 개별 기능이 아닌 전체 사용 흐름을 테스트한다. 현실적인 상황 재현: 실제 사용 환경과 유사한 상황을 시뮬레이션한다. 비기술적 언어 사용: 비기술적인 사용자도 이해할 수 있는 언어로 작성된다. 장점 사용자 경험 개선: 실제 사용자의 관점에서 테스트하므로 사용자 불편이나 흐름의 단절을 발견할 수 있다. 종합적인 결함 발견: 기능 간 상호작용에서 발생하는 결함을 발견할 수 있다. 예기치 못한 문제 파악: 다양한 상황을 미리 시뮬레이션하여 잠재적인 오류와 리스크를 발견할 수 있다. 팀 간 협업 강화: 비기술적 이해관계자도 시나리오를 이해하기 쉬워 협업이 용이하다. 적용 방법 시나리오 정의: 테스트할 주요 시나리오를 식별한다. 테스트 데이터 준비: 실제 환경에서 발생할 수 있는 모든 상황을 포괄하는 데이터를 준비한다. 시나리오 실행: 정의된 시나리오에 따라 테스트를 수행한다. 결과 분석: 시나리오 실행 결과를 분석하고 문제점을 식별한다. 참고 및 출처

November 2, 2024 · 1 min · Me

조합 테스트 설계 기법 (Combinatorial Test Design Techniques)

조합 테스트 설계 기법 (Combinatorial Test Design Techniques) 조합 테스트 설계는 입력 매개변수의 다양한 조합을 사용하여 소프트웨어 애플리케이션을 테스트하는 방법이다. 이 기법은 모든 가능한 입력 조합을 테스트하는 대신 최적화된 조합을 선택하여 테스트 범위를 최대화하고 테스트 케이스 수를 최소화한다. 실제 테스트 설계 시에는 다음과 같은 단계를 따르는 것이 좋다: 테스트 대상 식별 먼저 어떤 요소들을 테스트할 것인지 명확히 한다. 각 요소가 가질 수 있는 값들도 정의한다. 제약조건 파악 일부 조합은 현실적으로 불가능하거나 의미가 없을 수 있다. 예를 들어, 특정 운영체제 버전에서는 5G를 지원하지 않을 수 있다. 테스트 방법 선택 시스템의 중요도와 가용 자원을 고려하여 적절한 테스트 방법을 선택한다. 테스트 케이스 생성 선택한 방법에 따라 테스트 케이스를 생성한다. 이때 자동화 도구를 활용하면 더욱 효율적이다. 주요 특징 수학적 기반: 직교 배열(Orthogonal Arrays)과 같은 수학적 개념을 활용한다. 효율성: 적은 수의 테스트 케이스로 넓은 범위의 입력 조합을 커버한다. 리스크 감소: 특정 입력 조합에서 발생할 수 있는 결함을 효과적으로 찾아낸다. 자동화 가능: 특수한 알고리즘이나 도구를 사용하여 테스트 케이스를 자동으로 생성할 수 있다. 주요 기법 페어와이즈 테스팅(Pairwise Testing): 모든 가능한 입력 매개변수 쌍의 조합을 테스트한다. N-방향 테스팅(N-way Testing): 페어와이즈를 확장하여 3개 이상의 매개변수 조합을 고려한다. 직교 배열 테스팅(Orthogonal Array Testing): 수학적 직교 배열을 사용하여 효율적인 테스트 조합을 생성한다. 올페어 알고리즘(AllPairs Algorithm): 각 변수 쌍에 대한 모든 값 조합을 효율적으로 테스트한다. 장점 테스트 케이스 수 감소: 효율적인 조합으로 필요한 테스트 케이스 수를 줄인다. 높은 결함 검출률: 다양한 입력 조합을 테스트하여 더 많은 결함을 발견할 수 있다. 시간과 비용 절감: 적은 수의 테스트로 넓은 범위를 커버하여 테스트 시간과 비용을 줄인다. 조기 결함 발견: 개발 초기 단계에서 결함을 식별할 수 있다. 한계점 복잡한 시스템에서의 적용 어려움: 매개변수가 많은 복잡한 시스템에서는 구현이 어려울 수 있다. 모든 결함 검출 불가능: 특정 시퀀스나 의존성이 필요한 결함은 놓칠 수 있다. 정확한 입력 데이터 필요: 효과적인 테스트를 위해서는 정확하고 완전한 입력 데이터가 필요하다. 조합 테스트를 더욱 효과적으로 수행하기 위한 팁 우선순위 설정: 모든 조합을 테스트할 수 없다면, 중요도나 위험도를 기준으로 우선순위를 정한다. 예를 들어, 사용자가 가장 많이 사용하는 조합이나 문제가 발생했을 때 영향이 큰 조합을 먼저 테스트한다. ...

November 2, 2024 · 2 min · Me

랜덤 테스팅(Random Testing)

랜덤 테스팅 (Random Testing) 랜덤 테스팅은 테스트 대상 시스템에 임의로 생성된 데이터를 입력하여 프로그램의 동작을 검증하는 기법이다. 이는 통계적 테스팅 또는 몬테카를로 테스팅으로도 알려져 있다. 실제 예시를 통해 더 자세히 살펴보자. 숫자 정렬 프로그램을 테스트한다고 가정해보자. 전통적인 테스트 방식에서는 미리 정해진 테스트 케이스(예: [1,2,3] 또는 [3,2,1])를 사용할 것. 하지만 랜덤 테스팅에서는 무작위로 생성된 숫자 배열을 사용한다. 이를 통해 개발자가 미처 생각하지 못한 경우의 수를 테스트할 수 있다. 랜덤 테스팅의 실제 적용 과정을 단계별로 살펴보자: ...

November 2, 2024 · 2 min · Me

페어와이즈 테스팅(Pairwise Testing)

페어와이즈 테스팅(Pairwise Testing) 페어와이즈 테스팅은 모든 가능한 입력 값 조합을 테스트하는 대신, 입력 매개변수의 모든 쌍(pair)을 최소한 한 번씩 테스트하는 기법이다. 이는 대부분의 결함이 두 입력 값의 상호작용에 기인한다는 관찰에 기반한다. 실제 예시를 통해 더 자세히 살펴보자. 웹 브라우저 애플리케이션을 테스트한다고 가정하면: 운영체제: Windows, Mac, Linux 브라우저: Chrome, Firefox, Safari 화면 해상도: HD, FHD, 4K 언어 설정: 한국어, 영어, 일본어 만약 모든 가능한 조합을 테스트하려면 몇 개의 테스트 케이스가 필요할까? 3(운영체제) × 3(브라우저) × 3(해상도) × 3(언어) = 81개의 테스트 케이스가 필요하다. ...

November 2, 2024 · 2 min · Me

비교 검사(Comparison Testing)

비교 검사(Comparison Testing) 비교 검사는 블랙박스 테스팅 기법 중 하나로, 동일한 기능을 수행하는 여러 버전의 소프트웨어나 시스템을 비교하여 테스트하는 방법. 비교 검사는 동일한 입력값을 여러 버전의 소프트웨어에 제공하고, 그 출력값을 비교하는 방식으로 진행된다. 예를 들어, 새로운 버전의 소프트웨어와 이전 버전의 소프트웨어에 같은 입력을 주고 결과를 비교하여 일관성을 검증한다. 비교 검사는 특히 다음과 같은 상황에서 효과적이다: 중요한 시스템 업그레이드 시: 새로운 버전이 기존 기능을 정확히 수행하는지 확인 예상치 못한 부작용 발견 호환성 검증 시: ...

November 2, 2024 · 2 min · Me

Fakes

Fakes Fakes는 Test Double 기법 중 하나로, 실제 객체의 간단한 구현을 제공하는 테스트용 객체. 목적 실제 구현체를 단순화하여 테스트 환경에서 사용한다. 외부 의존성을 제거하고 테스트 속도를 향상시킨다. 실제 객체와 유사한 동작을 제공하여 현실적인 테스트 환경을 구성한다. 장점 테스트 실행 속도가 빠르다. 실제 구현체보다 구성이 간단하다. 테스트 간 재사용이 용이하다. 실제 객체와 유사한 동작으로 신뢰성 있는 테스트가 가능하다. 단점 실제 구현체와 동작이 완전히 일치하지 않을 수 있다. Fake 객체 구현에 추가적인 시간과 노력이 필요하다. Fake 객체 자체의 유지보수가 필요할 수 있다. 예시 Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class RealDatabase: def connect(self): # 실제 데이터베이스 연결 로직 pass def fetch_data(self): # 실제 데이터 조회 로직 return "Real data" class FakeDatabase: def connect(self): # 연결 시뮬레이션 pass def fetch_data(self): # 가짜 데이터 반환 return "Fake data" class DataService: def __init__(self, database): self.database = database def get_data(self): self.database.connect() return self.database.fetch_data() # 테스트 fake_db = FakeDatabase() data_service = DataService(fake_db) result = data_service.get_data() assert result == "Fake data" JavaScript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class RealDatabase { connect() { // 실제 데이터베이스 연결 로직 } fetchData() { // 실제 데이터 조회 로직 return "Real data"; } } class FakeDatabase { connect() { // 연결 시뮬레이션 } fetchData() { // 가짜 데이터 반환 return "Fake data"; } } class DataService { constructor(database) { this.database = database; } getData() { this.database.connect(); return this.database.fetchData(); } } // 테스트 const fakeDb = new FakeDatabase(); const dataService = new DataService(fakeDb); const result = dataService.getData(); console.assert(result === "Fake data"); 참고 및 출처

November 1, 2024 · 2 min · Me

Dummy Objects

Dummy Objects 테스트 과정에서 실제로는 사용되지 않지만 메서드의 파라미터를 채우기 위해 전달되는 객체 Dummy Objects는 Test Double 기법 중 하나로, 테스트에 필요하지만 실제로 사용되지 않는 객체를 의미합니다. 목적 테스트 대상 코드의 인터페이스 요구사항을 충족시키기 위해 사용된다. 테스트 실행을 위해 필요하지만 테스트 자체와는 관련이 없는 객체를 대체한다. 장점 테스트 코드를 단순화하고 가독성을 높인다. 불필요한 객체 생성을 피해 테스트 성능을 향상시킨다. 테스트 대상 코드를 외부 의존성으로부터 격리시킨다. 단점 실제 객체의 동작을 정확히 반영하지 않을 수 있다. 과도한 사용 시 테스트의 현실성이 떨어질 수 있다. 예시 Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 # 실제 이메일 서비스 클래스 class EmailService: def __init__(self, smtp_server, port): self.smtp_server = smtp_server self.port = port def send_notification(self, user, message): # 실제로는 이메일을 보내는 복잡한 로직이 있을 것입니다 print(f"Sending email to {user.email}: {message}") # 사용자 클래스 class User: def __init__(self, name, email, notification_service): self.name = name self.email = email self.notification_service = notification_service def notify_login(self): self.notification_service.send_notification( self, f"New login detected for {self.name}" ) # Dummy 객체 class DummyEmailService: def __init__(self, smtp_server=None, port=None): pass # 아무것도 하지 않음 def send_notification(self, user, message): pass # 아무것도 하지 않음 # 테스트 코드 def test_user_creation(): # Dummy 이메일 서비스 사용 dummy_email_service = DummyEmailService() # 실제로 테스트하고 싶은 것은 사용자 생성 로직입니다 user = User("John Doe", "john@example.com", dummy_email_service) assert user.name == "John Doe" assert user.email == "john@example.com" JavaScript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // 실제 로깅 서비스 클래스 class LoggerService { constructor(logLevel) { this.logLevel = logLevel; } log(message) { // 실제로는 로그를 저장하는 복잡한 로직이 있을 것입니다 console.log(`[${this.logLevel}] ${message}`); } } // 사용자 관리 클래스 class UserManager { constructor(logger) { this.logger = logger; this.users = []; } addUser(user) { this.users.push(user); this.logger.log(`User ${user.name} added`); return this.users.length; } } // Dummy 로거 class DummyLogger { constructor(logLevel) { // 아무것도 저장하지 않음 } log(message) { // 아무것도 하지 않음 } } // 테스트 코드 describe('UserManager', () => { it('should add a new user correctly', () => { // Dummy 로거 사용 const dummyLogger = new DummyLogger(); const userManager = new UserManager(dummyLogger); // 실제로 테스트하고 싶은 것은 사용자 추가 로직입니다 const userCount = userManager.addUser({ name: 'John' }); expect(userCount).toBe(1); expect(userManager.users.length).toBe(1); }); }); 참고 및 출처

November 1, 2024 · 2 min · Me

Spies

Spies Spies는 Test Double 기법 중 하나로, 실제 객체의 메서드 호출을 추적하고 기록하는 데 사용된다. 목적 메서드 호출 여부, 횟수, 전달된 인자 등을 검증한다. 실제 구현을 변경하지 않고 메서드의 동작을 관찰한다. 코드의 상호작용을 분석하고 테스트한다. 장점 비침투적: 실제 객체의 동작을 변경하지 않고 관찰할 수 있다. 유연성: 다양한 정보를 수집하고 검증할 수 있다. 상세한 검증: 메서드 호출의 세부 사항을 정확히 확인할 수 있다. 단점 복잡성: 과도한 사용 시 테스트 코드가 복잡해질 수 있다. 오버스펙: 구현 세부사항에 너무 의존적인 테스트를 작성할 위험이 있다. 성능: 많은 spy를 사용할 경우 테스트 실행 속도가 느려질 수 있다. 예시 예시 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 from typing import Dict, Optional from datetime import datetime # 실제 데이터베이스 리포지토리 class UserRepository: def __init__(self, database_connection): self.db = database_connection def save(self, user_id: str, user_data: Dict): # 실제로는 데이터베이스에 SQL 쿼리를 실행할 것입니다 self.db.execute( "INSERT INTO users (id, data, created_at) VALUES (?, ?, ?)", [user_id, user_data, datetime.now()] ) def find_by_id(self, user_id: str) -> Optional[Dict]: # 실제로는 데이터베이스에서 조회할 것입니다 result = self.db.execute( "SELECT * FROM users WHERE id = ?", [user_id] ) return result.fetchone() # Fake 리포지토리 class FakeUserRepository: def __init__(self): # 데이터베이스 대신 딕셔너리를 사용 self.users: Dict[str, Dict] = {} def save(self, user_id: str, user_data: Dict): # 메모리에 직접 저장 self.users[user_id] = { 'data': user_data, 'created_at': datetime.now() } def find_by_id(self, user_id: str) -> Optional[Dict]: # 메모리에서 직접 조회 return self.users.get(user_id) # 사용자 서비스 class UserService: def __init__(self, user_repository): self.repository = user_repository def create_user(self, user_id: str, name: str, email: str): user_data = {'name': name, 'email': email} self.repository.save(user_id, user_data) def get_user(self, user_id: str): return self.repository.find_by_id(user_id) # 테스트 코드 def test_user_service(): # Fake 리포지토리 사용 fake_repository = FakeUserRepository() user_service = UserService(fake_repository) # 사용자 생성 테스트 user_service.create_user('user1', 'John Doe', 'john@example.com') # 사용자 조회 테스트 user = user_service.get_user('user1') assert user['data']['name'] == 'John Doe' assert user['data']['email'] == 'john@example.com' JavaScript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // 실제 외부 API 서비스 class WeatherService { async getTemperature(city) { // 실제로는 외부 API를 호출할 것입니다 const response = await fetch( `https://api.weather.com/${city}/temperature` ); return response.json(); } } // Fake 날씨 서비스 class FakeWeatherService { constructor() { // 미리 정의된 도시별 온도 데이터 this.temperatureData = { 'Seoul': { temperature: 25 }, 'New York': { temperature: 20 }, 'London': { temperature: 15 } }; } async getTemperature(city) { // 실제 API 호출 대신 저장된 데이터 반환 return Promise.resolve(this.temperatureData[city] || { temperature: 0 }); } } // 날씨 알림 서비스 class WeatherAlertService { constructor(weatherService) { this.weatherService = weatherService; } async shouldSendAlert(city) { const data = await this.weatherService.getTemperature(city); return data.temperature > 30; } } // 테스트 코드 describe('WeatherAlertService', () => { it('should not send alert for normal temperature', async () => { // Fake 날씨 서비스 사용 const fakeWeatherService = new FakeWeatherService(); const alertService = new WeatherAlertService(fakeWeatherService); const shouldAlert = await alertService.shouldSendAlert('Seoul'); expect(shouldAlert).toBe(false); }); }); 참고 및 출처

November 1, 2024 · 3 min · Me

Stubs

Stubs Stubbing은 테스트에서 사용되는 기법으로, 실제 객체나 아직 구현되지 않은 코드를 대신하여 미리 정의된 응답을 제공하는 메커니즘 목적 의존성 격리: 실제 구현체로부터 테스트 대상을 분리하여 독립적인 테스트를 가능하게 합니다. 특정 시나리오 테스트: 다양한 상황에 대한 테스트를 용이하게 합니다. 미구현 코드 대체: 아직 개발되지 않은 부분을 임시로 대체할 수 있습니다. 테스트 속도 향상: 실제 리소스 접근 없이 빠른 테스트가 가능합니다. 특징 미리 정의된 응답(canned answer)을 제공합니다. 실제 코드의 동작을 단순화하여 모사합니다. 주로 상태 테스팅에 중점을 둡니다. 메서드 호출의 결과만 정의하며, 호출 여부는 검증하지 않습니다. 사용 사례 구현되지 않은 함수나 외부 라이브러리 함수를 사용할 때 복잡한 로직을 단순화하여 테스트하고자 할 때 특정 조건에서의 예외 상황을 테스트할 때 외부 의존성(예: 데이터베이스, 네트워크 요청)을 가진 코드를 테스트할 때 예시 Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from unittest.mock import Mock, patch import pytest from datetime import datetime # 테스트할 실제 클래스 class PaymentService: def __init__(self, payment_gateway): self.payment_gateway = payment_gateway def process_payment(self, amount): if amount <= 0: raise ValueError("Amount must be positive") response = self.payment_gateway.charge(amount) if response['status'] == 'success': return True return False # 외부 결제 게이트웨이 클래스 (실제로는 외부 서비스) class PaymentGateway: def charge(self, amount): # 실제로는 외부 API를 호출하는 복잡한 로직 pass # Stub 예시 class PaymentGatewayStub: def charge(self, amount): # 항상 성공 응답을 반환하는 단순한 구현 return {'status': 'success', 'timestamp': datetime.now()} # 테스트 코드 def test_payment_service_with_stub(): # Stub 사용 gateway_stub = PaymentGatewayStub() payment_service = PaymentService(gateway_stub) assert payment_service.process_payment(100) == True Javascript 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 // Jest를 사용한 테스트 예시 const { jest } = require('@jest/globals'); // 테스트할 실제 클래스 class UserService { constructor(database) { this.database = database; } async getUserById(id) { const user = await this.database.findUser(id); if (!user) { throw new Error('User not found'); } return user; } async updateUserEmail(id, newEmail) { const user = await this.database.findUser(id); if (!user) { throw new Error('User not found'); } user.email = newEmail; await this.database.updateUser(id, user); return user; } } // Stub 예시 class DatabaseStub { constructor() { this.users = new Map([ [1, { id: 1, name: 'John Doe', email: 'john@example.com' }], [2, { id: 2, name: 'Jane Doe', email: 'jane@example.com' }] ]); } async findUser(id) { return this.users.get(id); } async updateUser(id, userData) { this.users.set(id, userData); return userData; } } // 테스트 코드 describe('UserService', () => { // Stub을 사용한 테스트 describe('with stub', () => { const dbStub = new DatabaseStub(); const userService = new UserService(dbStub); test('should return user when exists', async () => { const user = await userService.getUserById(1); expect(user.name).toBe('John Doe'); }); test('should throw error when user not found', async () => { await expect(userService.getUserById(999)) .rejects .toThrow('User not found'); }); }); }); 참고 및 출처

November 1, 2024 · 3 min · Me