주제

소개 페이지 맨 위

물리적 객체와 같이 테스트를 구분할 수 있습니다. 마모되는 것이 아니라 환경에서 변경되는 사항입니다. 새 운영 체제에 포트되었을 수도 있습니다. 또는 실행하는 코드가 테스트를 정확하게 실패하도록 하는 방식으로 변경되었을 가능성이 큽니다. e-banking 어플리케이션의 버전 2.0에서 작업한다고 가정하십시오. 버전 1.0에서 다음 로그인에 이 메소드가 사용되었습니다.

public boolean login (String username);

버전 2.0에서 마케팅 부서는 암호 보호가 유용한 개념이라는 사실을 인식했습니다. 따라서 메소드가 다음과 같이 변경되었습니다.

public boolean login (String username, String password);

login을 사용하는 모든 테스트가 실패합니다. 컴파일조차도 되지 않습니다. 로그인하지 않고는 수행될 수 있는 유용한 작업이 많지 않으므로 로그인을 사용하지 않고 작성 가능한 유용한 테스트가 많지 않습니다. 수 많은 테스트 실패를 경험하게 될 수도 있습니다.

이러한 테스트는 login(something)의 모든 인스턴스를 찾아 login(something, "dummy password")로 바꾸는 글로벌 찾기 및 바꾸기 툴을 사용하여 수정할 수 있습니다. 그런 다음 해당 암호를 사용하도록 모든 테스트 계정을 배열하면 됩니다.

그런 다음, 마케팅이 해당 암호에 공간이 포함되지 않도록 결정하면 이를 모두 다시 수행해야 합니다.

이러한 작업은 테스트가 쉽게 변경되지 않는 경우(대부분 경우) 소모적인 부담입니다. 보다 나은 방법이 있습니다.

테스트가 원래 제품의 login 메소드를 호출하지 않는다고 가정하십시오. 테스트가 로그인되고 처리될 준비가 되게 하는 라이브러리 메소드를 호출합니다. 초기에 해당 메소드는 다음과 같이 표시될 수도 있습니다.

public boolean testLogin (String username) {
  return product.login(username);
}

버전 2.0이 변경되면 유틸리티 라이브러리가 다음에 일치하도록 변경됩니다.

public Boolean testLogin (String username) {
  return  product.login(username, "dummy password");
}

천 개의 테스트를 변경하는 대신 하나의 메소드를 변경합니다.

이상적으로, 필요한 모든 라이브러리 메소드가 테스트 노력 초기에 사용 가능할 것입니다. 실제로, 모두가 참여할 수는 없습니다. 처음으로 제품 로그인이 변경되어야 testLogin 유틸리티 메소드가 필요하다는 것을 인식하지 못했을 수도 있습니다. 따라서 테스트 유틸리티 메소드는 종종 필요에 따라 기존 테스트에서 "제외된 요소"가 됩니다. 스케줄 압박 속에서도 이 진행 중인 테스크 복구를 수행하는 것은 매우 중요합니다. 그렇지 않은 경우, 유지보수 불가능한 잘못된 테스트 도구를 처리하는데 상당한 시간을 소모됩니다. 이 테스트 도구를 폐기할 수도 있으며 사용 가능한 테스트 시간을 이전 테스트 유지보수에 소모했으므로 필요한 새 테스트를 작성하지 못할 수도 있습니다.

참고: 제품의 로그인 메소드 테스트는 여전히 직접 호출해야 합니다. 이 작동이 변경될 경우, 일부 또는 모든 테스트가 갱신되어야 합니다. (작동 변경시 로그인 테스트가 실패하지 않은 경우, 결함을 발견하는데 매우 효율적이지 않습니다.)

관리 복잡도에 도움이 되는 추상적 개념 페이지 맨 위

