개념:초기 테스트 설계
주제
테스트 설계는 유스 케이스 구현, 설계 모델 또는 분류자 인터페이스와 같은
설계 결과물을 포함하여 여러 가지 결과물로부터의 정보를 사용하여 작성됩니다.
테스트는 컴포넌트가 작성된 후에 실행됩니다. 테스트를 실행하기 직전(소프트웨어 설계
결과물이 작성된 후)에 테스트 설계를 작성하는 것이 일반적입니다. 다음
그림1은 예를 보여줍니다.
여기에서 테스트 설계는 가끔 구현 끝 무렵에 시작됩니다. 컴포넌트 설계 결과 위에
테스트 설계를 그립니다. 구현부터 테스트 실행까지의 화살표는 구현이 완료될 때까지
테스트를 실행할 수 없다는 것을 나타냅니다.

그림1: 일반적으로 테스트 설계는 라이프사이클 후반에 수행됩니다.
그러나 반드시 이러한 방식일 필요는 없습니다. 컴포넌트가 구현될 때까지
테스트 실행을 기다려야 하지만 테스트 설계를 미리 수행할 수 있습니다. 이것은
설계 결과물이 완료된 직후에 수행할 수 있습니다. 이것은 다음과 같이
컴포넌트 설계와 동시에 수행할 수도 있습니다.

