Engineering 챕터 4 약 6분

소프트웨어 공학 — 4강: 소프트웨어 테스팅과 품질 보증

O
OIYO 편집부 기여자
4/4

소프트웨어 테스팅의 기초

소프트웨어 테스팅:
→ 결함 발견·품질 평가를 위한 실행 기반 검증
→ 검증 (Verification): 올바르게 만들고 있는가? (사양 충족)
→ 확인 (Validation): 올바른 것을 만들고 있는가? (요구 충족)

테스팅의 7가지 원칙 (ISTQB):
→ 테스팅은 결함 존재를 보여주지만 결함 없음을 증명 불가
→ 완전한 테스팅은 불가능 (경우의 수 무한)
→ 조기 테스팅: 결함 발견이 늦을수록 비용 급증 (1:10:100 규칙)
→ 결함 군집화 (Defect Clustering): 결함은 소수 모듈에 집중
→ 살충제 역설: 같은 테스트 반복 → 새 결함 발견 안됨
→ 테스팅은 상황 의존적
→ 오류 부재 오류 (Absence-of-Errors Fallacy): 버그 없어도 사용자 요구 미충족 가능

결함 용어:
→ 에러 (Error): 사람의 실수 (개발자 오타·잘못된 이해)
→ 결함 (Defect/Bug): 코드 내 문제
→ 오류/장애 (Failure): 실행 시 발생하는 잘못된 동작

테스팅의 비용:
→ 요구사항 단계에서 발견: 비용 1
→ 설계 단계: 비용 5
→ 구현 단계: 비용 10
→ 테스팅 단계: 비용 20
→ 운영 후: 비용 100~200

테스팅 수준

단위 테스트 (Unit Testing):
→ 개별 함수·클래스·모듈을 독립적으로 테스트
→ 가장 빠르고 저렴·결함 위치 파악 쉬움
→ 목(Mock)·스텁(Stub): 의존성 대체 (격리 테스트)
→ xUnit 프레임워크: JUnit·PyTest·Jest

통합 테스트 (Integration Testing):
→ 여러 모듈을 결합하여 인터페이스 테스트
→ 빅뱅 통합: 전체 한 번에 통합 (결함 위치 파악 어려움)
→ 점진적 통합:
  하향식 (Top-Down): 상위 모듈 먼저·스텁 필요
  상향식 (Bottom-Up): 하위 먼저·드라이버 필요
  샌드위치 (Sandwich): 혼합

시스템 테스트 (System Testing):
→ 전체 시스템을 사용자 관점에서 테스트
→ 기능적 요구사항 + 비기능적 요구사항 모두 테스트
→ 성능·보안·호환성·신뢰성 테스트 포함

인수 테스트 (Acceptance Testing):
→ 사용자·고객이 요구사항 충족 여부 확인
→ 사용자 인수 테스트 (UAT)·운영 인수 테스트 (OAT)
→ 알파 테스트: 개발 환경에서 사용자 테스트
→ 베타 테스트: 실 환경에서 선택된 사용자 테스트

회귀 테스트 (Regression Testing):
→ 변경 후 기존 기능이 정상 동작하는지 확인
→ 자동화가 필수 (변경마다 전체 재실행 부담)
→ 테스트 스위트 관리: 우선순위·선택 전략

블랙박스와 화이트박스 테스트

블랙박스 테스트 (Black-Box):
→ 내부 구현 무관·입출력 기반
→ 사용 시점: 시스템·인수 테스트

동등 분류 (Equivalence Partitioning):
→ 입력을 유사한 처리를 받는 그룹으로 분류
→ 각 그룹에서 대표값만 테스트 (경우의 수 감소)
→ 유효 분류 + 무효 분류 모두 테스트

경계값 분석 (Boundary Value Analysis):
→ 경계 근처에서 결함 발생 빈도 높음
→ 최솟값·최솟값+1·최솟값-1·최댓값·최댓값-1 테스트
→ 예: 입력 범위 1~100: 0, 1, 2, 99, 100, 101 테스트

결정 테이블 테스트:
→ 여러 조건 조합과 그에 따른 행동 표로 정리
→ 복잡한 비즈니스 규칙 테스트에 효과적

상태 전이 테스트:
→ 시스템 상태 변화와 이벤트 기반 테스트
→ 자동화기기·프로토콜·워크플로우 테스트

화이트박스 테스트 (White-Box):
→ 내부 코드 구조 기반
→ 사용 시점: 단위·통합 테스트

커버리지 기준:
→ 구문 커버리지 (Statement Coverage): 모든 구문 실행 (C0)
→ 분기 커버리지 (Branch Coverage): 모든 조건 분기 (C1)
→ 조건 커버리지 (Condition Coverage): 각 조건 T/F 모두
→ 경로 커버리지 (Path Coverage): 모든 실행 경로 (완전 테스트)
→ MC/DC: 조건 독립적 영향 검증 (안전 임계 시스템)

TDD와 자동화 테스팅

테스트 주도 개발 (TDD):
→ 켄트 벡 (Kent Beck): 익스트림 프로그래밍(XP) 실천
→ Red-Green-Refactor 사이클:
  Red: 실패하는 테스트 먼저 작성
  Green: 테스트를 통과하는 최소 코드 작성
  Refactor: 코드 구조 개선 (테스트 통과 유지)

TDD의 장점:
→ 명확한 요구사항 사전 정의
→ 회귀 테스트 자동 보장
→ 설계 개선: 테스트 가능한 설계 → 결합도 낮춤
→ 즉각적 피드백

