본문 바로가기
학교공부/[소프트웨어공학]

[소프트웨어공학] - 테스팅

by 윈디개 2026. 6. 8.

1. 테스트 개념

소프트웨어 개발은 사람이 중심이 되는 활동이기 때문에 오류가 발생하기 쉽다. 따라서 개발 과정에서는 오류를 줄이기 위한 다양한 활동이 필요하다. 오류는 사전에 방지하기 위한 방법으로는 코드 인스펙션과 정적 분석이 있으며, 이미 존재하는 결함을 식별하고 제거하기 위한 방법으로는 테스트와 디버깅이 있다.

 

테스트는 시험할 소프트웨어에 테스트 케이스를 입력하여 실행한 뒤, 시스템의 동작이 예상한 결과가 일치하는지 확인하는 과정이다. 즉, 단순히 프로그램을 실행해 보는 것이 아니라, 의도한 조건에서 올바른 결과가 나오는지 체계적으로 검증하는 활동이다.

1.1. 검증과 확인

소프트웨어 테스트와 관련하여 검증과 확인이 있다.

 

검증은 "제품을 올바르게 구축하고 있는가?"를 확인하는 활동이다. 즉, 개발 과정에서 설계나 명세에 맞게 소프트웨어가 만들어지고 있는지를 점검하는 것이다. 반면 확인은 "올바른 제품을 만들고 있는가?"를 확인하는 활동이다. 이는 최종 소프트웨어가 실제 사용자의 요구를 만족하는지를 보는 것이다.

 

다음 그림을 한 번 확인해보자.

리뷰 과정에서는 각 개발 산출물이 이전 단계의 기준에 맞게 작성되었는지를 점검한다. 예를 들어 분석 명세서는 사용자 요구를 기준으로 검토하고, 설계는 분석 명세서를 기준으로 검토하며, 코드는 설계 명세서를 기준으로 검토한다. 이처럼 개발 과정의 산출물이 앞 단계의 산출물과 일치하는지 확인하는 활동은 검증이다.


2. 테스트 기초

테스트를 이해하기 위해서는 버그, 오류, 결함, 고장의 차이를 알아야 한다. 각 개념은 다음과 같다.

  • 버그: 문제, 결함 또는 난이도를 나타내는 데 일반적으로 사용되는 용어
  • 오류: 개발자가 잘못하여 설계나 코딩에 실수한 것
  • 결함: 오류의 결과로 인해 시스템이 고장을 일으킬 수 있는 상태
  • 고장: 시스템이 원하는 작업을 수행할 수 없는 상황이나 현상

2.1. 테스트 원리

테스트는 오류를 발견하기 위해 프로그램을 실행하는 활동이다. 따라서 테스트의 목적은 프로그램이 정상적으로 동작한다는 것을 증명하는 것이 아니라, 숨어 있는 오류를 찾아내는 데 있다.

 

완벽한 테스트는 현실적으로 불가능하다. 모든 입력값과 모든 실행 경로를 테스트하는 것은 대부분의 시스템에서 불가능하기 때문이다. 따라서 효과적인 테스트 케이스를 선택하느 능력이 중요하다.

 

또한 테스트는 창조적이고 어려운 작업이다. 단순히 정상적인 입력만 넣는 것이 아니라, 오류가 발생할 가능성이 높은 상황을 예측하고 다양한 조건을 설계해야 한다. 그리고 테스트는 구현한 개발자와 독립된 팀이 수행하는 것이 바람직하다. 개발자는 자신이 작성한 코드의 문제를 놓치기 쉽기 때문에 독립적인 관점에서 테스트하는 것이 결함 발견에 유리하다.

 

2.2. 테스트 작업 과정

테스트 작업은 일반적으로 다음과 같은 순서로 진행된다.

  1. 테스트에 의해 무엇을 점검할지 정한다.
  2. 테스트 방법을 결정한다.
  3. 테스트 케이스를 개발한다.
  4. 각 테스트에 대해 예상되는 올바른 결과를 작성한다
  5. 테스트 케이스를 실제 프로그램에 적용하여 실행시킨다.

