|
SW 테스트의 종류는 매우 다양하며, 'SW 개발 단계', '테스트 케이스 생성 기법', '테스트 목적' 등과 같은 기준에 따라 분류될 수 있다. 표는 분류 기준에 따른 소프트웨어 테스트 종류를 보여준다.
이 장에서는 SW 개발 단계에 따라 테스트를 수행하는 전략, 즉 개발 단계에 따른 테스트 방법에 대해 살펴본다. 단위 테스트, 통합 테스트, 시스템 테스트, 인수 테스트 및 회귀 테스트는 테스트 목적이나 방법이 서로 다르기 때문에 각각에 대해 자세하게 알아 볼 필요가 있다.
분류 기준 | 테스트 종류 | 설명 |
SW 개발 단계 | 단위 테스트 | 각각의 모듈을 테스트 함 |
통합 테스트 | 단위 테스트가 완료된 모듈 간의 I/F를 테스트 함 | |
시스템 테스트 | 전체 시스템에 대해 초기의 목적을 만족시키는지를 테스트 함 | |
인수 테스트 | 사용자의 요구사항을 만족하는지를 확인 함 | |
회귀 테스트 | 유지보수 단계에서 변경이 제대로 이루어졌는지를 테스트 함 | |
테스트 케이스 생성기법 | 블랙박스 테스트 | 프로그램 내부 구조를 블랙박스로 보고 입력과 출력을 확인하며 프로그램을 테스트 함 |
화이트박스 테스트 | 프로그램 코드 정보를 바탕으로 프로그램을 테스트 함 | |
테스트 목적 | 침임 테스트 | 시스템의 보안성을 테스트 함 |
견고성 테스트 | 타당하지 못한 입력 값 등을 사용하여 프로그램 견고성을 테스트 함 | |
성능 테스트 | 시스템의 성능을 테스트 함 | |
신뢰성 테스트 | 시스템의 신뢰도를 측정할 목적으로 테스트 함 | |
프로그램 실행 여부 | 동적 테스트 | 프로그램을 실제로 실행하여 오류를 검출 함 |
정적 테스트 | 프로그램을 실행하지 않고 프로그램 코드를 분석하여 오류를 검출 함 |
표. 소프트웨어 테스트의 분류
3.1 단위 테스트
단위 테스트(Unit test)란 하나의 SW 모듈이 정상적으로 기능을 수행하는지 여부를 확인하는 테스트이다. 단위 테스트의 목적은 단위 프로그램별로 명세서에 정의된 기능을 제대로 수행하는지 검증하는 것이다. 테스트 대상이 하나의 모듈이기 때문에 테스트 대상 모듈은 나머지 코드 또는 모듈들과 격리된 상태에서 테스트된다. 이러한 단위 테스트를 수행하기 위해서는 '테스트 드라이버'와 '테스트스텝' 이 필요하다.
테스트 드라이버(Test driver)는 테스트 대상이 되는 모듈을 호출하여 준비한 테스트 데이터를 제공하고 모듈의 실행 결과를 받는 모듈이다. 그림은 테스트 대상 모듈과 테스트 드라이버와의 관계를 보여준다.
기본적으로 테스트 드라이버는 다음과 같은 작업을 수행한다.
• 1단계 : 테스트 데이터가 저장되어 있는 파일이나 데이터베이스로 부터 하나의 테스트 데이터를 읽음
• 2단계 : 읽은 테스트 데이터를 입력 인자로 사용하여 테스트 대상이 되는 모듈을 호출 함
• 3단계 : 모듈의 실행 결과를 받아 저장하고 다음 테스트 데이터를 읽음
• 4단계 : 더 이상 실행할 테스트 데이터가 없을 때까지 2~3단계 과정을 반복하여수행 함
경우에 따라서는 테스트 드라이버가 테스트 오라클(Test oracle)의 역할을 수행할 수도 있다. 즉, 예상 결과와 실제 수행된 결과가 일치하는지를 판별하여 프로그램에 오류가 존재하는지를 판별하는 기능을 수행한다.
테스트 대상이 되는 모듈 M이 또 다른 모듈 M1, M2를 호출하는 경우, 모듈 M을 테스트 하기 위해서는 테스트 드라이버뿐만 아니라 추가적으로 모듈 M1, M2가 필요하다. 그러나 모듈 M1, M2의 구현이 완료되지 않았거나 모듈 M1, M2에 대한 단위 테스트가 완료되지 않은 경우 모듈 M1, M2를 대치할 수 있는 모듈들이 필요하다. 이러한 모듈들을 테스트 스텝(Test stub)이라 한다. 그림은 테스트 드라이버, 모듈 및 테스트 스텝의 관계를 보여준다.
테스트 스텝은 테스트를 위해 필요한 모듈이기 때문에 모든 기능을 완전히 구현할 필요는 없으며, 어느 정도의 기능을 갖도록 스텝을 구현하느냐의 문제는 상황에 따라 다를 수 있다.
테스트 대상 모듈 M에 의해 호출되는 모듈 M1을 대치하는 테스트 스텝 구현 시, 단순히 호출되는 모듈명과 인자들을 출력하는 형태로 만들 수 있다. 예를 들어, 모듈 M이 사용자로부터 명령어를 입력 받아 실제 명령어를 처리하는 모듈을 호출하는 경우 모듈 M은 다음과 같은 구조를 가질 것이다.
MODULE M (...)
BEGIN
IF
CALL COMMAND_1 (...);
ELSE IF
CALL COMMAND_2 (…);
...
ELSE
CALL COMMAND N(...);
END IF
END
이와 같은 구조의 모듈을 테스트하기 위해서는 실제로 각 조건문에 따라 COMMAND_N이 적절하게 호출되었는가를 살펴볼 필요가 있다. 따라서 COMMAND_N에 해당하는 실제 모듈들과 같은 기능을 하는 테스트 스텝들을 작성할 필요는 없고, 다음과 같이 호출되는 모듈명과 인자들만을 출력하는 형태로 만들 수 있다.
MODULE STUB_COMMAND_N(…)
BEGIN
WRITE ( “모듈 이름: STUB COMMAND_N");
WRITE ( "인자 정보: …”);
RETURN;
END
만약 모듈 M1이 인자 값에 따라 특정한 기능을 수행하는 경우, 파일로부터 이미 정의된 정보를 읽어 들여 특정 기능만이 실행되도록 만들 수 있다. 예를 들면, 다음 모듈은 모듈 M1을 호출하는 상황 또는 인자 값들과는 관계없이 특정 실행 결과를 미리 파일에 저장해서 이를 전달하는 형태로 테스트 스텝을 작성한 예이다.
MODULE STUB_M1 (…)
BEGIN
RESULT = READFROMFILE (...);
RETURN RESULT;
END
또한, 사용자로부터 직접 입력을 받을 수도 있다. 아래의 예와 같이 테스트 대상 모듈이 정렬 프로그램을 호출할 때 실제 정렬 프로그램을 구현하지 않고 사용자로 부터 입력 받도록 테스트 스텝을 작성할 수 있다.
MODULE STUB_SORT(VAR a:SEQ)
BEGIN
FOR (I=1; N) DO
READ(a[I]);
END
이처럼 테스트 스텝을 테스트에 활용함으로써 실제 모듈을 사용할 때 발생시키기 어려운 상황을 강제적으로 만들 수 있다. 예를 들면, 원래의 모듈이 비행기 이륙정보를 데이터베이스로부터 읽어 반환하는 경우를 보자. 이 모듈을 대치하는 테스트 스텝에서는 비행기 10대가 동시에 이륙하는 경우를 만들어 이를 호출하는 모듈이 올바르게 작동하는지를 살펴볼 수 있다.
객체 지향 프로그램에서 단위 테스트 수행을 용이하게 하기 위한 모의 객체에 대해서는 (참고 3.1)을 참고한다.
3.2 통합 테스트
통합 테스트(Integration test)는 모듈을 통합하는 과정에서 수행되는 테스트이다. 단위 테스트는 개별적인 모듈의 기능이 올바르게 수행되었는지를 테스트하는반면, 통합 테스트는 모듈 간의 상호 작용이 올바르게 이루어 지는지를 검사하는테스트이다. 개별적인 모듈에 대해 테스트가 수행되었다 할지라도 실제로 모듈을통합한 후에 오류가 발생할 수 있다. 예를 들면, 개별적인 모듈에 대한 단위 테스트가 불충분하게 이루어졌거나 제한된 상황만을 고려한 테스트 스텝 때문에 실제 모듈과 통합하는 과정에서 오류가 발생할 수 있다. 또한, 전역 변수 등으로 인해 모듈상호 간에 발생할 수 있는 예기치 못한 부작용(Side effect) 때문에 오류가 발생할수도 있다.
통합 테스트를 수행하는 방법은 모듈들을 통합하는 전략과 밀접한 관계가 있다.가장 쉬운 방법은 빅뱅(Big-bang) 통합 전략을 사용하는 것이다.
빅뱅 통합 전략에서는 개별적인 모듈에 대해 단위 테스트를 수행한 후 한번에 모듈들을 통합하는데, 이 경우 전체 시스템에 대해 통합 테스트를 수행하여야 한다.그러나 이러한 테스트 방법은 심각한 문제를 초래할 수 있다. 전체 시스템에 대해 테스트를 수행하기 때문에 오류가 발생했을 경우 오류가 발생한 모듈 및 원인을 찾기가 매우 어려운 것이다. 이러한 문제점을 해결하기 위해서는 한번에 모듈들을 통합하지 않고 점진적으로 모듈들을 통합하면서 테스트하는 것이 필요하다. 점진적으로 모듈들을 통합하는 방법에는 하향식 통합(Top-down integration)과 상향식 통합(Bottom-up integration)이 있다.
하향식 통합은 시스템을 구성하는 모듈들의 계층 구조에서 가장 상위에 있는 모듈부터 시작하여 하위에 있는 모듈들을 점진적으로 통합하는 방식이다. 따라서 상위 모듈을 테스트할 때에는 하위 모듈을 대치할 테스트 스텝이 필요하다.
그림은 하향식 통합 테스트를 수행하는 과정이다. 하향식으로 모듈은 깊이우선(Depth first) 순서나 너비 우선(Breadth first) 순서에 따라 통합할 수 있다. 깊이 우선 방식으로 통합할 때에는 우선 M1부터 시작하여 M2, M3, M4, M5, M6, M7의 순서로 모듈들을 통합하며, 너비 우선 방식으로 통합할 때에는 M1, M2, M6, M7, M3, M4, M5의 순서로 모듈들을 통합한다.
하향식 통합 방식을 사용하여 시스템을 테스트하는 과정은 다음과 같다.
• 1단계 : 가장 상위 모듈을 테스트하기 위해 하위 모듈들을 테스트 스텝들로 대치한 후에 테스트를 수행함
• 2단계 : 깊이 우선 방식이나 너비 우선 방식을 사용하여 테스트 스텝을 한번에 하나씩 실제 모듈로 대치함. 단, 추가된 모듈이 호출하는 하위 모듈들을 테스트 스텁들로 대치한 후 회귀 테스트(Regression test)를 수행함
• 3단계 : 2단계 과정을 시스템이 완전히 통합될 때까지 반복하여 수행함
2단계에서의 회귀 테스트란 프로그램이 변경된 후에 새로 유입된 오류가 없는지를 확인하는 일종의 반복 시험을 말한다. 이 경우에 테스트 스텝이 실제 모듈로 대치되었으므로 회귀 테스트를 수행할 필요가 있다. 회귀 테스트는 통합 단계에서뿐만 아니라 유지 보수단계에서도 프로그램이 변경될 때마다 새로운 오류가 유입되었는지를 판별하기 위해 수행한다.
모듈 종속 관계에서 기본적으로 상위 모듈은 시스템의 기능을 결정하는 논리를 갖고 있으며, 하위 모듈들은 시스템이 제공하는 기능을 보조하는 역할을 갖는다. 즉, 상위 모듈의 오류는 시스템 설계 상의 오류가 나타난 것으로 해석할 수 있다. 하향식 통합 테스트는 상위 모듈을 반복적으로 테스트하게 되므로 설계상의 오류를 빨리 발견할 수 있다는 장점이 있다.
그러나 기본적으로 하향식 통합 테스트 방법은 많은 수의 테스트 스텝이 필요하기 때문에, 테스트 스텝을 구현할 때 비용이 많이 소요되는 경우에는 효과적인 테스트 방법이 아니다. 또한, 하향식 통합 테스트를 할 때 블랙박스 테스트만을 사용하는 경우에는 하위 모듈이 충분하게 테스트 되지 않을 수 있다. 보통 상위 모듈은하위 모듈을 호출하여 시스템의 기능을 제공하고 하위 모듈은 보다 하위 모듈들을 호출하여 상위 모듈이 필요로 하는 서비스를 제공한다. 따라서, 가장 하위에 있는 모듈들을 충분히 테스트하기 위해서는 모듈 종속 관계를 파악하고 테스트 케이스 선정 시 이를 반영해야 한다. 모듈 종속 관계를 파악하기 위해서는 통합되는 각 모듈들의 구조를 참고하여 테스트 집합을 생성할 필요가 있다. 즉, 하향식으로 통합테스트를 수행하는 경우에는 프로그램 구현 정보를 이용한 화이트박스 테스트를 수행할 필요가 있다.
하향식 통합 테스트와는 대조적으로 상향식 통합 테스트는 하위 모듈을 먼저 테스트하고 상위에 있는 모듈들을 통합하는 방식이다. 상향식 통합은 하위 모듈을 우선 통합하기 때문에 하위 모듈을 호출하는 테스트 드라이버가 필요하다.
그림은 상향식 통합 테스트 과정을 보여준다. 상향식 통합을 하는 과정은 특별한 기능을 제공하는 하위의 모듈들을 식별하여 그룹화한다. 이러한 모듈들의 모임을 '클러스터 (Cluster)' 또는 '빌드(Build)'라 한다. 하위에 있는 모듈들을 클러스터링한 후에 테스트 드라이버를 작성하여 테스트를 수행한다. 클러스터들을 테스트한 후에 테스트 드라이버를 제거하고 결합한다. 이와 같은 과정을 시스템이 완전히 통합 될 때까지 반복한다.
상향식 통합 테스트는 하위에 있는 모듈들을 충분하게 테스트할 수 있다는 장점이 있다. 모듈 의존 관계에서 하위에 위치한 모듈들은 보통 시스템이 제공하는 서비스들에 필요한 공통적인 기능들을 제공하는 역할을 한다. 따라서 모듈이 하위에 있을수록 여러 상위 모듈들에서 빈번하게 사용되는 코드를 갖는다고 간주할 수 있으며 통합이 진행될수록 이 코드들을 테스트하는 횟수가 증가한다. 또한, 하향식통합에서 볼 수 있는 테스트 스텝을 제공하는 비용이 들지 않는다는 장점도 있다. 그러나, 설계 오류를 조기에 발견하지 못하는 단점이 있다.
실제로는 상향식 또는 하향식 통합 방식 중에서 어느 한가지 방식으로 시스템을 통합하기 보다는 두 방법을 결합하여 시스템을 통합할 수 있다. 이러한 통합 방식을 샌드위치 통합(Sandwich integration)이라 한다.
그림은 샌드위치 통합 전략을 사용하여 모듈을 통합하는 과정을 보여준다. 그림에서 상위 모듈 M1은 모듈 M2, M3, M4에 해당하는 테스트 스텝들을 사용하여 테스트하고 모듈 M5, M6, M7은 클러스터로 그룹화하여 테스트 드라이버 D1을 사용하여 테스트한다. 클러스터에 대한 테스트가 완료되면 실제 모듈 M2를 통합하여 테스트한다. 나머지 모듈 M3, M4도 추가하여 완전한 시스템을 구축한다.
3.3 시스템 테스트
시스템 테스트(System test)는 완전한 시스템에 대해 시스템이 명세된 요구사항에 맞게 개발되었는지를 검사하는 테스트이다. 따라서 단위 테스트와 통합 테스트가 완료된 단계에서 진행된다. 시스템 테스트는 테스트의 목적이 단위 테스트나 통합 테스트와는 다르다. 단위 테스트나 통합 테스트는 기능이 올바르게 수행 되는지를 검증하는 것에 중점을 두지만, 시스템 테스트는 시스템의 기능 측면뿐만 아니라 신뢰성(Reliability), 견고성(Robustness), 성능(Performance), 안정성(Security) 등과 같은 비기능적인 요구사항을 시스템이 만족하는지도 검증한다. 아래에서는 시스템 테스트를 수행할 때 주로 고려하는 속성들을 설명하도록 한다.
3.3.1 신뢰성 테스트
신뢰성 테스트는 시스템이 특정 기간 동안에 요구되는 서비스를 제공할 수 있는 능력을 측정하는 테스트이다. 보통 신뢰성은 가용성(Availability), MTTF(MeanTime To Failure) 등의 척도에 의해 정량화 된다. 가용성은 시스템이 주어진 기간동안 서비스를 실제로 제공할 수 있는지를 나타내는 속성이다. 예를 들면, 어떤 시스템의 가용성이 0.995를 갖는다고 했을 때 이 시스템은 1,000 시간 단위(날, 주, 달)동안 총 995 시간 단위 동안 서비스를 정상적으로 제공할 수 있다는 것을 의미한다. MTTF는 시스템이 운영된 후 오류가 발생할 때까지의 평균 동작 시간이다. 예를 들면, 시스템의 MTTF가 100이라는 사실은 100 시간 단위마다 1개의 오류가 발생할 수 있다는 것을 의미한다. 보통 신뢰성을 테스트하기 위해서 통계적 테스트(Statistical testing) 방법을 사용한다. 통계적 테스트는 운영 프로파일(Operational profile)을 사용하여 테스트 케이스를 생성한다. 운영 프로파일은 시스템이 실제로 사용자들에 의해 사용되는 패턴으로, 가능한 입력들을 여러 개의 클래스들로 분류하고 각 입력 클래스의 발생 확률로 구성된다. 일단 운영 프로파일을 작성하였으면 각 입력 클래스의 발생 확률에 따라 테스트 케이스를 생성한다. 소프트웨어의 신뢰성을 측정하기 위해서는 오류가 발생하기까지의 시스템 동작 시간이 중요하므로 오류가 발생한 시간과 오류가 발생하고 다음 오류가 발생할 때까지의 동작시간을 기록한다. 이렇게 모아진 데이터를 사용하여 여러 신뢰성 추정 모델을 통해 신뢰성을 추정하게 된다.
3.3.2 견고성 테스트
견고성이란 시스템이 비정상적인 운영환경에서 얼마나 원활하게 동작하는지를 나타내는 속성이다. 보통 견고성을 테스트하기 위해서 시스템이 기대하지 않는 입력들을 테스트 데이터로 사용한다. 이러한 테스트를 부정적 테스트(Negative test)라 한다. 예를 들면, 시스템이 영어 소문자 및 대문자로 구성되는 문자열만을 입력으로 기대한다고 했을 때 특수 문자 등을 포함하는 문자열들을 테스트 데이터로 입력하여 테스트할 수 있다.
3.3.3 성능 테스트
성능 테스트는 개발된 시스템이나 소프트웨어 프로그램이 주어진 환경 하에서 응답 속도, 처리량, 처리 속도 등의 항목에 대하여 요구된 목표값을 달성하는지를 확인하는 시험이다. 성능 테스트는 부하와 시간 유형에 따라 다음과 같은 테스트가 가능하다.
• 부하 테스트(Load test) : 애플리케이션을 서서히 최대 부하에 노출시킴. 실 세계의 부하 모델을 기준으로, 특정 하드웨어와 소프트웨어의 구성이 성능 요구에 적합한지를 확인함
• 스트레스 테스트(Stress test) : 애플리케이션을 짧은 시간 동안 실제 기대되는 부하보다 더 큰 부하에 노출시킴. 가장 최악의 조건에서 애플리케이션이 처리할수 있는 능력 및 한계상황 대처 능력을 점검함
• 스파이크 테스트(Spike test) : 애플리케이션을 갑자기 증가하는 부하에 노출시킴. 동시에 많은 사용자에 의해 사용되는 애플리케이션을 평가하기에 유용함
• 안정성 테스트(Stability test) : 애플리케이션을 오랜 시간 동안 평균 부하에 노출시킴. 메모리 누수와 같은 문제 발견이 가능함
3.3.4 보안성 테스트
네트워크 기술의 발전과 인터넷의 광범위한 사용으로 인해 전자상거래가 활성화되고 있는 시점에서 보안성은 매우 중요한 소프트웨어 품질을 결정하는 한 요소로 주목 받고 있다. 안정성을 유지하기 위해서는 불순한 의도를 가진 사용자로부터개인 또는 조직의 정보를 보호할 수 있도록 시스템을 구축하는 것이 매우 중요하다. 시스템의 보안성을 검증하기 위해 주로 사용하는 방법으로 침입 테스트가 있다. 침입 테스트(Penetration test)는 침입자(Hacker)의 관점에서 소프트웨어 시스템의 취약성(Vulnerability)을 찾는 테스트로, 시스템 보안성에 관해 경험과 지식이 많은 사람들이 보통 수행한다. 이들은 침입 시나리오들을 이용하여 소프트웨어의 취약성을 찾고 해결책을 제시한다. 그러나 만약 침입 테스트에 의해 소프트웨어의 취약성이 발견되지 않았더라도 시스템의 보안성이 확보되었다고는 확신할 수 없다. 그 이유는 수많은 침입자가 만들어내는 침입 시나리오들을 모두 미리 테스트하는 것은 거의 불가능하며, 어느 시점에서의 모든 침입 시나리오가 있어 이를 모두 테스트 한다 할지라도 침입자들이 또 다른 침입 시나리오를 개발하여 시도하기 때문이다.
위에서 기술한 테스트 이외에 컴퓨터가 어떤 이유로 인하여 동작할 수 없을때 정상적으로 복구되는지를 알아보는 복구 테스트(Recovery test), 사용자가 시스템을 얼마나 쉽게 사용할 수 있는지를 검증하는 사용성 테스트(Usability test), 시스템의 구 버전이 존재할 때 구 버전과의 차이를 알아보는 Back-to-back 테스트 등이 있다.
3.4 인수 테스트
시스템 테스트가 완료되면 실제 사용자에 의해 인수 테스트(Acceptance test)를 수행해야 한다. 인수 테스트에서 사용되는 테스트 케이스는 일반적으로 사용자 또는 소프트웨어 구매자에 의해 제공된다. 실제 사용자가 시스템을 사용하는 방식은 개발자가 시스템을 테스트 할 때 가정했던 방식과 차이가 있을 수 있다. 따라서 인수 테스트에서는 개발자에 의해 수행된 테스트에서 발견되지 못한 오류를 발견할 수 있다는 장점이 있다. 인수 테스트의 주 목적은 오류를 검출하는 것이 아니라 시스템을 인수 받을 수 있는지를 고객의 입장에서 평가하는 것이다. 만약 시스템이 인수 기준을 만족하지 못한다면 시스템의 인수를 거부할 수 있다.
또한, 시스템 테스트에서 사용했던 테스트 케이스를 사용하여 인수 테스트를 수행할 수도 있다. 이 경우 시스템 테스트와 인수 테스트와의 차이점은 개발 환경이 아닌 실제 운영 환경에서 테스트를 수행한다는 점이다. 실제로 개발된 시스템을 고객에게 인도하기에 앞서 알파 테스트와 베타 테스트를 수행할 수 있다. 알파 테스트(Alpha test)는 사용자에 의해 테스트가 수행되지만 개발자 환경에서 통제된 상태로 수행된다. 반면에 베타 테스트(Beta test)는 소프트웨어를 일정 수의 사용자들에게 사용하게 하고 피드백을 받는다. 보통 베타 테스트에서는 개발자는 참여하지 않는다.
3.5 회귀 테스트
유지보수 단계에서 SW가 수정된 후에 변경이 올바르게 되었는지를 검사하기 위한 테스트를 수행해야 한다. 유지보수 단계에서는 다음과 같은 이유로 SW에 수정이 가해진다
• 오류 수정 작업(Corrective maintenance) : SW를 사용하는 도중에 발견된 오류를 수정하기 위해 SW를 변경하는 유지보수 활동
• 기능 보강 작업(Perfective maintenance) : SW 기능을 추가하거나 성능을 개선하기 위해 SW를 변경하는 유지보수 활동
• 적응 작업(Adaptive maintenance) : SW 시스템을 새로운 운영환경에 적응시키기 위해 SW를 변경하는 유지보수 활동
• 예방 작업(Preventive maintenance) : 더 나은 유지보수를 위해 기존의 SW 시스템에 대한 문서를 준비하거나 시스템 구조를 유지보수하기 용이한 새로운 구조로 변경하는 작업 활동
이러한 유지 보수 활동은 필연적으로 SW를 수정하는 작업을 수반한다. 따라서 유지보수 단계에서는 SW가 변경되었을 때 변경 작업으로 인해 새로운 오류가 도입되었는지를 검사하는 회귀 테스트(Regressiontest)를 수행해야 한다.
보통 회귀 테스트를 수행하는 방법은 개발 단계에서 사용한 테스트 케이스를 이용한다. 개발 단계에서 사용한 테스트 케이스 모두 또는 일부를 수정이 된 프로그램에서 실행하여 실행 결과가 수정되기 전의 프로그램의 실행 결과와 다른지를 살펴본다. 만약 다르다면 이 변화가 의도한 것인지를 보고 의도한 결과가 아니라면 프로그램에 오류가 있는 것으로 판단할 수 있다. 현재 시장에 나와있는 대부분의 테스트 도구 및 환경은 이러한 형태의 회귀 테스트를 지원한다.
참고. 모의 객체(Mock Object)
객체 지향 프로그램에서 단위 테스트를 수행할 때 테스트되는 메소드가 다른 클래스의 객체에 의존할 수 있다. 이 경우에는 메소드를 고립화하여 테스트하는 것이 불가능해진다. 따라서 독립적인 단위 테스트를 위해서는 절차 지향적 프로그래밍에서 스텁과 같은 개념을 사용하는 것이 필요하다. 모의 객체는 일종의 스텁의 객체 지향 버전이라 할 수 있다.
모의 객체는 개발자가 처음부터 수작업으로 만들거나 모의 객체 라이브러리를 이용하여 만들 수 있다. 여기에서는 EasyMock 모의 객체 라이브러리(easymock.org 참조)를 이용하여 모의 객체를 만들어 단위 테스트를 수행하는 방법에 대해 간략하게 살펴본다. 예를 들어, 인자로 은행의 계좌 정보 (Account 객체)를 받는 클래스 Foo의 메소드 perform(Account a)를 테스트하는 상황을 생각해보자. Account 인터페이스는 다음과 같이 정의되어 있다.
interface Account {
Money balance();
boolean withdraw (Money m);
void deposit (Money m);
}
메소드 perform( )을 수행하기 위해서 비용이 500불이 소요된다고 가정하면, 은행 잔고가 최소 500불 이상이어야 한다는 사실을 알 수 있다. 실제의 계좌 객체는 네트워크를 통해 은행과 통신하여 인터페이스에 정의된 기능들을 수행하여야 한다. 그러나 모의 객체는 실 객체 대신에 여러 상황을 별다른 어려움 없이 간단하게 테스트할 수 있도록 한다. 우선 perform( )이 정상적으로 동작하는 시나리오를 고려한 테스트 케이스는 다음과 같다.
private MockControl account Control;
private Account accountMock;
...
public testperformA() {
1 : Foo f = new Foo();
2 : accountControl = MockControl.createControl (Account.class);
3 : accountMock = (Account) accountControl.getMock();
4 : accountMock.balance();
5 : acccountControl.setReturnValue ( (Money) 850);
6 : accountMock.withdraw ( (Money) 500);
7 : accountControl.replay( );
8 :f.perfrom (accountMock);
9 : account Control.verify();
10 : assertTrue (f.isFilled());
}
EasyMock의 테스트 케이스는 크게 네 부분으로 구성된다. 첫 번째 부분은 테스트에 필요한 상황 준비를 설정하는 부분으로, 이 부분에서는 테스트에 필요한 객체들을 생성하거나 자원들을 할당한다. 위의 예에서는 1-3번 라인이 이에 해당된다. 1번 라인에서 테스트 대상이 되는 객체와 계좌 모의 객체를 생성한다. 그 제어 객체도 함께 생성된다.
두 번째 부분은 실제 메소드 perform()을 수행했을 때 모의 객체에 발생되는 예상 함수 호출들에 대한 시나리오(Expectation)를 기술한다. perform( )이 실행되면 계좌 객체에 대해 balance()가 호출되고(4번 라인) 이 함수의 반환 값으로 현재 잔고가 850불이 있어야 된다는 사실(이 테스트는 현재 500불 이상인 상황을 묘사하고 있다는 사실을 상기하라)을 표현한다(5번 라인). 따라서 반환 값으로 꼭 500불 이상인 어떤 값도 실제로는 상관 없다. balance( )가 실행된 후에는 실제 예금 인출이 이루어져야 하므로 withdraw()함수 호출이 있어야 한다는 사실을 표현하고 있다(6번 라인). EasyMock에서는 4-6번 라인까지 즉, 7번 라인에서 replay( )가 호출되기 전까지를 기록 모드(Record mode)라고 하며, replay() 다음부터 재현 모드(Replay mode)라고 한다.
세 번째 부분은 실제 테스트 대상 메소드를 호출하여 테스트를 실행하는 부분이다(8번 라인),마지막 단계는 실제 실행 결과가 예상했던 대로인지를 확인하기 위한 검증 단계(Verify)이다(910번 라인). 만약 balance()나 withdraw()가 perform( )을 실행할 때 호출 되지 않았다면 9번 라인에서 이를 검출할 수 있다. 10번 라인은 정상적으로 perform( )이 수행되었는지를 검사한다.
다음은 은행 잔고가 500불이 되지 않는 상황을 테스트하기 위한 것이다. withdraw()메소드가호출되지 않아야 한다는 사실을 주의하라.
public testperformB() {
Foo f = new Foo();
accountControl = MockControl.createControl (Account.class);
accountMock = (Account) accountControl.getMock();
accountMock.balance ();
acccountControl.setReturnValue ((Money) 450);
accountControl.replay();
f.perform(accountMock);
accountControl.verify();
assertFalse(f.isFilled());
}
연습문제
1 소프트웨어 테스트를 다음과 같은 기준으로 각각 분류하여 설명하시오.
A. 소프트웨어 개발 단계별에 따른 테스트 분류
B. 테스트 케이스 생성의 기법에 따른 분류
C. 테스트 목적에 따른 분류
D. 프로그램 실행 여부에 따른 분류
2 단위 테스트를 수행할 때 필요한 테스트 드라이버와 테스트 스텝의 역할에 대해 설명하시오.
3 점진적 테스트에는 하향식 테스트와 상향식 테스트가 있다. 이 두 방식을 비교 설명하시오.
4 모의 객체와 테스트 스텝의 차이를 기술하시오.
5 통합 테스트에 의해서 검출될 수 있는 오류들을 예를 들어 기술하시오.
6 시스템 테스트와 인수 테스트의 차이에 관해 설명하시오.
7 커닝험에 의해 개발된 FIT 인수 테스트 프레임워크(fit.c2.com)에 대해 조사하시오.
8 성능 테스트의 종류에는 어떤 것이 있는지 구분하고, 각각의 특징에 대해 기술하시오.
|