소프트웨어공학 — 3강: 테스팅·품질 보증·유지보수
소프트웨어 테스팅 개요
테스팅의 목적:
→ 결함(Defect) 발견 — 소프트웨어가 완벽하다는 증명 아님
→ 품질 신뢰도 향상
→ 릴리즈 위험 감소
테스팅의 한계:
→ 완전한 테스트는 불가능 (경우의 수 무한)
→ 테스팅은 결함 부재를 증명할 수 없음 (다이크스트라)
→ 결함 군집: 소수의 모듈에 결함 집중 (파레토 법칙)
결함 vs 에러 vs 실패:
→ 에러(Error): 개발자 실수
→ 결함(Defect/Bug): 코드 내 오류
→ 실패(Failure): 실행 시 잘못된 결과
→ 조기 발견 비용: 요구사항 단계 vs 운영 단계 = 1:100 이상
테스트 유형 분류:
→ 화이트박스: 내부 구조 기반
→ 블랙박스: 외부 명세 기반
→ 그레이박스: 일부 내부 정보 활용
화이트박스 테스팅
화이트박스 테스팅 (White-Box / Structural Testing):
→ 소스 코드 내부 구조 기반
→ 개발자가 주로 수행
→ 단위 테스트에서 주로 활용
코드 커버리지:
구문 커버리지 (Statement Coverage):
→ 모든 실행 가능 구문 최소 1번 실행
→ 가장 약한 커버리지
분기 커버리지 (Branch Coverage):
→ 모든 조건문의 참·거짓 분기 모두 실행
→ 구문 커버리지보다 강함
조건 커버리지 (Condition Coverage):
→ 각 조건의 참·거짓 조합 실행
경로 커버리지 (Path Coverage):
→ 모든 실행 경로 실행
→ 가장 강하지만 경로 수 폭발 문제
기초 경로 테스팅 (Basis Path):
→ McCabe 순환 복잡도: V(G) = E - N + 2
(E: 에지, N: 노드)
→ V(G) = 독립 경로의 최소 수
→ V(G) = 조건문 수 + 1
루프 테스팅:
→ 루프 0번, 1번, 2번, 최대, 최대-1번 실행
블랙박스 테스팅
블랙박스 테스팅 (Black-Box / Behavioral Testing):
→ 명세 기반 — 내부 구조 모름
→ QA 엔지니어·테스터가 주로 수행
→ 시스템·인수 테스트에 적합
동등 분할 (Equivalence Partitioning):
→ 입력을 동등 클래스로 분할
→ 각 클래스에서 1개 대표 값 선택
→ 유효 클래스 + 무효 클래스
→ 예: 나이 [1~120]: 유효(50), 무효(-1, 0, 121)
경계값 분석 (Boundary Value Analysis):
→ 경계 근처에서 결함 다발
→ 경계값, 경계-1, 경계+1 테스트
→ 동등 분할의 보완
결정 테이블 (Decision Table):
→ 조건 조합별 행동 정의
→ 복잡한 비즈니스 로직에 유용
상태 전이 테스팅 (State Transition):
→ 상태 다이어그램 기반
→ 로그인·주문 흐름 등에 적합
유즈케이스 테스팅:
→ 사용자 시나리오 기반
→ 주 흐름 + 대안 흐름 + 예외 흐름
탐색적 테스팅 (Exploratory Testing):
→ 명세 없이 직관·경험 기반
→ 자동화하기 어려운 영역 보완
테스트 레벨과 TDD
테스트 레벨:
단위 테스트 (Unit Test):
→ 함수·클래스·모듈 단위
→ 빠름, 격리(Mock/Stub 사용)
→ 도구: JUnit, pytest, Jest
통합 테스트 (Integration Test):
→ 모듈 간 인터페이스 검증
→ 빅뱅: 모두 통합 후 테스트
→ 점진적: 상향식(Bottom-Up) / 하향식(Top-Down)
시스템 테스트:
→ 전체 시스템 기능·비기능 검증
→ 성능, 보안, 부하, 복구 테스트 포함
인수 테스트 (Acceptance Test):
→ 고객이 요구사항 충족 여부 검증
→ UAT(User Acceptance Test)
→ 알파 테스트: 개발 환경에서 사용자
→ 베타 테스트: 실제 환경에서 사용자
TDD (Test-Driven Development):
→ 테스트 먼저 작성 → 코드 작성 → 리팩토링
→ Red-Green-Refactor 사이클
→ 장점: 설계 품질 향상, 회귀 결함 방지
→ 단점: 초기 개발 속도 저하
BDD (Behavior-Driven Development):
→ Given-When-Then 시나리오
→ 비개발자도 이해 가능한 테스트 명세
→ 도구: Cucumber, Behave
CI/CD와 소프트웨어 품질
CI/CD:
CI (Continuous Integration):
→ 자주 코드 통합 (최소 1일 1회)
→ 자동 빌드·테스트
→ 빠른 결함 발견
→ 도구: GitHub Actions, Jenkins, CircleCI
CD (Continuous Delivery):
→ 릴리즈 준비 상태 항상 유지
→ 수동 배포 승인 포함 가능
CD (Continuous Deployment):
→ 통과 시 자동 운영 배포
→ 기능 플래그(Feature Flag)로 점진적 롤아웃
소프트웨어 품질 속성 (ISO 25010):
기능 적합성:
→ 기능 완전성, 정확성, 적절성
성능 효율성:
→ 시간 효율성, 자원 활용, 용량
호환성:
→ 공존성, 상호 운용성
사용성:
→ 학습 용이성, 운영 편의성
신뢰성:
→ 성숙도, 가용성, 내결함성, 복구성
보안성:
→ 기밀성, 무결성, 부인 방지, 인증
유지 보수성:
→ 모듈성, 재사용성, 분석성, 수정성, 시험성
이식성:
→ 적응성, 설치성, 대체성
소프트웨어 유지보수
유지보수 유형 (Lientz & Swanson):
수정 유지보수 (Corrective):
→ 결함 수정
→ 전체 유지보수의 약 20%
적응 유지보수 (Adaptive):
→ 환경 변화(OS, DB, HW) 대응
→ 전체의 약 25%
완전 유지보수 (Perfective):
→ 기능 추가·개선 (가장 많음 ~50%)
→ 성능 최적화 포함
예방 유지보수 (Preventive):
→ 미래 문제 예방, 리팩토링
→ 전체의 약 5%
레거시 시스템 전략:
→ 동결(Freeze): 그대로 유지 (안정적, 변경 불가)
→ 보수(Maintain): 최소 수정
→ 재공학(Re-engineering): 현대화
→ 재작성(Rewrite): 완전 재개발
리팩토링 (Refactoring):
→ 외부 동작 변경 없이 내부 구조 개선
→ 코드 스멜(Code Smell) 제거
- 긴 메서드, 중복 코드, 거대 클래스
→ 기술 부채(Technical Debt) 상환
소프트웨어 유지보수 비용:
→ 개발 비용의 2~4배 (전체 생명주기)
→ 변경 이해에 50% 이상 소요
→ 문서화와 가독성이 유지보수 비용에 결정적
자주 묻는 질문
Q. 테스트 커버리지 100%면 버그가 없는 건가요? A. 아닙니다. 커버리지 100%는 모든 코드 라인이 실행되었다는 의미이지 모든 버그가 없다는 의미가 아닙니다. 예를 들어 구문 커버리지 100%라도 잘못된 논리(off-by-one 에러, 경계값 오류)는 잡지 못할 수 있습니다. 커버리지는 테스트의 충분 조건이 아닌 최소 기준으로 이해해야 합니다. 구글·마이크로소프트 같은 회사도 80% 정도를 실용적인 목표로 삼습니다.
Q. TDD를 실무에서 적용하기 어렵다는 이유는 무엇인가요? A. 가장 큰 이유는 초기 개발 속도 저하와 팀의 습관 변화 저항입니다. UI 의존성이 높은 코드, 레거시 시스템에 통합하는 코드, 외부 API 연동 코드에서는 TDD 적용이 특히 어렵습니다. 하지만 핵심 비즈니스 로직이나 알고리즘에서는 TDD가 설계 품질을 크게 높입니다. 처음에는 테스트 작성이 익숙하지 않더라도, 회귀 테스트로 기존 기능이 깨지지 않음을 보장한다는 장점이 점차 속도 향상으로 이어집니다.
O
OIYO 편집부
Content Editor지식 인큐베이터이자 전문 콘텐츠 크리에이터. 경영, 경제, 법률 및 실생활에 유용한 실무/자격증 중심의 깊이 있는 정보를 연구하고 공유합니다.