테스트 단계와 소프트웨어 개발 단계의 관계를 다음 그림을 통해 확인해보자.

각 개발 단계에 상응하는 테스트가 존재한다.

  • 범위와 목표 설정 단계 - 인수 테스트(최종적으로 사용자가 시스템을 받아들일 수 있는지 확인하는 테스트)
  • 요구 분석 - 인수 테스트, 시스템 테스트(시스템 전체가 요구사항을 만족하는지 확인하는 테스트)
  • 구조 설계 - 통합 테스트(모듈 간 구조와 인터페이스 테스트)
  • 프로그램 설계 - 단위 테스트
  • 코딩 - 단위테스트

또한 테스트는 한 번 수행하고 끝나는 활동이 아니라 다른 테스트 결과와 연결되어 반복적으로 수행될 수 있다. 예를 들어 단위 테스트에서 오류를 수정한 뒤에는 해당 모듈과 관련된 통합 테스트를 다시 수행해야 할 수 있고, 통합 테스트에서 수정이 발생하면 시스템 테스트에도 영향을 줄 수 있다. 이처럼 수정된 기능이 기존 기능에 영향을 주지 않았는지 확인하기 위해 리그레션 테스트가 필요하다.

 

리그레션 테스트는 변경이나 수정 이후 기존에 정상적으로 동작하던 기능이 여전히 올바르게 동작하는지를 확인하는 테스트이다. 따라서 단위 테스트, 통합 테스트, 시스템 테스트 이후 수정이 발생하면 리그레션 테스트를 통해 기존 기능의 안정성을 다시 확인해야 한다.

2.3. 테스트 케이스

테스트 케이스는 결함을 검사할 수 있는 입력과 조건을 의미한다. 테스트 케이스에는 시험조건, 테스트 데이터, 예상 결과가 포함된다.

 

다음 그림을 한 번 살펴보면 테스트 케이스를 이해할 수 있다.

2.4. 블랙박스 테스트

블랙박스 테스트는 프로그램의 내부 구조나 코드 경로를 보지 않고, 기능이나 성능이 요구사항에 맞게 동작하는지를 확인하는 테스트 방법이다. 즉, 소프트웨어를 하나의 검은 상자로 보고 입력과 출력만을 기준으로 테스트하는 것이다.

 

블랙박스 테스트는 요구사항 명세나 기능 사양을 기반으로 수행된다. 대표적인 기법으로는 동등 분할 기법, 경계값 분석, 원인-결과 그래프, 결정 테이블 등이 있다.

2.4.1. 동등 분할 기법

동등 분할 기법은 시스템의 동작이 같을 것으로 예상되는 입력값들을 하나의 동등 클래스로 나누고, 각 클래스에서 대표값을 선택하여 테스트하는 방법이다.

 

예를 들어 나이에 따라 요금이 달라지는 시스템이 있다고 하자. 17세 이하, 18세 이상 60세 이하, 61세 이상으로 동작이 나뉜다면 각각을 동등 클래스로 볼 수 있다. 이때 모든 나이를 테스트하지 않고 각 구간에서 대표값을 선택하여 테스트하면 효율적으로 결함을 찾을 수 있다.

2.4.2. 경계값 분석

경계값 분석은 동등 클래스의 경계에서 오류가 자주 발생한다는 점에 주목한 테스트 기법이다. 실제 프로그램에서는 조건문 처리 과정에서 경계값을 잘못 다루는 경우가 많기 때문에 진행한다.

 

예를 들어 입력값 조건이 min <= X <= max라면 테스트 케이스로 min-1, min, min+1, max-1, max, max+1을 선택할 수 있다. 이를 통해 경계값 안쪽, 경계값, 경계 바깥쪽을 모두 확인할 수 있다.

2.4.3. 원인-결과 그래프

원인과 결과 그래프는 입력 조건의 조합을 체계적으로 선택하기 위한 테스트 기법이다. 여기서 원인은 입력 조건을 의미하고, 결과는 출력 조건을 의미한다. 원인과 결과 사이의 관계는 AND, OR, NOT과 같은 논리 기호로 표현된다.

 

