비교 검사(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

Path Coverage

경로 커버리지(Path Coverage) 경로 커버리지는 프로그램의 제어 흐름 그래프(Control Flow Graph, CFG)에서 모든 가능한 실행 경로를 테스트하는 구조적 테스팅 기법이다. 이는 프로그램의 입력과 출력 값보다는 내부 제어 흐름에 초점을 맞춘다. 먼저 경로 커버리지의 기본 개념을 간단한 예제를 통해 이해해보자: 1 2 3 4 5 6 7 8 9 10 11 def calculate_discount(price, is_member, is_sale_period): if is_member: if is_sale_period: return price * 0.8 # 20% 할인 else: return price * 0.9 # 10% 할인 else: if is_sale_period: return price * 0.95 # 5% 할인 else: return price # 할인 없음 이 함수에는 다음과 같은 가능한 실행 경로들이 있다: ...

November 1, 2024 · 4 min · Me

조건 커버리지 (Condition Coverage)

조건 커버리지 (Condition Coverage) 조건 커버리지는 결정 포인트 내의 각 개별 조건식이 참(true)과 거짓(false)의 결과를 최소한 한 번씩 갖도록 테스트하는 기법이다. 이는 전체 조건식의 결과와는 독립적으로 각 개별 조건의 결과에 초점을 맞춘다. 주요 특징 개별 조건 중심: 전체 조건식이 아닌 각 개별 조건식의 결과를 검증한다. 최소 요구사항: 각 조건이 최소한 한 번씩 참과 거짓의 결과를 가져야 한다. 세분화된 테스트: 복잡한 조건문의 각 부분을 개별적으로 테스트할 수 있다. 장점 조건의 독립적 평가: 각 조건을 독립적으로 평가하여 더 세밀한 테스트가 가능하다. 제어 흐름에 대한 높은 민감도: 프로그램의 제어 흐름을 더 정확하게 테스트할 수 있다. 결정 커버리지보다 강력: 더 많은 테스트 케이스를 요구하므로 더 철저한 테스트가 가능하다. 단점 전체 조건식 결과 보장 부족: 개별 조건의 참/거짓만을 테스트하므로 전체 조건식의 모든 결과를 보장하지 않을 수 있다. 테스트 케이스 증가: 조건의 수가 많아질수록 필요한 테스트 케이스의 수가 증가한다. 따라서 조건 커버리지는 다른 테스트 커버리지 지표들(구문 커버리지, 분기 커버리지 등)과 함께 사용되어야 하며, 이를 통해 더 완성도 높은 테스트를 수행할 수 있다. 조건 커버리지를 계산하는 방법 1 2 3 4 5 6 public boolean isEligibleForDiscount(int age, boolean isMember, int purchaseAmount) { if (age >= 60 && isMember || purchaseAmount > 1000) { return true; } return false; } 이 코드에는 세 가지 개별 조건이 있다: ...

November 1, 2024 · 4 min · Me

Decision Coverage

결정 커버리지 (Decision Coverage) 결정 커버리지는 프로그램의 모든 결정 포인트(조건문)에서 전체 조건식이 최소한 한 번씩 참(True)과 거짓(False)의 결과를 가지도록 테스트하는 방법이다. 이는 브랜치 커버리지(Branch Coverage)라고도 불린다. 간단한 예제: 1 2 3 4 5 6 7 8 9 10 11 public class LoanApproval { public boolean approveLoan(double income, double creditScore) { if (income >= 50000) { if (creditScore >= 700) { return true; } return false; } return false; } } 이 코드의 결정 커버리지를 100% 달성하기 위해서는 다음과 같은 테스트 케이스가 필요하다: ...

November 1, 2024 · 3 min · Me

Statement Coverage

구문 커버리지 (Statement Coverage) 구문 커버리지는 프로그램을 구성하는 모든 문장들이 최소한 한 번은 실행될 수 있는 입력 데이터를 테스트 데이터로 선정하는 기준이다. 또한 라인 커버리지(Line Coverage)라고도 불린다. 먼저 간단한 예제를 통해 구문 커버리지의 이해: 1 2 3 4 5 6 7 8 9 10 11 12 13 def calculate_grade(score): # 구문 1 if score >= 90: # 구문 2 grade = 'A' elif score >= 80: # 구문 3 grade = 'B' else: # 구문 4 grade = 'C' # 구문 5 return grade 이 함수의 모든 구문을 실행하기 위해서는 다음과 같은 테스트 케이스가 필요하다: ...

November 1, 2024 · 4 min · Me

Function Coverage

함수 커버리지 (Function Coverage) 함수 커버리지는 프로그램 내의 모든 함수가 테스트 중에 최소한 한 번 이상 호출되었는지를 측정하는 지표이다. 간단한 예제: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Calculator: def add(self, a, b): return a + b def subtract(self, a, b): return a - b def multiply(self, a, b): return a * b def divide(self, a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b 이 계산기 클래스의 모든 함수를 테스트하기 위해서는 다음과 같은 테스트 코드가 필요하다: ...

November 1, 2024 · 4 min · Me

다중 조건 테스팅 (Branch Condition Combination Testing)

다중 조건 테스팅 (Branch Condition Combination Testing) 다중 조건 테스팅은 각 결정문에서 가능한 모든 조건 조합을 테스트하는 기법이다. 이는 조건 커버리지의 확장된 형태로, 가능한 모든 부울 조건 조합을 테스트하는 것을 목표로 한다. 주요 특징 완전한 조건 조합: 결정문 내의 모든 개별 조건식에 대해 가능한 모든 true/false 조합을 테스트한다. 높은 커버리지: 100% 다중 조건 커버리지를 달성하면 결정 커버리지와 조건 커버리지도 100% 달성된다. 복잡한 로직 테스트: 여러 조건이 복합적으로 사용되는 복잡한 의사결정 구조를 철저히 테스트할 수 있다. 장점 철저한 테스트: 모든 가능한 조건 조합을 테스트하므로 누락된 경우의 수 없이 철저한 테스트가 가능하다. 숨겨진 버그 발견: 특정 조건 조합에서만 발생하는 오류를 찾아낼 수 있다. 로직 오류 검출: 복잡한 조건문의 로직 오류를 효과적으로 발견할 수 있다. 단점 테스트 케이스 증가: 조건의 수가 증가할수록 테스트 케이스의 수가 기하급수적으로 늘어난다. 시간과 비용: 많은 테스트 케이스로 인해 테스트 수행 시간과 비용이 증가한다. 복잡성: 조건이 많은 경우 모든 조합을 고려하는 것이 매우 복잡해질 수 있다. 사용 사례 다중 조건 테스팅은 다음과 같은 상황에서 특히 유용하다: ...

November 1, 2024 · 2 min · Me