이전 예제는 테스트가 구체적인 어플리케이션을 추상화할 수 있는지 표시합니다. 상당히 더 추상화할 수 있습니다. 여러 테스트가 일반적인 메소드 호출 순서(로그인, 일부 상태 설정 및 테스트 중인 어플리케이션 일부 탐색)로 시작됨을 알 수 있습니다. 그런 다음, 각 테스트는 다른 작업을 수행합니다. 이 모든 설정이 readyAccountForWireTransfer와 같이 연상되는 이름의 단일 메소드로 추상화될 수 있고 되어야 합니다. 이를 수행함으로써, 특정 유형의 새 테스트 작성시 상당한 시간을 절약할 수 있으며 각 테스트의 의도를 보다 이해할 수 있게 합니다.

이해 가능한 테스트는 중요합니다. 이전 테스트 도구의 공통된 문제점은 누구도 테스트 수행 내용 또는 이유를 알지 못했다는 것입니다. 테스트 도구가 중단되면 가능한 가장 단순한 방법으로 수정합니다. 따라서 종종 테스트의 결함을 제대로 찾지 못하게 됩니다. 더 이상 본래 테스트하려던 의도대로 테스트하지 않습니다.

기타 예제 페이지 맨 위

컴파일러를 테스트한다고 가정하십시오. 작성된 일부 첫 클래스가 컴파일러의 내부 단계 트리 및 작성된 변환을 정의합니다. 단계 트리를 구성하고 변환을 테스트하는 여러 테스트가 있습니다. 이러한 테스트 중 하나는 다음과 같습니다.

/* 
 * Given
 *   while (i<0) { f(a+i); i++;}
 * "a+i" cannot be hoisted from the loop because 
 * it contains a variable changed in the loop.
 */