그림2: 초기 테스트 설계는 소프트웨어 설계와 같은 선상에서 연대순으로 테스트 설계를 가져옵니다.
이 방식에서 테스트 노력을 "업스트림"으로 이동시키는 것을
일반적으로 "초기 테스트 설계"라고 합니다. 장점은 다음과 같습니다.
- 아무리 소프트웨어를 주의깊게 설계한다고 해도 실수를 하기 마련입니다.
관련 사실을 누락시킬 수도 있습니다. 또는 다른 대안을 찾기 어렵게 만드는
특정 사고 습관을 가지고 있을 수도 있습니다. 또는 피곤한 나머지 중요한 사항을
간과할 수 있습니다. 다른 사람에게 설계 결과물을 검토하게 하면 도움이 됩니다.
누락된 사실 또는 간과한 사항이 있을 수 있습니다.
이러한 경우, 이 사람이 설계자와 다른 Perspective를 가질 경우에 가장 적합합니다. 그들은
설계를 다르게 보고 누락한 부분을 찾아낼 수 있습니다.
경험상, 테스트 Perspective는 중요한 사항입니다. 이것은 상당히 구체적입니다. 소프트웨어 설계 중에
특정 필드를 "현재 고객의 직위 표시"로
간주하여 실제로 그에 대해 생각해 보지 않고 진행하기 쉽습니다.
예를 들어, 해군에서 퇴역한 후 변호사직을 얻은 고객이 자신을 "Lieutenant
Morton H. Throckbottle (Ret.), Esq."라고 주장할 때 그의 직위가
"Lieutenant(대위)"인지 또는 "Esquire(변호사)"인지 테스트 설계 중에
해당 필드가 보여주는 것이 무엇인지를 명확하게 판단해야 합니다.
테스트 설계가 테스트 실행 직전까지 연기되는 경우 그림 1에서처럼 돈을 낭비하게 됩니다.
일부 테스터가 "알다시피, 저는 이 사람을 해군에서 알게 되었습니다."라고
말하고 "Morton" 테스트를 작성 및 문제점을 발견할 경우, 소프트웨어 설계의 오류는 테스트 설계까지
파악되지 않습니다. 이제 부분적으로 또는 전체적으로 구현을
다시 작성하고 설계 결과물을 갱신해야 합니다. 구현 시작 전에
문제점을 파악하면 보다 저렴한 비용이 듭니다.
- 일부 실수는 테스트 설계 전에 파악될 수 있습니다. 대신 구현자에 의해
발견됩니다. 그다지 좋은 현상은 아닙니다. 초점이 설계의 구현 방법에서
설계가 당연히 가지고 있어야 하는 내용으로 전환되면 구현은 멈춰있어야 합니다.
그것은 구현자 및 설계자 역할이 동일한 사람에 의해 수행될 때조차도
혼란스럽습니다. 두 역할을 다른 사람이 수행하는 경우 훨씬 더 문제가 큽니다.
이러한 혼란을 예방할 수 있는 또 다른 방법은 초기 테스트 설계를 통해 효율성을
향상시키는 것입니다.
- 테스트 설계는 다른 방식(설계를 명확히 설명)으로 구현자를 도와줍니다.
설계가 의미하는 것에 관해 구현자가 의문을 가지고 있는 경우, 테스트 설계는
예상하는 작동의 특정 예를 제공할 수도 있습니다. 이것은 구현자의 오해로 버그 수가 줄어들 것입니다.
- 구현자에게 의문이 없지만 의문을 갖어야 할 경우에도
버그의 수는 줄어들 것입니다. 예를 들어, 설계자는 무의식적으로 단방향으로 이해하고
구현자는 다방향으로 이해하는 모호한 사항이 있을 수 있습니다.
구현자가 설계 및 테스트 케이스에서 컴포넌트의 수행 사항에 관한 특정 지시사항 둘 다에 의거하여
작업하는 경우, 컴포넌트는 실제로 필요한 사항을 더 많이 수행할 수 있습니다.
다음은 초기 테스트 설계의 특징을 보여주는 몇 가지 예입니다.
"비서에게 물어서" 회의룸을 지정하는 오래된 방식을 대체하는
시스템을 작성하고 있다고 가정하십시오. MeetingDatabase 클래스의
메소드 중 하나는 getMeeting이고 이것은 다음과 같은 서명을 가지고 있습니다.
Meeting getMeeting(Person, Time);
사람과 시간을 지정하면 getMeeting은 제 시간에
해당 사람을 스케줄한 회의를 리턴합니다. 사람이 어느 것에 대해서도
스케줄되지 않은 경우, 특수 Meeting 객체인
unscheduled를 리턴합니다. 다음은 몇 가지 간단한 테스트 케이스입니다.
- 사람이 지정된 시간의 회의에 있지 않습니다. unscheduled 회의가
리턴되었습니까?
- 사람이 해당 시간에 회의에 있었습니다. 메소드가 올바른 회의를 리턴합니까?
이러한 테스트 케이스가 재미는 없지만 결국 시도해야 합니다.
또한 언젠가 실행될 실제 테스트 코드를 작성하여 테스트 케이스가 현재 작성되었을 수도 있습니다.
첫 번째 테스트에 대한 Java 코드는 다음과 유사합니다.
// if not in a meeting at given time,
// expect to be unscheduled.
public void testWhenAvailable() {
Person fred = new Person("fred");
Time now = Time.now();
MeetingDatabase db = new MeetingDatabase();
expect(db.getMeeting(fred, now) == Meeting.unscheduled);
}
그러나 보다 흥미로운 테스트 아이디어가 있습니다. 예를 들어, 이 메소드는 일치사항을
검색합니다. 메소드가 검색할 때마다 검색이 둘 이상의 일치사항을 발견하면
발생되어야 하는 사항을 묻는 것이 좋습니다. 이 경우 이것은
"한 사람이 동시에 두 회의에 참석할 수 있습니까?"라고 질문하는
것을 의미합니다. 불가능해 보이지만 이러한 경우에 관해 비서에게 물어보면
놀라운 사실이 드러날 수 있습니다.
일부 간부들은 상당히 자주 두 회의에 동시에 스케줄되어 있음이 밟혀집니다. 간부들의
역할은 회의에 잠깐 들어가 잠깐 동안 "참석자들을 고무시킨" 뒤
다음으로 이동합니다. 이러한 작동을 조정하지 못하는 시스템은
적어도 부분적으로 사용되지 않게 됩니다.
이것은 분석 문제점을 파악하는 구현 레벨에서 수행된 초기 테스트 설계의 한 예입니다.
이에 관해 알아두어야 하는 다음 몇 가지 사항이 있습니다.
- 적절한 유스 케이스 스펙 및 분석이 이미 이 요구사항을 발견했을 것이라고
기대합니다. 이 경우, 문제점은 "업스트림"을 피해갔고 getMeeting은
다르게 설계되었다는 점입니다. (회의 세트를 리턴해야 하는 데 회의를 리턴할 수 없습니다.) 그러나
분석은 항상 일부 문제점을 누락하게 되므로 전개 이후보다 구현 중에
발견하는 것이 바람직합니다.
- 많은 경우 설계자와 구현자는 이러한 문제점을 파악할 수 있는 전문
지식이 없습니다. 그들에게는 비서에게 질문할 기회 또는 시간이 없습니다.
이 경우에서 getMeeting에 대한 테스트를 설계하는 사람은
"두 회의가 리턴되어야 하는 경우가 있습니까?"라고 질문하고
한동안 생각한 후 없다고 결론 짓습니다. 따라서 초기 테스트 설계는 모든 문제점을 파악하지는
못하지만 올바른 유형의 질문을 던지는 사실만으로도 문제점을 발견할 기회가 증가합니다.
- 구현 중에 적용하는 동일한 테스트 기법 중 일부는 분석에도 적용됩니다.
초기 테스트 설계는 분석가에 의해서도 수행될 수 있지만
현재 이 페이지의 주제 범위를 벗어납니다.
세 가지 예 중 두 번째는 난방 시스템의 상태 차트 모델입니다.