이 기법은 입력 조건이 여러 개이고, 조건들의 조합에 따라 결과가 달라지는 시스템을 테스트할 때 유용하다.

2.4.4. 결정 테이블

결정 테이블은 여러 조건의 조합과 그에 따른 결과를 표 형태로 정리하는 방법이다. 각 조건이 참인지 거짓인지에 따라 가능한 조합을 나열하고, 각 조합에서 어떤 결과가 발생해야 하는지 명확하게 표현한다.

 

결정 테이블은 조건이 복잡하게 얽혀 있는 업무 규칙이나 기능을 테스트할 때 유용하다. 특히 모든 조건 조합을 빠뜨리지 않고 확인할 수 있다는 장점이 있다.

 

다음 예시를 한 번 살펴보자.

세로의 c1 ~ c4는 조건을 나타내고, e1 ~ e5는 결과 또는 동작을 나타낸다. 가로의 1 ~ 5는 각각 하나의 규칙 또는 테스트 케이스 조합을 의미한다.

 

이때 1은 true, 0은 false, x는 don’t care로, 참이든 거짓이든 상관없다는 의미이다.

 

표를 해석할 때는 각 열을 세로로 읽으면 된다. 예를 들어 1번 규칙에서는 c1과 c2가 false이면 c3와 c4의 값은 상관없이 결과가 e1이 된다. 2번 규칙에서는 c1이 true이고 c3가 false이면 c2와 c4는 상관없이 결과가 e2가 된다.

 

이처럼 결정 테이블은 여러 조건이 조합될 때 각 조건 조합에 따른 결과를 명확하게 정리할 수 있으며, 이를 바탕으로 테스트 케이스를 체계적으로 도출할 수 있다.

2.5. 화이트박스 테스트

화이트박스 테스트는 프로그램 내부 구조를 알고 있는 상태에서 모듈의 논리적인 구조를 체계적으로 점검하는 테스트 방법이다. 구조적 테스트라고도 한다.

 

화이트박스 테스트는 먼저 원시 코드를 분석하여 애플리케이션의 구조와 논리 흐름을 이해하는 것에서 시작한다.이후 어떤 커버리지 기준을 사용할지 정하고, 각 경로나 조건을 실행할 수 있는 테스트 데이터를 준비한다.

2.5.1. 논리 흐름도

논리 흐름도는 모듈 내부의 제어 흐름을 그래프로 표현한 것이다. 프로그램의 실행 흐름을 노드와 간선으로 나타내어 어떤 조건에서 어떤 경로로 실행이 진행되는지 확인할 수 있다.

 

화이트박스 테스트에서는 이러한 논리 흐름도를 바탕으로 어떤 문장, 분기, 경로가 테스트되었는지를 확인한다.

2.5.2. 검증기준

검증 기준(커버리지)을  정하기 위한 기준은 여러 가지 방법이 있는데 다음과 같다.

 

  • 문장 커버리지
    • 프로그램의 각 문장이 적어도 한 번 이상 실행되었는지를 기준으로 삼는 테스트 방법
    • 문장 커버리지만 만족한다고 해서 모든 조건이 충분히 테스트되었다고 볼 수는 없다.
  • 분기 커버리지
    • 조건문의 true와 false 분기가 모두 실행되었는지를 확인하는 기준이다. 문장 커버리지보다 더 강한 기준이다.
  • 경로 커버리지
    • 프로그램에서 가능한 모든 실행 경로를 테스트하는 기준이다. 가장 강력한 테스트 기준 중 하나이지만, 프로그램이 복잡해질수록 가능한 경로의 수가 급격히 증가하기 때문에 테스트가 어려워진다.

따라서 실제 테스트에서는 문장 커버리지, 분기 커버리지, 주요 경로 커버리지 등을 적절히 조합하여 사용한다.

2.6. 상태 기반 테스팅

상태 기반 테스팅은 시스템의 현재 상태에 따라 동일한 입력이라도 다른 결과가 나올 수 있는 시스템을 대상으로 하는 테스트 방법이다. 즉, 시스템의 동작이 단순히 입력값만으로 결정되는 것이 아니라 이전 입력이나 현재 상태의 영향을 받는 경우에 사용한다.

 