loopTest = new LessOp(new Token("i"), new Token("0"));
aPlusI = new PlusOp(new Token("a"), new Token("i"));
statement1 = new Statement(new Funcall(new Token("f"), aPlusI));
statement2 = new Statement(new PostIncr(new Token("i"));
loop = new While(loopTest, new Block(statement1, statement2));
expect(false, loop.canHoist(aPlusI))

이는 판독이 어려운 테스트입니다. 시간이 경과했다고 가정하십시오. 일부 사항이 변경되어 테스트를 갱신해야 합니다. 이 때, 더 많은 제품 인프라스트럭쳐가 필요합니다. 특히, 문자열을 구문 분석 트리로 변경하는 구문 분석 루틴이 필요합니다. 이 때, 이를 사용하여 다음과 같이 완전히 다시 작성하는 것이 낫습니다.

loop=Parser.parse("while (i<0) { f(a+i); i++; }");
// Get a pointer to the "a+i" part of the loop. 
aPlusI = loop.body.statements[0].args[0];
expect(false, loop.canHoist(aPlusI));

이러한 테스트가 이해하기 훨씬 쉬우며, 현재 및 차후 시간을 절약할 수 있습니다. 사실상, 이 테스트의 유지보수 비용은 매우 낮아서 대부분을 구문 분석기의 사용이 가능해질 때까지 지연하는 것이 옳습니다.

이 접근법에는 테스트가 변환 코드(의도) 또는 구문 분석기(우연)에서 결함을 발견할 수도 있다는 약간의 불리한 면이 있습니다. 따라서 문제점 분리와 디버깅이 다소 어려울 수도 있습니다. 하지만 구문 분석기 테스트가 누락한 문제점을 찾는 것은 그다지 나쁜 것이 아닙니다.

구문 분석기의 결함이 변환 코드의 결함을 감출 수도 있습니다. 이 가능성은 적은 편이며, 비용은 보다 복잡한 테스트를 유지보수하는 비용보다 확실히 작습니다.

테스트 개선에 중점 페이지 맨 위

대형 테스트 도구에는 변경되지 않은 블록의 일부가 포함되어 있습니다. 이는 어플리케이션의 안전한 영역과 일치합니다. 기타 테스트 블록은 종종 변경됩니다. 작동이 자주 변경되는 어플리케이션의 영역에 일치합니다. 이 테스트의 후자 블록은 유틸리티 라이브러리를 많이 사용합니다. 각 테스트의 변경 가능한 영역에서 특정 작동을 테스트합니다. 나머지는 테스트되지 않은 작동에서 상대적으로 변경되지 않는 동안 유틸리티 라이브러리는 이러한 테스트에서 대상 작동을 검사할 수 있도록 설계되었습니다.

예를 들어, 위에 표시된 "loop hoisting" 테스트는 구문 분석 트리가 빌드되는 방법에 대한 세부사항에 영향을 받지 않습니다. 이는 여전히 while 루프의 구문 분석 트리 구조에 민감합니다(a+i의 서브트리를 페치하는데 필요한 액세스 순서로 인해). 해당 구조가 변경 가능하면, 테스트를 fetchSubtree 유틸리티 메소드 작성으로 더욱 추상적으로 작성할 수 있습니다.

loop=Parser.parse("while (i<0) { f(a+i); i++; }");
aPlusI = fetchSubtree(loop, "a+i");

expect(false, loop.canHoist(aPlusI));

이제 테스트는 두 가지(언어 정의(예: 정수는 ++로 증가) 및 루프 호스팅을 제어하는 규칙(확인 중인 작업의 정확성))에만 영향을 받습니다. .

테스트 버리기 페이지 맨 위

유틸리티 라이브러리를 사용하고도 테스트가 점검하는 사항과 전혀 관계가 없는 작동 변경사항에 의해 테스트가 주기적으로 중단될 수 있습니다. 테스트 수정시, 변경사항으로 인해 결함을 찾는 경우가 많습니다. 테스트에 방해되지 않게 일부 결함은 나중에 찾으십시오. 그러나 이 일련의 수정사항에 대한 비용은 테스트에서 가상으로 결함을 찾는 비용을 초과할 수도 있습니다. 단순히 테스트를 버리고 보다 나은 가치의 새 테스트를 작성하는데 노력을 기울이는 것이 보다 나을 수 있습니다.

대부분의 개인들은 적어도 유지보수 부담에 부담을 느껴 모든 테스트를 버릴 때가지는 테스트를 버린다는 개념에 거부감을 갖습니다. 다음 사항을 질문하면서 테스트별로 주의깊게 결정을 내리는 것이 좋습니다.

  1. 이 테스트를 수정하는데 필요한 작업의 양은 얼마이며, 유틸리티 라이브러리를 추가해야 할 수도 있는가?
  2. 얼마나 더 시간을 사용해야 하는가?
  3. 앞으로 테스트에서 심각한 결함을 찾을 가능성은 얼마나 되는가? 테스트와 관련하여 추적 레코드의 내용은 무엇이 있는가?
  4. 테스트가 다시 중단될 때까지 시간이 얼마나 걸리는가?

이러한 질문의 대답은 대략적인 예측이거나 추측입니다. 그러나 이를 질문하는 것은 모든 테스트를 단순히 수정하는 정책보다는 나은 결과를 가져옵니다.

테스트를 버리는 또 다른 이유는 테스트가 중복되기 때문입니다. 예를 들어, 개발 초기에 기본 구문 분석 트리 구성 메소드(LessOp 구성자 등)의 여러 단순한 테스트가 있습니다. 나중에, 구문 분석기 작성 중에 여러 구문 분석기 테스트가 있습니다. 구문 분석기가 구성 메소드를 사용하므로 구문 분석기 테스트는 간접적으로 해당 메소드를 테스트합니다. 코드 변경사항이 구성 테스트를 중단하면 중복되는 일부 테스트를 버리는 것이 합리적입니다. 물론, 모든 새 구성 작동 또는 변경된 구성 작동에는 새 테스트가 필요합니다. 직접(구문 분석기를 통해 철저하게 테스트되기 어려운 경우) 또는 간접적(구문 분석기를 통해 테스트가 적절하고 보다 유지보수하기 쉬운 경우)으로 구현될 수 있습니다.



Rational Unified Process   2003.06.15