그림3: HVAC 상태 차트
테스트 세트가 상태 차트의 모든 원호를 가로지릅니다. 한 테스트는
대기 시스템에서 시작하여 강온 이벤트를 삽입하고, 냉각/실행 상태에서
시스템을 작동시키지 않고, 장애를 제거하고 다른 강온 이벤트를 삽입한 후
시스템을 다시 대기 상태로 실행합니다. 이것은 모든 단계를 수행하지는 않으므로
추가 테스트가 필요합니다. 이러한 종류의 테스트는 여러 가지 유형의 구현 문제점을
찾습니다. 예를 들어, 모든 단계를 가로질러 해당 구현이 제외되지 않았는지 확인합니다.
성공적으로 완료되어야 하는 경로 앞에 실패 경로가 있는 일련의 이벤트를 사용하여
오류 처리 코드가 이후 컴퓨터 사용에 영향을 미칠 수 있는 일부 결과를 제거하지 못했는지
여부를 확인합니다.
(상태 차트 테스트에 관한 자세한 정보는 가이드라인:
상태 차트 및 활동 다이어그램에 대한 테스트 아이디어를 참조하십시오.)
마지막 예에서는 설계 모델의 일부를 사용합니다. 채권자와 송장 사이에는
연관성이 있고 지정된 채권자는 둘 이상의 송장을 미결제로 둘 수 있습니다.