예를 들어 예금 계정 시스템이 있다. 계좌가 정상 상태인지, 잔액 부족 상태인지, 정지 상태인지에 따라 같은 출금 요청이라도 결과가 달라질 수 있다.

 

상태 모델은 상태, 트랜지션, 이벤트, 액션으로 구성되는데 각각은 다음과 같이 정의된다.

  • 상태: 시스템의 과거 입력에 대한 영향을 표시
  • 트랜지션: 이벤트에 대한 반응으로 시스템이 하나의 상태에서 다른 상태로 이동하는 과정을 의미
  • 이벤트: 시스템에 대한 입력
  • 액션: 이벤트에 대한 출력

상태 기반 테스트에서는 상태 전이가 올바르게 이루어지는지, 특정 상태에서 허용되지 않는 이벤트가 들어왔을 때 적절히 처리되는지 등을 확인한다.

2.7. 통합 테스트

통합 테스트는 여러 단위 모듈을 결합한 뒤, 모듈 간 인터페이스가 올바르게 동작하는지 확인하는 테스트이다. 단위 테스트가 개별 모듈 내부의 오류를 찾는 데 초점을 둔다면, 통합 테스트는 모듈과 모듈이 연결될 때 발생할 수 있는 문제를 찾는 데 초점을 둔다.

 

통합 테스트에서는 다음과 같은 용어가 사용된다.

  • 드라이버: 테스트 대상 모듈을 호출하는 간이 소프트웨어
  • 스텁: 테스트 대상 모듈이 호출하는 하위 모듈을 대신하는 간이 소프트웨어

통합 방식에는 총 빅뱅, 하향식, 상향식, 연쇄식 4가지가 존재한다.

2.7.1. 빅뱅 통합

빅뱅 통합은 모든 모듈을 한 번에 모아 통합하는 방식이다. 일정 관리가 비교적 쉽고, 통합을 위해 스텁을 구성할 필요가 적다는 장점이 있다.

 

하지만 실제 입출력 모듈은 보통 하위에 있기 때문에 초기 테스트에서 실제 데이터 흐름을 확인하기 어렵다. 또한 하위 기능이 늦게 구현되면 중요한 기능의 실제 동작 확인이 뒤로 밀릴 수 있다.

2.7.2. 하향식 통합

하향식 통합은 시스템 구조상 최상위 모듈부터 시작하여 하위 모듈로 내려가며 통합하는 방식이다. 중요한 상위 모듈의 인터페이스를 조기에 테스트할 수 있고, 스텁을 이용하여 시스템의 전체 모습을 비교적 빠르게 확인할 수 있다는 장점이 있다.

 

하지만 실제 입출력 모듈은 보통 하위에 있기 때문에 초기 테스트에서 실제 데이터 흐름을 확인하기 어렵다. 또한 하위 기능이 늦게 구현되면 중요한 기능의 실제 동작 확인이 뒤로 밀릴 수 있다.

2.7.3. 상향식 통합

상향식 통합은 시스템 구조상 최하위 모듈부터 시작하여 상위 모듈로 올라가며 통합하는 방식이다. 하위 모듈을 충분히 테스트할 수 있고, 점진적으로 통합하기 때문에 오류 발견이 비교적 쉽다.

 

그러나 초기에 시스템의 전체 구조나 뼈대가 잘 보이지 않는다는 단점이 있다. 또한 상위층의 중요한 인터페이스는 마지막 단계에 가서야 확인할 수 있기 때문에, 사용자나 의뢰자가 시스템을 조기에 시험해 보기 어렵다.

2.7.4. 연쇄식 통합

연쇄식 통합은 특정 기능을 수행하는 최소 단위의 모듈 집합, 즉 thread를 중심으로 통합하는 방식이다. 입력부터 출력까지 하나의 기본 기능을 수행할 수 있는 모듈들을 먼저 통합한다.

 