BDD (Behavior Driven Development):
→ 사용자 행동 관점의 시나리오 기반 테스트
→ Given-When-Then 형식
  "주어진 조건 / 사용자가 ~할 때 / ~가 발생해야 한다"
→ 비기술자(PO·QA)도 이해 가능한 테스트 명세
→ Cucumber·SpecFlow·Behave

테스트 자동화:
→ 자동화 피라미드 (Fowler):
  기반: 단위 테스트 (많이·빠름·저렴)
  중간: 통합 테스트 (중간)
  상단: E2E 테스트 (적게·느림·비쌈)
→ 빠른 피드백 루프 구축

테스트 도구:
→ 단위: JUnit·PyTest·Jest·RSpec
→ API 테스트: Postman·REST Assured
→ E2E: Selenium·Playwright·Cypress
→ 성능: JMeter·Locust·k6
→ 보안: OWASP ZAP·Burp Suite

CI/CD와 품질 지표

CI/CD (지속적 통합·배포):

지속적 통합 (CI, Continuous Integration):
→ 모든 개발자 코드를 자주 공유 저장소에 통합
→ 자동 빌드·테스트 실행으로 통합 문제 조기 발견
→ 도구: Jenkins·GitHub Actions·CircleCI·GitLab CI

지속적 배포 (CD, Continuous Deployment/Delivery):
→ Continuous Delivery: 프로덕션 배포 가능 상태 유지
→ Continuous Deployment: 자동으로 프로덕션 배포

CI/CD 파이프라인 단계:
→ 코드 커밋 → 빌드 → 단위 테스트 → 정적 분석
→ 통합 테스트 → 성능 테스트 → 보안 스캔
→ 스테이징 배포 → 인수 테스트 → 프로덕션 배포

코드 품질 지표:
→ 커버리지 (Code Coverage): 테스트가 실행하는 코드 비율
→ 순환 복잡도 (Cyclomatic Complexity): 독립 경로 수 (1~10 권장)
→ 코드 중복도: DRY 원칙 위반 정도
→ 기술 부채 (Technical Debt): 빠른 개발로 쌓인 품질 문제
→ MTTR (Mean Time To Recovery): 장애 복구 평균 시간
→ MTBF (Mean Time Between Failures): 평균 장애 간격

소프트웨어 품질 모델 (ISO 25010):
→ 기능 적합성·성능 효율성·호환성·사용성
→ 신뢰성·보안성·유지 보수성·이식성

코드 리뷰 (Code Review):
→ 동료 리뷰로 결함 조기 발견 (정적 테스팅)
→ 인스펙션·워크스루·리뷰 세 가지 형태
→ Pull Request 기반 리뷰: 깃허브·깃랩 표준 방식
→ 리뷰 기준: 기능 정확성·성능·보안·가독성·테스트 충분성

자주 묻는 질문

Q. TDD가 좋다는 것은 알겠는데 실제 업무에서 적용하기 어려운 이유는 무엇인가요? A. TDD 도입의 어려움은 몇 가지 원인이 있습니다. 첫째, 초기 학습 비용입니다. TDD에 익숙하지 않으면 테스트를 먼저 작성하는 것 자체가 낯설고, 테스트 가능한 코드를 작성하는 능력을 키우는 데 시간이 필요합니다. 둘째, 단기 속도 감소 체감입니다. 처음에는 테스트를 먼저 쓰고 코드를 두 번 작성하는 것처럼 느껴져 생산성이 떨어진다고 느낄 수 있습니다. 장기적으로는 디버깅 시간·회귀 결함 감소로 더 빠르지만 단기 효과가 즉각 보이지 않습니다. 셋째, 레거시 코드 문제입니다. 테스트 가능성을 고려하지 않고 만들어진 기존 코드에 TDD를 적용하려면 리팩토링이 선행되어야 합니다. 현실적으로 점진적 도입을 권장합니다. 새로운 기능부터, 또는 버그를 수정할 때 해당 버그를 재현하는 테스트 먼저 작성하는 방식으로 시작해 보세요.

Q. 100% 코드 커버리지를 달성하면 버그가 없다고 볼 수 있나요? A. 아닙니다. 코드 커버리지는 테스트가 실행한 코드 줄의 비율이지, 모든 경우를 테스트했다는 의미가 아닙니다. 100% 커버리지를 달성해도 많은 버그가 남아있을 수 있습니다. 예를 들어 a + b를 반환해야 하는 함수가 a - b를 반환하더라도 a=5, b=0으로만 테스트하면 결과값이 5로 같아 커버리지는 100%지만 버그를 잡지 못합니다. 또한 구문 커버리지 100%라도 모든 조건·경계값·예외 상황을 테스트한 것이 아닙니다. 커버리지는 테스트가 얼마나 충분한지의 하한선 지표이지 품질 보증이 아닙니다. 중요한 것은 의미 있는 테스트 케이스를 작성하는 것입니다. 특히 동등 분류·경계값 분석·결정 테이블을 활용한 테스트 케이스 설계가 단순 커버리지 숫자보다 훨씬 중요합니다.

O

OIYO 편집부

Content Editor

지식 인큐베이터이자 전문 콘텐츠 크리에이터. 경영, 경제, 법률 및 실생활에 유용한 실무/자격증 중심의 깊이 있는 정보를 연구하고 공유합니다.