그림4: 채권자 및 송장 클래스 간 연관
이 모델을 기반으로 하는 테스트는 채권자에게 송장이 없고 한 개의 송장이 있고
다수의 송장이 있는 경우 시스템을 작용시킵니다. 테스터는 또한
송장이 둘 이상의 채권자와 연관되어야 하는 상황 또는 송장에 채권자가 없는 상황이
있는지 여부도 요청합니다. (아마도 현재 서류 기반의 시스템을 운영하며 컴퓨터 시스템으로
바꾸려는 사람들은 보류 중인 작업을 추적하는 한 방식으로 채권자 없는 송장을 사용합니다) 그러한 경우
이것은 분석 중에 파악해야 하는 또 다른 문제점이 됩니다.
초기 테스트 설계는 설계 작성자 또는 다른 누군가에 의해 수행될 수 있습니다.
일반적으로 작성자가 초기 테스트 설계를 수행합니다. 이것은 의사소통 경비가 줄어드는
장점이 있습니다. 결과물 설계자 및 테스트 설계자는 서로에게 사정을 설명할
필요가 없습니다. 또한 별도의 테스트 설계자는 설계를 이해하기 위해 시간을
보내야 하지만 원래 설계자는 설계에 대해 이미 알고 있습니다. 마지막으로
"압축기가 X 상태에서 고장난 경우 발생하는 일은 무엇입니까?"와 같은
질문들 다수는 소프트웨어 결과물 설계 및 테스트 설계 중에 제기되는 자연스런
질문이므로 동일한 사람은 한 번만 질문을 하고 테스트 형식으로 응답을 기록할 수 있습니다.
물론 단점도 있습니다. 한 가지는 결과물 설계자가 어느 정도까지는
실수를 알지 못한다는 것입니다. 테스트 설계 프로세스는 이러한 일부 맹점을
밝혀주지만 아마도 다른 사람이 발견할 수 있는 만큼은 아닙니다. 이것이
얼마나 큰 문제점인가는 사람마다 상당히 차이가 나는 것처럼 보이고 종종
설계자의 경험 정도와 관련되어 있습니다.
같은 사람이 소프트웨어 설계 및 테스트 설계를 모두 수행하는 것의
또 다른 단점은 병행이 없다는 것입니다. 역할을 별도의 사람에게
지정하는 데에는 보다 많은 총 노력이 사용되지만 이것은 아마도 그보다 적은
시간이 걸릴 것입니다. 사람들이 설계에서 벗어나 구현으로 가고자 못견뎌하는 경우
테스트 설계에 시간이 소요되는 것은 좌절감을 줄 수 있습니다. 보다 중요한 것은
옮겨 가기 위해 작업을 날림하기 쉽다는 것입니다.
아닙니다. 이유는 모든 의사결정이 설계 시간에 수행되지 않기 때문입니다. 구현 중에
내려진 의사결정은 설계에서 작성된 테스트에 의해 잘 테스트되지 않습니다.
이에 대한 전형적인 예는 배열을 정렬하는 루틴입니다. 서로 다른 상쇄사항을 가진
여러 가지 많은 정렬 알고리즘이 있습니다. Quicksort는 보통 대형 배열의
삽입 정렬보다는 빠르지만 종종 소형 배열에서는 느립니다. 따라서 정렬 알고리즘은
16개 이상의 요소를 가진 배열에 대해서는 Quicksort를 사용하여 구현되고
그렇지 않은 경우는 삽입 정렬을 사용할 수 있습니다. 이러한 노력의 분배가
설계 결과물에서는 보이지 않을 수도 있습니다. 설계 결과물에 이를
나타낼 수 있지만 설계자는 이러한 명백한 의사결정의 장점이
가치가 없다고 판단할 수도 있습니다. 설계에서 배열 크기가 별다른
역할을 수행하지 않기 때문에 테스트 설계는 우연히 소형 배열만 사용할 수 있는 데
이는 Quicksort 코드를 전혀 테스트하지 않는다는 것을 의미합니다.
또 다른 예로 순서 다이어그램의 다음 부분에 대해 생각해봅시다. 이것은
StableStore의 log() 메소드를 호출하는
SecurityManager를 보여줍니다. 이 경우에서 log()는
실패를 리턴하는 데 이것은 SecurityManager가
Connection.close()를 호출하게 합니다.