이 방식은 중요한 기능부터 개발하고 테스트할 수 있으며, 초기부터 시스템의 골격을 형성할 수 있다는 장점이 있다. 또한 사용자 의견을 빠르게 확인할 수 있어 점진적 개발에 적합하다.

2.8. 시스템 및 인수 테스팅

시스템 테스트는 컴포넌트 통합 후 전체 시스템이 요구사항을 만족하는지 확인하는 테스트이다. 이 단계에서는 기능뿐만 아니라 성능 보안, 사용성, 설치 가능성 등 다양한 측면을 확인한다.

 

대표적인 시스템 테스트에는 기능 테스트, 성능 테스트, 보안 테스트, 사용성 테스트, 인수 테스트 설치 테스트가 있다.

2.8.1. 기능 테스트

기능 테스트는 기능적 요구사항과 실제 시스템 동작 사이의 차이를 발견하기 위한 테스트이다. 사용자의 요구와 직접 관련된 기능을 대상으로 하며, 오류가 발생할 가능성이 높은 기능을 우선적으로 선정한다.

 

기능 테스트 케이스는 사용 사례 모델을 검토하여 작성할 수 있다. 일반적인 정상 사례뿐만 아니라 예외적인 사례도 함께 테스트해야 한다. 예를 들어 회원가입 기능에서는 정상적인 회원가입뿐만 아니라 이미 존재하는 아이디, 잘못된 이메일 형식, 비밀번호 미충족 같은 예외 상황도 테스트해야 한다.

2.8.2. 성능 테스트

성능 테스트는 시스템이 주어진 작업을 얼마나 효율적으로 처리하는지를 확인하는 테스트이다. 성능을 평가할 때는 작업 부하, 처리량, 반응 시간, 자원 효율성 등을 기준으로 본다.

 

작업 부하는 시스템이 처리해야 하는 작업의 양을 의미한다. 처리량은 일정 시간 동안 처리할 수 있는 트랜잭션의 수를 의미하며, 반응 시간은 사용자의 요청을 처리하는 데 걸리는 총 시간을 의미한다. 자원 효율성은 CPU 시간이나 메모리와 같은 자원을 얼마나 효율적으로 사용하는지를 나타낸다.

 

각 성능과 관련된 대표적인 테스트 방법으로는 성능 테스트와 스트레스 테스트가 있다.

  • 스트레스 테스트: 시스템 처리능력의 몇 배의 작업부하를 처리하고 견딜 수 있는지 측정(nGrinder)
  • 성능 테스트
    • 정상적인 사용 환경에서 시스템의 성능을 측정
    • 시뮬레이션을 이용한 테스트

+) 보안 테스트는 성능 테스트와는 별개의 테스트로 시스템의 보안 취약점을 찾아내기 위한 테스트이다. 인증, 권한 관리, 입력값 검증, 접근 제어 등이 제대로 이루어지는지 확인한다.

2.9. UI 테스트

UI 테스트는 기능, 성능, 보안 테스트와는 목적이 다르다. UI 테스트는 사용자가 시스템을 보고 조작하는 과정에서 발생할 수 있는 결함을 확인한다.

 

테스트 대상에는 화면 구성, 데이터 입력과 출력 표시, 사용자와 시스템 사이의 동작, 오류 처리 메시지, 문서와 도움말 등이 포함된다. 즉, 사용자가 시스템을 편리하고 정확하게 사용할 수 있는지를 확인하는 테스트이다.

2.10. 인수 테스트

인수 테스트는 시스템을 실제로 사용할 준비가 되었는지를 확인하는 테스트이다. 일반적으로 개발자가 아니라 의뢰자나 사용자의 대리인이 수행한다.

 

인수 테스트는 시스템 요구 분석서를 기반으로 하며, 실제 업무 절차에 따라 수행된다. 이를 통해 개발된 시스템이 계약이나 요구사항을 만족하는지 최종적으로 확인한다.

 

인 테스트에는 다음과 같은 두 가지 유형이 존재한다.

  • 알파 테스트: 선택된 사용자가 개발 환경에서 시스템을 시험하는 것
  • 베타 테스트: 선택된 사용자가 실제 외부 환경에서 시스템을 시험하는 것(=필드 테스트)