728x90
품질을 위한 체계적인 검증 전략
오늘은 테스트에 대해 알아보자.
오늘의 배움 |
|
1. 소프트웨어 개발 테스트
- 정의: 개발된 시스템이 올바르게 동작하는지 검증하는 과정으로, 버그를 발견하고 품질을 보장하는 활동
- 한 줄 요약: "오류를 미리 발견하여 품질을 보장하고 유지보수성을 높이는 체계적인 검증 과정"
- 특징:
- 테스트는 '오류가 없다'를 증명하는 것이 아닌 '오류를 발견'하기 위한 과정
- 다양한 테스트 기법과 레벨이 존재 (단위, 통합, 시스템 등)
- 자동화 도구를 활용해 효율성 증대 가능
- 필요성:
- 결함 발견: 코드에 존재하는 오류를 조기에 발견하고 수정한다.
- 소프트웨어 품질 보장: 예상된 동작과 실제 동작이 일치하는지 확인한다.
- 개발 비용 절감: 개발 단계에서 버그를 발견하고 해결하면 비용을 절감할 수 있다.
- 유지보수성 향상: 코드의 신뢰성을 높여 장기적인 유지보수성을 개선한다.
- 장점/단점:
- 장점: 품질 향상, 유지보수 비용 절감, 사용자 만족도 증가
- 단점: 시간과 비용 소모, 완벽한 테스트는 불가능, 모든 결함을 찾을 수 없음
- 예시: 은행 앱에서 송금 기능 테스트 - 올바른 계좌로 정확한 금액이, 중복 처리 없이, 보안적으로 안전하게 송금되는지 검증
쇼핑몰 주문 시스템 개발 예시:
|
2. 핵심 개념 정리
2-1. 블랙박스 테스트 (Black-Box Testing)
- 정의: 내부 코드 구조를 고려하지 않고 입력과 출력만을 테스트하는 기법
- 작동 원리: 명세를 기반으로 '무엇을 하는지'에 중점을 두고 기능적 측면 검증
- 특징: 개발자가 아닌 테스터도 수행 가능, 사용자 관점에서 테스트
- 장점/단점:
- 장점: 개발자 편향 없음, 실제 사용 시나리오 기반 테스트 가능
- 단점: 모든 경우의 수를 테스트하기 어려움, 내부 로직 누락 가능성
- 필요성: 사용자 관점의 기능 검증과 요구사항 충족 여부 확인
- 주요 기법:
- 동등 분할 테스트(ECP, Equivalence Class Partitioning): 입력값을 여러 개의 그룹으로 나누어 대표값을 테스트
- 경계값 분석(BVA, Boundary Value Analysis): 입력값의 경계 영역을 집중적으로 테스트
- 원인-결과 그래프(Cause-Effect Graphing): 입력 조건(원인)과 출력 동작(결과) 간의 관계를 그래프로 표현해 테스트 케이스 도출
- 결정 테이블 테스트(Decision Table Testing): 다양한 입력 조합에 대한 출력을 테스트하는 기법
- 상태 전이 테스트(State Transition Testing): 시스템의 상태 변화에 따른 동작을 검증
- 유스케이스 테스트(Use Case Testing): 실제 사용자 시나리오나 업무 흐름을 기반으로 기능 테스트
- 예시:
# 블랙박스 테스트 예시 (경계값 분석)
def test_age_verification():
# 경계값 분석: 18세 기준 성인 판별 함수
assert is_adult(17) == False # 경계값 아래
assert is_adult(18) == True # 경계값
assert is_adult(19) == True # 경계값 위
2-2. 화이트박스 테스트 (White-Box Testing)
- 정의: 코드의 내부 구조와 로직을 분석하여 테스트 케이스를 설계하는 기법
- 작동 원리: 'if-else', 반복문 등의 모든 논리적 경로를 확인
- 특징: 개발자 수준의 지식 필요, 코드 커버리지 측정 가능
- 장점/단점:
- 장점: 코드 내부 흐름 검증, 논리적 오류 발견
- 단점: 개발자 지식 필요, 시간 소모적, 요구사항 누락 발견 어려움
- 필요성: 내부 구현의 정확성 보장, 모든 코드 경로 실행 확인
- 주요 기법:
- 문장(구문) 커버리지(Statement Coverage) : 모든 개별 코드 라인(문장)이 최소 1회 이상 실행되었는지 확인
- 분기(결정) 커버리지(Decision / Branch Coverage): if, while, for 등의 분기점에서 True/False 경로를 각각 1회 이상 실행
- 조건 커버리지(Condition Coverage): 하나의 분기문 안에 있는 각 조건 요소(예: A && B)의 True/False 결과를 각각 1회 이상 테스트
- 다중 조건(복합 조건) 커버리지(Multiple Condition Coverage): 모든 조건 조합을 테스트 (예: A && B → TT, TF, FT, FF)
- 루프 테스트(Loop Testing): 반복문을 0회, 1회, 여러 회, 최대 회수 등 다양한 반복 조건에서 실행하며 검증
- 기본 경로 테스트(Basic Path Testing): 코드의 논리 복잡도(Cyclomatic Complexity)를 기반으로 독립적인 실행 경로를 모두 테스트
- 예시:
# 화이트박스 테스트 예시 (분기 커버리지)
def discount_calculator(price, member_type):
if member_type == "VIP":
return price * 0.8 # 20% 할인
elif member_type == "GOLD":
return price * 0.9 # 10% 할인
else:
return price # 할인 없음
def test_discount_calculator_branch_coverage():
# 모든 분기 커버
assert discount_calculator(1000, "VIP") == 800 # VIP 분기
assert discount_calculator(1000, "GOLD") == 900 # GOLD 분기
assert discount_calculator(1000, "REGULAR") == 1000 # 기본 분기
2-3. 객체지향 테스트
- 정의: 객체 간의 상호작용을 고려하여 테스트하는 기법
- 작동 원리: 캡슐화, 상속, 다형성 등 객체지향 특성을 고려한 테스트
- 특징: 클래스 단위부터 시작해 점차 확장, 객체 간 메시지 전달 검증
- 장점/단점:
- 장점: 객체 간 상호작용 검증, 재사용성 높은 테스트 코드
- 단점: 객체지향 설계 이해 필요, 복잡한 의존성 처리 필요
- 필요성: 객체 기반 설계의 정확한 동작 보장
- 주요 기법:
- 클래스 테스트: 단위 테스트 수준, 클래스/객체의 속성과 메서드 검증
- 통합 테스트: 여러 클래스를 통합하여 인터페이스/메시지 전달 검증
- 스레드 기반 테스트: 하나의 유스케이스를 구성하는 클래스 흐름 단위로 테스트
- 사용 기반 테스트: 상위 클래스 테스트 → 종속 클래스 테스트 흐름
- 확인 테스트: 요구사항 명세대로 구현되었는지 확인 (Validation)
- 시스템 테스트: 모든 컴포넌트가 통합되어 전체 동작을 테스트 (End-to-End)
- 예시:
# 객체지향 테스트 예시 (클래스 테스트)
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
return True
return False
def test_bank_account():
# 클래스 테스트
account = BankAccount(1000)
assert account.balance == 1000
# 메서드 테스트
assert account.deposit(500) == True
assert account.balance == 1500
assert account.withdraw(200) == True
assert account.balance == 1300
# 경계조건 테스트
assert account.withdraw(0) == False # 0원 출금 불가
assert account.withdraw(1500) == False # 잔액보다 큰 금액 출금 불가
2-4. 통합 테스트
- 정의: 둘 이상의 모듈이나 클래스를 결합해 상호작용을 테스트하는 기법
- 작동 원리: 컴포넌트 간 인터페이스와 데이터 흐름 검증
- 특징: 여러 단위 테스트보다 복잡하고 환경 구성 필요
- 장점/단점:
- 장점: 객체 간 상호작용 확인, 단위 테스트로 찾기 어려운 오류 발견
- 단점: 테스트 환경 구성 복잡, 실패 원인 파악 어려움
- 필요성: 개별 컴포넌트가 함께 동작할 때 발생하는 문제 발견
- 예시:
# 통합 테스트 예시
class ShoppingCart:
def __init__(self):
self.items = {}
def add_item(self, product_id, quantity):
if product_id in self.items:
self.items[product_id] += quantity
else:
self.items[product_id] = quantity
def get_total_quantity(self):
return sum(self.items.values())
class OrderProcessor:
def process_order(self, cart, payment_method):
if not cart.items:
return False
# 실제로는 여기서 결제 처리
return True
# 통합 테스트
def test_cart_and_order_integration():
# 장바구니와 주문처리 객체 간 통합 테스트
cart = ShoppingCart()
processor = OrderProcessor()
# 빈 장바구니로 주문 시도
assert processor.process_order(cart, "credit") == False
# 상품 추가 후 주문 처리
cart.add_item("product1", 2)
assert cart.get_total_quantity() == 2
assert processor.process_order(cart, "credit") == True
2-5. 시스템 테스트
- 정의: 모든 구성 요소를 통합한 전체 시스템의 동작을 검증하는 테스트
- 작동 원리: 실제 사용 환경과 유사한 조건에서 엔드투엔드 시나리오 검증
- 특징: 실제 사용자 관점, 기능 간 상호작용 확인
- 장점/단점:
- 장점: 전체 기능 흐름 확인, 실제 환경 유사한 테스트
- 단점: 설정 복잡, 테스트 대상 크고 시간 소요 많음
- 필요성: 개별 테스트로는 확인할 수 없는 시스템 전체의 품질 확인
- 예시:
# 시스템 테스트 예시 (웹 애플리케이션)
from selenium import webdriver
def test_end_to_end_purchase():
# 웹 브라우저 실행
driver = webdriver.Chrome()
try:
# 쇼핑몰 접속
driver.get("https://example-shop.com")
# 로그인
driver.find_element_by_id("username").send_keys("testuser")
driver.find_element_by_id("password").send_keys("password123")
driver.find_element_by_id("login-button").click()
# 상품 검색 및 선택
driver.find_element_by_id("search").send_keys("노트북")
driver.find_element_by_id("search-button").click()
driver.find_element_by_class_name("product-item").click()
# 장바구니 추가
driver.find_element_by_id("add-to-cart").click()
# 결제 진행
driver.find_element_by_id("checkout").click()
# 주소 입력
driver.find_element_by_id("address").send_keys("서울시 강남구")
# 결제 방법 선택
driver.find_element_by_id("payment-card").click()
# 주문 완료
driver.find_element_by_id("place-order").click()
# 주문 성공 페이지 확인
success_message = driver.find_element_by_id("order-confirmation").text
assert "주문이 완료되었습니다" in success_message
finally:
driver.quit()
2-6. 테스트 자동화
- 정의: 테스트 케이스를 자동으로 실행하고 결과를 확인하는 도구와 기법
- 작동 원리: 테스트 스크립트를 작성하여 반복 실행, 결과 검증 자동화
- 특징: 반복 가능, 일관성 있는 테스트, CI/CD 파이프라인 통합 가능
- 장점/단점:
- 장점: 시간 절약, 인적 오류 감소, 회귀 테스트 효율화
- 단점: 초기 구축 비용, 유지보수 필요, 모든 테스트 자동화 불가
- 필요성: 반복적인 테스트 효율화, 지속적 통합/배포(CI/CD) 지원
- 자동화 도구:
- JUnit (Java) : 단위 테스트, 자동화 테스트 지원
- Jest (JavaScript) : 프론트엔드 및 백엔드 테스트 지원
- PyTest (Python) : 단순한 API부터 복잡한 시스템 테스트까지 지원
- 간결한 문법 + 강력한 기능 + 다양한 플러그인을 갖춘 Python 테스트 프레임워크로, 단위 테스트, 통합 테스트, UI 테스트(Selenium과 함께) 등 다양한 영역에서 사용된다.
- Selenium (다양한 언어) : 웹 애플리케이션 UI 자동화 테스트
- 예시 (PyTest):
# PyTest 자동화 테스트 예시
import pytest
# 테스트할 함수
def calculate_discount(price, quantity):
if quantity >= 10:
return price * 0.9 # 10개 이상 구매 시 10% 할인
elif quantity >= 5:
return price * 0.95 # 5개 이상 구매 시 5% 할인
return price # 할인 없음
# 파라미터화된 테스트
@pytest.mark.parametrize("price, quantity, expected", [
(100, 1, 100), # 할인 없음
(100, 5, 95), # 5% 할인
(100, 10, 90), # 10% 할인
(100, 20, 90), # 10% 할인 (최대)
])
def test_calculate_discount(price, quantity, expected):
assert calculate_discount(price, quantity) == expected
# 예외 테스트
def divide(a, b):
return a / b
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(5, 0)
# 픽스처 활용 예시
@pytest.fixture
def sample_db():
# 테스트용 임시 DB 생성
db = {"users": [{"id": 1, "name": "Test User"}]}
yield db
# 테스트 후 정리 작업
def test_with_db(sample_db):
# 픽스처로 생성된 DB 사용
assert len(sample_db["users"]) == 1
assert sample_db["users"][0]["name"] == "Test User"
2-7. 실제 적용 예시
1. [온라인 쇼핑몰 결제 시스템 테스트]
온라인 쇼핑몰 결제 시스템을 테스트하는 경우:
블랙박스 테스트:
- 동등 분할: 결제 금액을 범위별로 나누어 테스트 (1만원 미만, 1~10만원, 10만원 이상)
- 경계값 분석: 무료배송 기준인 5만원 경계에서 49,999원, 50,000원, 50,001원 결제 테스트
- 결정 테이블: 멤버십 등급(일반/VIP) × 결제 방법(카드/현금) × 할인쿠폰(유/무) 조합 테스트
화이트박스 테스트:
- 분기 커버리지: 결제 프로세스의 모든 조건 분기(if-else) 테스트
- 루프 테스트: 장바구니 상품 개수에 따른 반복 처리 검증
2. [PyTest를 활용한 사용자 인증 시스템 테스트]
# 사용자 인증 모듈
class UserAuth:
def __init__(self):
self.users = {"admin": "admin123", "user1": "pass123"}
def authenticate(self, username, password):
if username not in self.users:
return "USER_NOT_FOUND"
if self.users[username] != password:
return "INVALID_PASSWORD"
return "SUCCESS"
# PyTest로 테스트 자동화
import pytest
@pytest.fixture
def auth_system():
return UserAuth()
def test_successful_login(auth_system):
result = auth_system.authenticate("admin", "admin123")
assert result == "SUCCESS"
def test_invalid_password(auth_system):
result = auth_system.authenticate("admin", "wrong_pass")
assert result == "INVALID_PASSWORD"
def test_user_not_found(auth_system):
result = auth_system.authenticate("nonexistent", "anypass")
assert result == "USER_NOT_FOUND"
# 파라미터화 테스트
@pytest.mark.parametrize("username, password, expected", [
("admin", "admin123", "SUCCESS"),
("admin", "wrong", "INVALID_PASSWORD"),
("unknown", "anypass", "USER_NOT_FOUND"),
("user1", "pass123", "SUCCESS"),
])
def test_authentication_scenarios(auth_system, username, password, expected):
assert auth_system.authenticate(username, password) == expected
728x90
'Develop > SW공학' 카테고리의 다른 글
형상관리 개요, Git 개요를 알아보자. (0) | 2025.04.14 |
---|---|
객체 설계를 알아보자. (0) | 2025.04.14 |
시스템 설계를 알아보자. (2) | 2025.04.14 |
SW공학 - 요구사항 분석 알아보자. (1) | 2025.04.11 |