소개
"개발자 테스트"라는 문구는 소프트웨어 개발자가 가장 적절하게 수행할 수 있는 테스트 타스크를 분류하는 데 사용됩니다. 여기에는 해당 타스크에서 작성되는 중간 산출물도 포함됩니다. 개발자 테스트에는 유닛 테스트,
통합 테스트의 대부분, 보통 시스템 테스트라고 불리는 테스트의 일부 측면과 같은 카테고리에 속한다고 볼 수 있는 작업이 포함됩니다. 개발자 테스트는 전형적으로 구현 원칙의 타스크와 연관되는 반면, 분석 및 디자인
원칙의 타스크와도 관계를 가지고 있습니다.
이와 같은 "전체론적" 방식으로 개발자 테스트를 생각하면, 전형적으로 취하는 한층 "원자론적"인 접근 방식과 연관되는 일부 위험성이 완화될 수 있습니다. 개발자 테스트에 대한 전형적인 접근 방식에서, 노력은 초기에
모든 유닛이 독립적으로 동작하는지 평가하는 데 초점을 맞춥니다. 개발 라이프사이클의 후반부에서 개발 작업이 거의 완료됨에 따라, 통합된 유닛은 작업 중인 서브시스템 또는 시스템으로 어셈블되고 이 설정에서 처음으로
테스트됩니다.
이 접근 방식에서는 여러 가지의 실패가 발생합니다. 첫 번째로, 통합된 유닛과 이후의 서브시스템의 테스트에 대한 단계화된 접근 방식을 취하므로, 테스트 중 식별되는 오류가 종종 너무 늦게 발견됩니다. 이렇게 늦게
발견하게 됨으로써 결국 정정 조치를 취하지 않도록 결정하거나 정정을 위해 많은 재작업을 필요로 하게 됩니다. 이와 같은 재작업에는 비용이 많이 들며 이로 인해 다른 영역에서 앞으로 진행하지 못하게 됩니다. 이렇게
되면 프로젝트를 실패하거나 포기하게 되는 위험성이 증가하게 됩니다.
두 번째로, 유닛, 통합 및 시스템 테스트 사이에 엄격한 경계를 만들면 경계 근처의 오류가 발견되지 않을 가능성이 커집니다. 이와 같은 유형의 테스트 책임이 별도의 팀에 지정될 경우 위험성은 복잡해집니다.
RUP에서 권장하는 개발자 테스트 양식에서는 개발자가 가장 유익하고 적절한 테스트에 초점을 맞춰서 지정된 시점에 수행하도록 권장합니다. 단일 반복 범위 내에서도, 개발자가 별도의 테스트 그룹에 이양하는 추가
오버헤드 없이 자신의 고유 코드 결함을 가능한 많이 찾아서 정정하도록 하는 것이 더 효율적입니다. 원하는 결과는 오류가 독립 유닛에 있든지, 아니면 유닛 통합이나 의미있는 일반 사용자 시나리오 내에 통합된 유닛
작동에 있든지 여부에 관계없이 가장 중요한 소프트웨어 오류를 일찍 발견하는 것입니다.
개발자 테스트 시작하기의 함정
실제로 한층 더 철저한 테스트 수행을 시도하는 많은 개발자가 금방 노력을 포기하곤 합니다. 이를 수행할 만한 가치가 없다고 생각하게 됩니다. 게다가, 개발자 테스트를 사용하여 제대로 시작한 개발자 중 일부도
유지보수할 수 없어 결국 포기하게 되는 테스트 스위트를 작성했다고 생각하게 됩니다.
이 페이지는 첫 번째 장애물을 극복하고 유지보수성 함정을 피하는 테스트 스위트를 작성하기 위한 가이드라인을 제공합니다. 자세한 정보는 가이드라인: 자동화된 테스트 스위트 유지보수를 참조하십시오.
기대 설정
개발자 테스트 가치를 아는 개발자는 테스트를 수행하고, 불필요한 작업으로 보는 개발자는 피할 방법을 찾습니다. 이는 대부분의 업계에 있는 대부분의 개발자 특성이며, 이를 원칙 부족으로 다루어 성공한 적은 역사상
없었습니다. 따라서 개발자는 테스트가 가치가 있다고 예상하고 가치있게 만들기 위해 취해야 하는 조치를 수행해야 합니다.
바람직한 개발자 테스트는 확실하게 연결된 편집-테스트 루프를 따릅니다. 제품을 약간이라도 변경하면(예: 클래스에 새 메소드 추가) 즉시 테스트를 재실행합니다. 테스트가 중단되면 원인이 되는 코드를 정확하게 알 수
있습니다. 이와 같이 쉽고 안정된 속도의 개발이 개발자 테스트의 가장 큰 가치입니다. 장기간의 디버깅 세션은 예외입니다.
하나의 클래스에서 수행된 변경사항이 다른 클래스에서 어떤 사항을 중단시키는 일은 보기 드문 일이 아니므로, 변경된 클래스의 테스트 뿐만 아니라 많은 테스트를 재실행해야 합니다. 바람직하게는 컴포넌트의 전체 테스트
스위트를 매시간 여러 번 재실행합니다. 중요한 변경을 수행할 때마다 스위트를 재실행하고 결과를 감시하여 다음 변경으로 진행하거나 마지막 변경을 수정합니다. 빠른 피드백이 가능하려면 노력이 필요합니다.
테스트 자동화
테스트가 수동인 경우 테스트를 자주 실행하면 좋지 않습니다. 어떤 컴포넌트의 경우에는 자동화된 테스트가 바람직합니다. 예를 들어 인메모리 데이터베이스가 있습니다. 이 데이터베이스는 API를 통해 클라이언트와
통신하므로 외부 세계와의 다른 인터페이스가 없습니다. 이 데이터베이스에 대한 테스트는 다음과 같습니다.
/* Check that elements can be added at most once. */
// Setup
Database db = new Database();
db.add("key1", "value1");
// Test
boolean result = db.add("key1", "another value");
expect(result == false);
테스트는 단방향으로 된 보통의 클라이언트 코드와 다릅니다. API 호출 결과를 믿는 대신 확인합니다. API로 인해 클라이언트 코드를 쉽게 작성할 수 있으면 테스트 코드도 쉽게 작성할 수 있게 됩니다. 테스트
코드를 작성하는 것이 쉽지 않으면 API를 개선할 수 있음을 알리는 초기 경고를 얻은 셈입니다. 테스트 우선 디자인은 중요한 위험성을 초기에 처리하는 데 초점을 맞추는 RUP와 일관성이 있습니다.
그러나 컴포넌트가 외부 세계와 단단하게 연결되어 있을 수록 테스트는 어려워집니다. 두 가지의 공통 사례인 그래픽 사용자 인터페이스와 백엔드 컴포넌트가 있습니다.
그래픽 사용자 인터페이스
위의 예에서 데이터베이스가 사용자 인터페이스 오브젝트로부터의 콜백을 통해 데이터를 수신한다고 가정해 보십시오. 콜백은 사용자가 일부 텍스트 필드를 채우고 단추를 누를 때 호출됩니다. 수동으로 필드를 채우고 단추를
눌러서 테스트하는 것은 1시간에 여러 번 수행하려고 하는 테스트가 아닙니다. 프로그램 방식의 제어 하에 입력을 전달하는 방식을 배열해야 합니다(일반적으로 코드에서 단추를 "누름").
단추를 누르면 컴포넌트에서 특정 코드가 실행됩니다. 대개, 코드는 사용자 인터페이스 오브젝트의 상태로 변경됩니다. 따라서 오브젝트를 조회하는 방법도 프로그램 방식으로 배열해야 합니다.
백엔드 컴포넌트
테스트할 컴포넌트가 데이터베이스를 구현하지 않는다고 가정해 보십시오. 대신, 이 컴포넌트는 실제 온디스크 데이터베이스 주변의 랩퍼입니다. 실제 데이터베이스에 대해 테스트를 수행하는 것은 어려울 수 있습니다.
설치하고 구성하기가 어려울 수 있습니다. 라이센스 비용도 많이 소비될 수 있습니다. 데이터베이스가 너무 느려져서 사용자가 자주 실행하지 않게 될 수 있습니다. 이와 같은 경우, 테스트를 지원하기에 충분하며 더
단순한 컴포넌트로 데이터베이스를 "스텁아웃"하는 것이 바람직합니다.
스텁은 또한 사용자 컴포넌트가 통신하는 컴포넌트가 아직 준비되어 있지 않을 때 유용합니다. 테스트에서 다른 누군가의 코드를 대기하는 것은 바람직하지 않습니다.
자세한 정보는 개념: 스텁을 참조하십시오.
사용자 자체 도구 작성 안함
개발자 테스트는 매우 간단해 보입니다. 오브젝트를 설정하고, API를 통해 호출하며, 결과를 확인한 후 결과가 예상한 대로가 아니면 테스트 실패를 선언합니다. 또한 개별적으로 또는 완전한 스위트로 실행할 수 있도록
테스트를 그룹화하는 방법을 가지고 있는 것도 편리합니다. 이와 같은 요구사항을 지원하는 도구를 테스트 프레임워크라고 합니다.
개발자 테스트는 간단하므로 테스트 프레임워크에 대한 요구사항도 복잡하지 않습니다. 그러나 자신의 고유한 테스트 프레임워크를 작성하려는 마음이 생기면 예상한 것보다 많은 시간을 프레임워크를 고치는데
소비하게 될 것입니다. 상용 및 개방형 소스로 많은 테스트 프레임워크가 사용 가능하므로 이 중 하나를 사용하지 않을 이유가 없습니다.
지원 코드 작성 수행
테스트 코드는 반복되는 경향이 있습니다. 일반적으로 다음과 같은 코드 시퀀스를 볼 수 있습니다.
// null name not allowed
retval = o.createName("");
expect(retval == null);
// leading spaces not allowed
retval = o.createName(" l");
expect(retval == null);
// trailing spaces not allowed
retval = o.createName("name ");
expect(retval == null);
// first character may not be numeric
retval = o.createName("5allpha");
expect(retval == null);
이 코드를 작성할 때는 하나의 확인을 복사하여 붙여넣은 후 편집하여 다른 확인을 작성합니다.
이 때 두 가지의 위험이 발생합니다. 인터페이스가 변경될 경우 많은 편집을 수행해야 합니다. (복잡한 경우 단순한 글로벌 대체로는 충분하지 않습니다.) 또한 코드가 복잡하면 텍스트 전체에서 테스트의 의도를 잃을 수
있습니다.
스스로 반복하고 있음을 발견하면, 반복을 분리하여 지원 코드에 포함하는 것을 심각하게 고려하십시오. 위의 코드는 단순한 예제이지만 다음과 같이 작성하면 더 읽기 쉽고 유지보수가 용이합니다.
void expectNameRejected(MyClass o, String s) {
Object retval = o.createName(s);
expect(retval == null); }
...
// null name not allowed
expectNameRejected(o, "");
// leading spaces not allowed.
expectNameRejected(o, " l");
// trailing spaces not allowed.
expectNameRejected(o, "name ");
// first character may not be numeric.
expectNameRejected(o, "5alpha");
테스트를 작성하는 개발자는 종종 복사 후 붙여넣기를 너무 많이 수행합니다. 스스로 이러한 경향이 있는 것으로 의심이 가면 의식적으로 다른 방향으로 진행하는 것이 유용합니다. 모든 중복 텍스트 코드를 제거하면
해결됩니다.
테스트 먼저 작성
코드 다음에 테스트를 작성하는 것은 귀찮은 일입니다. 그냥 지나치거나 빨리 끝내려는 충동이 생깁니다. 코드 이전에 테스트를 작성하면 테스트 부분에 긍정적 피드백 루프가 작성됩니다. 코드를 많이 구현할수록 많은
테스트를 패스하고 결국 모든 테스트를 패스하여 종료됩니다. 테스트를 우선 작성하는 사람은 성공률이 높고 시간이 적게 걸립니다. 테스트를 먼저 작성하는 방법은 개념: 테스트 우선 디자인을 참조하십시오.
테스트를 이해 가능하도록 유지
사용자나 다른 누군가가 나중에 테스트를 수정해야 함을 예상해야 합니다. 전형적인 상황은 이후 반복에서 컴포넌트의 동작 변경이 요청되는 것입니다. 단순한 예로, 컴포넌트가 다음과 같은 제곱근을 메소드를 선언했다고
가정해 보십시오.
double sqrt(double x);
이 버전에서, 음수 인수는 sqrt가 NaN(IEEE 754-1985 2진 부동 소수점 산술 표준의 "숫자 아님")을 리턴하도록 합니다. 새 반복에서, 제곱근 메소드는 음수를 받아들이고
복소수 결과를 리턴하게 됩니다.
Complex sqrt(double x);
sqrt의 이전 테스트를 변경해야 합니다. 즉, 이전 테스트가 무엇을 수행하는지 이해하고 새 sqrt로 작업하도록 갱신해야 함을 의미합니다. 테스트를 갱신할 때 버그를 찾는 기능을
없애지 않도록 주의해야 합니다. 가끔 사용되는 한 가지 방법은 다음과 같습니다.
void testSQRT () {
// Update these tests for Complex
// when I have time -- bem
/* double result = sqrt(0.0); ... */ }
다른 방법은 더 이해하기 어렵습니다. 테스트가 변경되어 실제로 실행되지만 원래 테스트하려던 내용을 더 이상 테스트하지 않습니다. 많은 반복을 거친 최종 결과는 많은 버그를 잡기에는 너무 약한 테스트 스위트가 될 수
있습니다. 이를 "테스트 스위트 쇠퇴"라고도 합니다. 쇠퇴된 스위트는 보관할 가치가 없으므로 버립니다.
테스트가 구현하는 테스트 아이디어가 무엇인지 명확하지 않으면 테스트의 버그 찾기 기능을 유지보수할 수 없습니다. 테스트 코드 아래에 주석을 넣지만 제품 코드보다 테스트
코드 이면의 "이유"를 이해하기 어려운 경우가 많습니다.
테스트 스위트 쇠퇴는 sqrt에 대한 간접 테스트보다 직접 테스트에서 덜 발생합니다. sqrt를 호출하는 코드가 있습니다. 이 코드는 테스트를 가집니다. sqrt가 변경되면 일부 테스트는
실패합니다. sqrt를 변경하는 사람이 테스트도 변경해야 합니다. 변경하는 사람은 테스트에 익숙하지 않고 테스트와 변경의 관계가 명확하지 않으므로, 테스트를 패스하도록 하는 프로세스 중에 이를 더
약화시키게 됩니다.
테스트의 지원 코드 작성 시 주의해야 합니다(위에 강조된 대로). 지원 코드는 이 코드를 사용하는 테스트의 목적을 명백하게 설명해야 합니다. 객체 지향 프로그램에 대한 공통적인 불만은 어떤 내용이 수행되는 단일
공간이 없다는 것입니다. 한 가지 메소드를 보고 알 수 있는 것은 그 메소드가 해당 작업을 다른 어딘가로 보낸다는 것 뿐입니다. 이와 같은 구조에는 장점도 있지만 새로운 인원이 코드를 이해하기가 어려워집니다. 새
인원이 노력하지 않으면 잘못 변경하거나 코드를 더 복잡하고 부실하게 만들기 쉽습니다. 테스트 코드에서도 마찬가지입니다. 게다가 나중에 유지보수 담당자가 주의할 가능성도 적습니다. 이해할 수 있는 테스트를 작성하여
문제점을 미리 방지해야 합니다.
테스트 구조를 제품 구조에 일치시키기
누군가 사용자의 컴포넌트를 인계받았다고 가정해 보십시오. 그리고 컴포넌트의 일부를 변경해야 합니다. 새로 디자인할 때 이전 테스트를 점검할 수 있습니다. 코드를 작성하기 전에 이전 테스트를 갱신하려 할
것입니다(테스트 우선 디자인).
적절한 테스트를 찾을 수 없으면 이런 좋은 의도가 무산됩니다. 보통 변경을 작성하고 어떤 테스트가 실패하는지 확인한 후 수정하기 마련입니다. 이렇게 하면 테스트 스위트 쇠퇴를 가져올 것입니다.
이와 같은 이유로, 테스트 스위트를 잘 구성하여 제품의 구조를 통해 테스트 위치를 예측할 수 있도록 해야 합니다. 대개, 개발자는 테스트를 병렬 계층 구조로 배열합니다(제품 클래스마다 하나의 테스트 클래스를
배열함). 따라서 누군가 Log라고 하는 클래스를 변경하면 테스트 클래스가 TestLog임을 알고 소스 파일이 있는 위치를 알게 됩니다.
캡슐화 위반 테스트
클라이언트 코드가 사용하는 인터페이스와 동일한 인터페이스를 통해 클라이언트 코드와 똑같이 사용자 컴포넌트와 상호작용하도록 테스트를 제한할 수 있습니다. 그러나 여기에는 단점이 있습니다. 이중으로 링크된 목록을
유지보수하는 단순 클래스를 테스트한다고 가정해 보십시오.
그림 1: 이중 링크된 목록
특히, DoublyLinkedList.insertBefore(Object existing, Object newObject) 메소드를 테스트합니다. 테스트 중 하나에서 목록 중간에 요소를 삽입한 후
성공적으로 삽입되었는지 확인하려고 합니다. 테스트는 위의 목록을 사용하여 다음과 같은 갱신된 목록을 작성합니다.
그림 2: 이중 링크된 목록 - 항목이 삽입됨
다음과 같이 목록의 정확성을 확인합니다.
// the list is now one longer.
expect(list.size()==3);
// the new element is in the correct position
expect(list.get(1)==m);
// check that other elements are still there.
expect(list.get(0)==a); expect(list.get(2)==z);
이것으로 충분해 보이지만 그렇지 않습니다. 목록 구현이 올바르지 않고 역방향 포인터가 올바르게 설정되어 있지 않다고 가정해 보십시오. 즉, 갱신된 목록이 실제로 다음과 같다고 가정해 보십시오.
그림 3: 이중 링크된 목록 - 구현 결함
DoublyLinkedList.get(int index)가 목록의 처음부터 끝까지 지나갈 경우(보통의 경우임) 테스트는 이 실패를 놓칩니다. 클래스가 elementBefore 및
elementAfter 메소드를 제공할 경우에는 이런 실패를 바로 확인할 수 있습니다.
// Check that links were all updated
expect(list.elementAfter(a)==m);
expect(list.elementAfter(m)==z);
expect(list.elementBefore(z)==m);
//this will fail
expect(list.elementBefore(m)==a);
그러나 이와 같은 메소드를 제공하지 않을 경우에는 어떻게 합니까? 의심이 가는 결함이 존재할 경우 실패할 메소드 호출 시퀀스를 더 정교하게 정제할 수 있습니다. 예를 들어 다음과 같습니다.
// Check whether back-link from Z is correct.
list.insertBefore(z, x);
// If it was incorrectly not updated, X will have
// been inserted just after A.
expect(list.get(1)==m);
그러나 이와 같은 테스트에는 더 많이 작업이 필요하며 유지보수하기가 훨씬 어렵습니다. (주석을 제대로 작성하지 않으면 테스트 수행 내용과 이유가 명백하지 않게 됩니다.) 두 가지의 솔루션이 있습니다.
-
elementBefore 및 elementAfter 메소드를 public 인터페이스에 추가합니다. 그러나 사실상 모든 사람에게 구현을 노출하므로 차후 변경이 더 어렵게
됩니다.
-
테스트 자체가 "내부를 보고" 직접 포인터를 확인하도록 합니다.
DoublyLinkedList와 같은 단순 클래스와 특히 사용자 제품에 발생하는 더 복잡한 클래스에 대해서도 보통 마지막 솔루션이 최상의 솔루션이 됩니다.
일반적으로, 테스트는 테스트하는 클래스와 같은 패키지에 놓입니다. 테스트에는 보호 또는 동반자 액세스 권한이 부여됩니다.
특징적인 테스트 디자인 실수
각 테스트는 컴포넌트를 연습하여 올바른 결과가 나오는지 확인합니다. 테스트의 디자인(테스트에 사용되는 입력과 정정을 확인하는 방법)으로 결함을 잘 표시하지만, 실수로 결함을 숨길 수 있습니다. 다음은 특징적인
테스트 디자인 실수의 일부입니다.
미리 예상되는 결과를 지정하는 데 실패함
XML을 HTML로 변환하는 컴포넌트를 테스트한다고 가정해 보십시오. 샘플 XML을 가져다가 변환하여 실행한 후 브라우저에서 결과를 보려는 유혹에 빠지게 됩니다. 화면이 올바르게 보이면 이 HTML을 공식적 예상
결과로 저장하기 쉽습니다. 이후에, 테스트는 변환의 실제 산출물을 예상 결과와 비교합니다.
이는 위험한 사례입니다. 숙련된 사용자도 컴퓨터의 수행 결과를 믿기 쉽습니다. 화면상의 실수는 대충 훑어보게 됩니다. (잘못 형식화된 HTML도 브라우저에서는 그럴듯해 보이게 됩니다.) 올바르지 않은 HTML을
공식적 예상 결과로 만들면 테스트에서 문제점을 찾을 수 없게 됩니다.
직접 HTML을 보면서 한번 더 확인하면 덜하기는 하지만 역시 위험합니다. 출력이 복잡하므로 오류를 건너뛰기가 쉽습니다. 먼저 예상 출력을 직접 작성하면 더 많은 결함을 찾을 수 있습니다.
백그라운드 확인 실패
테스트는 보통 변경해야 할 내용이 변경되었는지 확인하지만, 테스트의 작성자는 종종 그대로 두어야 할 내용을 그대로 두었는지 확인하는 것을 잊습니다. 예를 들어, 프로그램이 파일에서 처음 100개의 레코드를
변경한다고 가정해 보십시오. 101번째가 변경되지 않았는지 확인하는 것이 좋습니다.
이론상, "백그라운드"에 있는 모든 내용(전체 파일 시스템, 메모리 전체, 네트워크를 통해 도달할 수 있는 모든 것)을 그대로 두었는지 확인하게 됩니다. 실제로는, 확인할 수 있을 만한 내용을 주의해서 선택해야
합니다.
지속성 확인 실패
컴포넌트에서 사용자에게 변경이 수행되었음을 알려준다고 해서 변경이 데이터베이스에서 실제로 확약되었음을 의미하지는 않습니다. 다른 방법으로 데이터베이스를 확인해야 합니다.
다양성 추가 실패
테스트가 데이터베이스 레코드에서 세 개의 필드 결과를 확인하도록 지정되었지만 테스트를 실행하려면 다른 많은 필드를 채워할 수 있습니다. 테스터는 보통 이렇게 "관계없는" 필드에 몇 번이고 같은 값을 사용합니다.
예를 들어, 텍스트 필드에 항상 애인의 이름을 사용하거나 숫자 필드에 999를 사용합니다.
문제점은 관계없어야 하는 내용이 실제로는 문제가 되는 경우가 있다는 것입니다. 때때로, 원하지 않는 입력의 불분명한 조합으로 버그가 발생합니다. 항상 같은 입력을 사용할 경우 이와 같은 버그를 찾을 수 없습니다.
지속적으로 다르게 입력하면 찾을 수도 있습니다. 999가 아닌 다른 숫자를 사용하거나 다른 이름을 사용하는 데는 거의 비용이 들지 않습니다. 테스트에 사용되는 값을 다양하게 해도 비용이 거의 들지 않고 잠재된
이점도 있으므로 다양화하십시오. (참고: 현재 애인과 함께 작업하고 있을 경우 현재 애인의 이름 대신 이전 애인의 이름을 사용하는 것은 현명하지 않습니다.)
또 다른 이점이 있습니다. 그럴듯한 결함 중 하나는 Y 필드를 사용해야 하는데 X 필드를 사용하는 프로그램의 결함입니다. 두 필드 모두에 "Dawn"이 있을 경우 결함을 발견할 수
없습니다.
실제적 데이터 사용 실패
테스트에서는 꾸며낸 데이터를 사용하는 것이 일반적입니다. 이런 데이터는 종종 실제와는 다르게 간단합니다. 예를 들어, 고객 이름이 "Mickey", "Snoopy", "Donald"일 수 있습니다. 이 데이터는
실제 사용자가 입력하는 데이터와 다르므로(예: 보통 더 짧음) 실제 고객이 보는 결함을 놓치게 됩니다. 예를 들어, 1단어 이름을 입력하면 코드에서 공백이 있는 이름을 처리하지 못하는 것을 발견하지 못합니다.
실제적 데이터를 사용하기 위해 추가 노력을 들이는 것이 현명합니다.
코드가 아무 것도 수행하지 않음을 알리는 데 실패함
데이터베이스 레코드를 0으로 초기화하고, 결과로 0이 레코드에 저장되어야 하는 계산을 실행한 후 레코드가 0인지 확인한다고 가정해 보십시오. 테스트의 결과가 무엇입니까? 계산이 전혀 수행되지 않았을 수도 있습니다.
아무 것도 저장되지 않았어도 테스트로는 알 수가 없습니다.
이와 같은 예제는 있음직하지 않지만, 알아차리기 어려운 방식으로 이와 같은 실수가 발생할 수 있습니다. 예를 들어, 복잡한 설치 프로그램에 대한 테스트를 작성할 수 있습니다. 테스트는 성공적인 설치 후에 모든 임시
파일이 제거되는지 확인하려고 합니다. 그러나 설치 프로그램 옵션으로 인해, 해당 테스트에서 하나의 특정 임시 파일이 작성되지 않았습니다. 우연하게도 프로그램에서 제거하지 않은 파일도 역시 이 파일일 수 있습니다.
코드가 잘못 수행됨을 알리는 데 실패함
때때로 프로그램은 잘못된 이유로 올바른 내용을 수행합니다. 흔한 예로, 다음 코드를 고려하십시오.
if (a < b && c)
return 2 * x;
else
return x * x;
논리식이 잘못되었는데, 사용자가 논리식을 잘못 평가하여 틀린 분기를 가지도록 테스트를 작성했습니다. 공교롭게도, 변수 X가 테스트에서 값 2를 가지고 있습니다. 따라서 잘못된 분기의 결과가 우연히 올바른 것처럼
되었습니다. 올바른 분기의 경우에도 마찬가지 결과가 나타납니다.
예상되는 결과마다, 잘못된 이유로 그럴듯한 결과가 발생할 수 있는지 확인해야 합니다. 보통은 알기 어렵지만 가능한 경우도 있습니다.
|