그림5: SecurityManager 순서 다이어그램 예
이것은 구현자에게 유익한 암시입니다. log()가 실패할 때마다
연결은 닫혀야 합니다. 응답할 테스트 질문은 구현자가 모든 케이스 또는
일부에서만 실제로 이것을 수행했는지(올바로
수행했는지) 여부입니다. 이 질문에 응답하기 위해 테스트 설계자는 StableStore.log()에
대한 모든 호출을 찾아서 이러한 각 호출 위치가 처리할 해당 실패인지 확인해야 합니다.
StableStore.log()를 호출하는 모든 코드를 방금 본 경우
이러한 테스트를 실행하는 것이 이상해보일 수 있습니다. 이것이 실패를 올바로
처리하는지 확인하는 검사만 수행할 수 없습니까?
아마도 검사만으로 충분할 수 있습니다. 그러나 오류 처리 코드는
종종 내재적으로 오류 존재가 위반한 가정에 의존하기 때문에
악명높은 오류 경향이 있습니다. 이것의 전형적인 예는 할당 실패를
처리하는 코드입니다. 다음은 한 예입니다.
while (true) { // top level event loop
try {
XEvent xe = getEvent();
... // main body of program
} catch (OutOfMemoryError e) {
emergencyRestart();
}
}
이 코드는 정리(메모리 사용 가능) 후 이벤트 처리를
계속하여 메모리 부족 오류에서 복구하려고 시도합니다. 그것이 승인 가능한
설계라고 가정해봅시다. emergencyRestart는 메모리 할당을
하지 않기 위해 대단히 유의합니다. 문제점은 emergencyRestart가
일부 유틸리티 루틴을 호출하고 이것은 일부 다른 유틸리티 루틴(새 객체 할당)을
호출한다는 것입니다. 메모리가 없다는 점을 제외하고 전체 프로그램이 실패합니다.
이러한 유형의 문제점은 검사를 통해 발견하기 어렵습니다.
지금까지는 가능한 일찍 가능한 많은 테스트 설계를 수행해왔다고 암묵적으로
가정해왔습니다. 즉 나중에 구현 본질에 기초하는 테스트만 추가하여 설계 결과물에서
수행할 수 있는 모든 테스트를 끌어내왔습니다.
이러한 전체 테스트가 반복의 목표에 적당하지 않을 수 있으므로
구현화 단계에서는 이것이 적절하지 않을 수 있습니다.
제품 가능성을 투자자에게 시연하기 위해 구조적 프로토타입을 작성하고
있다고 가정합시다. 이것은 소수의 핵심 유스 케이스 인스턴스에 기초할 수 있습니다. 코드가
이를 지원하는지 확인하기 위해 코드를 테스트해야 합니다. 그러나 추가 테스트를 작성할 때
다른 문제는 없습니까? 예를 들어, 프로토타입이 중요한 오류 사례를 명백하게 무시할 수 있습니다.
이를 수행하는 테스트 케이스를 작성하여 해당 오류 처리에 대한 요구를 문서화하는 것이 좋습니다.
그러나 프로토타입이 해당 작업을 수행하고 구조적 접근법이 작용하지 않는다는 사실을
보여준 경우는 어떠합니까? 그런 경우 오류 처리를 위한 모든 테스트와 함께
구조를 버리게 됩니다. 이 경우 테스트 설계 노력은 아무런 가치가 없게 됩니다.
이 개념 증명 프로토타입이 실제로 개념을 증명하는지 여부를 확인하는 데 필요한 테스트만 설계하고
기다리는 것이 바람직합니다.
이것은 사소한 것처럼 보이지만 작업 중에 강한 심리적 영향을 줍니다.
구현화 단계에서는 중요한 위험을 다루기 시작합니다. 전체 프로젝트 팀은
이러한 위험에 초점을 맞춰야 합니다. 사소한 이슈에 사람들이 집중하면
팀의 초점과 에너지가 소진됩니다.
따라서 구현화 단계에서 초기 테스트 설계를 성공적으로 사용할 수 있는 부분은 어디입니까?
이것은 구조적 위험을 적절하게 탐색하는 데 있어서 중요한 역할을 할 수 있습니다. 팀의
위험에 대한 인식 또는 예방법이 정확할수록 설계 프로세스가 명확해지고
처음부터 좀더 나은 구조를 빌드할 수 있습니다.
구성 단계 중에 설계 결과물은 마지막 양식에 삽입됩니다.
필요한 모든 유스 케이스 구현사항이 구현되고 모든 클래스의 인터페이스가 구현됩니다.
이 단계의 목표는 완전성이기 때문에 완전한 초기 테스트 설계가 적절합니다.
나중에 결과는 극소수의 테스트를 무효화해야 합니다.
초기 및 전이 단계에서는 일반적으로 테스트에 적절한 설계 활동에 초점을 적게
둡니다. 이러한 경우 초기 테스트 설계가 적절합니다.
예를 들어, 초기 단계에서 후보 개념 증명 작업과 함께 사용할 수 있습니다.
구성 및 구현화 단계 테스트와 마찬가지로 반복 목표에 맞춰야 합니다.
|