제목: 객체의 상태 테스팅에 대하여(On Object State Testing)
저자: D.C. Kung 외 5인, 미국
문서유형: 학계 페이퍼(총 17페이지), 1994년
멤버데이터(상태)와 멤버함수(동작)로 구성된 클래스를 테스트하는 프로그램 기반 객체 상태 테스팅 방법을 제안한 자료
객체 상태 테스팅(Object state testing)
- 객체 지향(OO) 소프트웨어 테스팅의 중요한 한 측면인 객체 상태 테스팅은 통제 흐름 테스팅(control flow testing)이나 데이터 흐름 테스팅(data flow testing)과는 차이가 있다.
- 통제 흐름 테스팅은 프로그램을 통제 구조(예, 시퀀싱, 브랜칭, 반복)에 따라 테스팅 하는데 중점을 두고, 데이터 흐름 테스팅은 개별 데이터의 ‘정의-사용 관계(define-and-use relationships)’를 테스팅 하는데 중점을 둔다.
- 객체 상태 테스팅은 통제 구조(control structures)나 개별 데이터 보다는 객체들의 상태 의존적 동작(state dependent behaviors)에 중점을 둔다.
객체 상태에 의존적인 에러 예
본 논문은 에러가 존재하는 간단한 객체 프로그램(클래스)의 예를 통해 객체 상태 테스팅의 중요성을 설명한다.
1) 자판기 코인 박스 프로그램
- 아래와 같이 C++로 구현된 자판기 코인 박스는 25센트 동전만을 받아 들이며, 25센트 동전 두 개를 받으면 판매를 한다(간략한 설명을 위해 해당 코인 박스가 아주 단순한 기능만을 수행하며 물리적 장치를 통제하기 위한 코드 등은 생략된 것으로 가정).
- 프로그램이 기록하는 정보(멤버변수)는 보유한 25센트 동전의 총 개수(totalQtrs), 현재 받은 25센트 동전 개수(curQtrs), 판매가 허용되는지 아닌지 여부(allowVend)이다.
- 프로그램 기능(멤버함수)으로는 25센트 동전을 받아서 추가하기, 현재 받은 25센트 동전 반환하기, 동전 박스를 초기 상태로 리셋하기, 물건 내주기(판매 허용)가 있다.
class CCoinBox { unsigned totalQtrs; // total quarters collected unsigned curQtrs; // current quarters collected unsigned allowVend; // 1 = vending is allowed public: CCoinBox() { Reset(); } void AddQtr(); // add a quarter void ReturnQtrs() { curQtrs = 0; } // return current quarters unsigned isAllowedVend() { return allowVend; } void Reset() { totalQtrs = 0; allowVend = 0; curQtrs = 0; } void Vend(); // if vending allowed, update totalQtrs and curQtrs };
void CCoinBox::AddQtr() { curQtrs = curQtrs + 1; // add a quarter if (curQtrs > 1) // if more than one quarter is collected, allowVend = 1; // then set allowVend }
void CCoinBox::Vend() { if ( isAllowedVend() ) // if allowVend { totalQtrs = totalQtrs + curQtrs; // update totalQtrs, curQtrs = 0; // curQtrs, and allowVend = 0; // allowVend, } // else no action } |
2) 상태 전이 다이어그램(state transition diagram)
- 객체의 상태는 객체 클래스의 멤버데이터에 의해 정의됨. 위 예의 경우 객체의 상태를 정의하는 멤버데이터로 allowVend와 curQtrs가 있다(특정 액션이 수행되어야 할지를 결정하기 위해 조건문에서 확인이 되는 변수임). 반면 TotalQtrs는 객체가 이 멤버데이터의 변화에 관계 없이 동작하므로 상태를 정의하는 데이터는 아니다.
- allowVend는 [0, 0]와 [1, M]의 두 개의 가능한 상태(intervals 또는 states)를 가진다. 여기서 M은 특정 구현에서 최대한 가능한 unsigned 값을 나타냄. 유사하게 curQtrs도 [0, 0]와 [1, M]의 두 개의 상태를 가짐. 결과적으로 코인 박스에는 총 4개의 상태가 존재한다(아래 그림 참조).
- 객체의 상태는 멤버함수 실행에 따라 변화하게 됨. 멤버함수의 영향은 해당 멤버함수가 적용되기 이전 상태(pre-state)와 이후 상태(post-state)를 식별하여 분석 가능함. 예를 들어 멤버함수 AddQtr()는 4개의 상태 어디에든 적용 가능함. S1에 적용 시에는 S2 상태로 이전하는 결과를 낳고, S2에 적용하면 S4가 결과 상태가 되고 조건식 curQtrs+1>1이 참이 되면서 allowVend가 0보다 커지게 된다.
- 이런 식으로 모든 함수를 분석하면 아래 그림과 같은 상태 전이 다이어그램을 얻게 됨. 클래스 생성자(a constructor)의 결과인 S1은 초기 상태이며, 어떤 상태에서든 실행될 수 있는 멤버함수 Reset()의 결과 상태는 항상 S1이고, 멤버함수 isAllowVend()는 단순화하기 위하여 표시를 생략하였다.
[코인 박스 예제의 상태와 상태 전이]
3) 테스트 시나리오와 에러 식별
- 위 다이어그램으로부터 AddQtr();AddQtr();Vend()의 시나리오(즉, 멤버함수 시퀀스)를 도출 할 수 있음. 이 시퀀스는 코인 박스의 정상적인 동작(25센트 동전 두 개를 받고 음료를 판매함)을 나타낸다.
- 하지만 다이어그램에서 AddQtr();AddQtr();ReturnQtrs();Vend()의 시나리오도 도출 가능함. 즉, 고객이 2개의 동전을 넣은 후 동전을 반환하도록 요구한 뒤에 판매 버튼을 눌러 공짜 음료를 얻게 되는 시나리오다.
- 위 프로그램에서 ReturnQtrs()가 실행 되었을 때 allowVend를 0으로 리셋하지 않기 때문에 두 번째 시나리오에서 판매를 허용하는 에러가 발생함. ReturnQtrs()의 코드를 void ReturnQtrs()으로 변경하여 이 에러를 제거할 수 있다. (물론 이렇게 ReturnQtrs() 코드를 변경하지 않고 프로그램의 다른 부분을 수정하여 구현하는 방법도 가능함)
- 전통적인 기능적 테스팅(functional testing)과 구조적 테스팅(structural testing)은 개별 멤버함수 내의 에러는 발견할지 몰라도 오브젝트 상태를 통한 여러 멤버 함수들간의 상호작용과 관련된 에러는 찾기가 쉽지 않음. 따라서 이런 상태 의존적인 에러(state dependent errors)를 찾기에 적합한 객체 상태 테스팅이 기존 테스팅을 보완 할 수 있다.
객체 상태 테스트 모델(Object State Test Model)
- 소프트웨어 시스템의 상태 의존적 동작은 대개 유한 상태 머신(finite state machines)에 의해 모델링 됨. 위 예에서 사용된 상태 머신은 기존의 function-oriented 소프트웨어의 모델링과 테스팅에 자주 사용되지만 상태와 전이(states and transitions)를 단일 계층에 표현하므로 객체 지향(OO) 패러다임을 모델링 하기에는 적합하지 않음(복잡도가 급증)
- 따라서 본 논문은 OSD(object state diagram)라 불리는 계층적이고(hierarchical) 동시적인(concurrent) 모델을 제안함
- OSD는 모델링 보다는 테스팅을 염두에 두고 정의됨. 즉, 테스터가 오브젝트 클래스의 상태 종속적인 동작을 이해하고, 상태 테스트 케이스를 생성하고, 테스트 결과를 평가하는 것을 용이하게 하려는 목적으로 정의됨
- OSD는 프로그램 코드로부터 구축됨. 즉, OSD의 상태와 전이는 상위 레벨 애플리케이션 도메인 개념 보다는 특정 프로그래밍 개념과 연관된다. 예를 들어 객체의 상태는 클래스의 멤버데이터 집합(또는 서브집합)의 값 범위에 의해 정의되며, 상태 전이는 멤버함수의 실행에 의해 정의된다.
- 제안된 OSD에서는 객체의 상태 정의에 참여하는 클래스의 멤버데이터 각각에 대한 개별 상태 머신을 작성하고 이를 AOSD(an atomic OSD)라 한다. COSD(A composite OSD)는 동시성을 가진 여러 AOSD를 종합하거나 또는 AOSD와 COSD를 종합하여 생성된다(아래 그림 참조).
- 이렇게 AOSD와 COSD를 묶어 또 다른 COSD를 구축하는 방식은 OO 소프트웨어의 상속(inheritance) 및 집합(aggregation) 관계를 표현하기에 적절함
[AOSD]
[COSD]
[코인 박스 예의 COSD]
역공학을 통한 OSD 자동 구축
- 제안된 상태 테스트 모델(OSD)을 매번 수작업으로 OO 프로그램으로부터 생성하는 데는 한계가 있음
- 본 논문은 기호 실행(symbolic execution)을 적용한 역공학으로 C++ 소스 코드로부터 AOSD를 자동으로 생성하는 방법을 제안함(일단 AOSD가 생성되면 COSD는 AOSD로부터 도출 가능)
- 기호 실행은 프로그램을 실제 값(literal values) 대신 기호 값(symbolic values)을 사용하여 실행하는 기법으로, 전통적으로 단일 함수(a single function)의 테스트 케이스를 생성하는데 사용된 잘 알려진 테스트 기법이다.
- 기호 실행의 결과는 규칙들의 집합(a set of rules)이며, 각각의 규칙은 경로조건(a path condition)과 기호 할당의 최종 값을 나타낸 표현식(a final value expression)으로 구성된다. 예를 들어 아래 좌측의 간단한 함수 프로그램에 기호 실행을 적용하면 아래 우측의 두 개의 규칙이 결과로 나온다. 여기서 a*a+b==0과 a*a+b!=0은 경로조건이고 x=0과 x=1은 최종값표현식이다.
프로그램 예 |
|
기호 실행 결과 |
|
|
|
- 제안된 OSD 자동 구축 방법은 3개의 주요 단계로 구성된다:
1) 클래스의 각 멤버함수에 기호 실행 적용
2) 기호 실행의 결과로부터 상태(states)를 식별
3) 기호 실행의 결과로부터 전이(transitions) 식별 - 루프나 포인터를 제대로 처리하지 못하는 것이 기호 실행 기법의 한계이므로 제안된 OSD 구축 방법도 포인터, 포인터 오퍼레이션, 루프는 고려하지 않음
- 기호 실행을 통한 역공학 방법은 OO 프로그램의 대부분의 멤버함수가 단 몇 줄의 라인으로 구성된 단순 코드인 경우가 많다는 특성에 착안하여 제안됨. 따라서 멤버함수의 규모가 크고 복잡하거나 기호 실행이 처리 못하는 루프나 포인터를 포함하는 경우라면 제안 방법과 수작업을 혼합하여 반자동으로 OSD를 생성한다.
테스트 케이스 생성을 위한 테스트 트리 구축
- 본 논문은 구축된 COSD를 신장 트리(A spanning tree)로 전환하는 알고리즘을 제안함. 트리의 노드는 COSD의 복합 상태(the composite states)를 나타내고 에지는 이 상태들간의 전이(transitions)를 나타냄
- 구축된 트리는 테스트 케이스(즉, 멤버함수 시퀀스) 도출에 사용된다. 트리의 가능한 경로를 커버하면서 테스트 케이스를 생성함
- 위 코인 박스 예의 경우 아래 그림과 같은 테스트 트리가 생성되며, 여기서 도출되는 테스트 시퀀스 AddQtr();AddQtr();ReturnQtrs();Vend()는 위에서 언급한 ReturnQtrs() 구현에 존재하는 에러를 발견할 수 있다.
[코인 박스 COSD의 실행 시퀀스를 보여주는 테스트 트리]
'시스템유형별 > 객체 지향' 카테고리의 다른 글
페이퍼요약 - 객체 지향 소프트웨어에서 테스트용이성을 위한 설계 by Payne (0) | 2018.07.13 |
---|---|
페이퍼요약 - 객체 지향 산출물의 검증 및 확인 by Carr (0) | 2018.03.26 |
페이퍼요약 - 객체 지향 소프트웨어 테스팅 이슈 by Barbey (0) | 2018.03.23 |