프로그램의 모든 경로를 테스트하면 모든 버그를 찾을 수 있습니까?
그렇지 않다면 왜 안됩니까? 가능한 모든 프로그램 흐름 조합을 통해 어떻게 문제를 발견 할 수 있습니까?
나는 "모든 버그"를 찾을 수 있다고 주저하지만, 아마도 경로 범위가 실용적이지 않기 때문에 (조합 적이므로) 경험이 없기 때문일 수 있습니까?
참고 : 이 기사 는 내가 생각할 때 적용 범위 유형에 대한 간략한 요약을 제공합니다.
프로그램의 모든 경로를 테스트하면 모든 버그를 찾을 수 있습니까?
그렇지 않다면 왜 안됩니까? 가능한 모든 프로그램 흐름 조합을 통해 어떻게 문제를 발견 할 수 있습니까?
나는 "모든 버그"를 찾을 수 있다고 주저하지만, 아마도 경로 범위가 실용적이지 않기 때문에 (조합 적이므로) 경험이 없기 때문일 수 있습니까?
참고 : 이 기사 는 내가 생각할 때 적용 범위 유형에 대한 간략한 요약을 제공합니다.
답변:
프로그램의 모든 경로를 테스트하면 모든 버그를 찾을 수 있습니까?
아니
그렇지 않다면 왜 안됩니까? 가능한 모든 프로그램 흐름 조합을 통해 어떻게 문제를 발견 할 수 있습니까?
모든 가능한 경로 를 테스트하더라도 모든 가능한 값 또는 가능한 모든 값의 조합으로 경로 를 테스트 하지 않았기 때문 입니다. 예를 들어 (의사 코드) :
def Add(x as Int32, y as Int32) as Int32:
return x + y
Test.Assert(Add(2, 2) == 4) //100% test coverage
Add(MAXINT, 5) //Throws an exception, despite 100% test coverage
프로그램 테스트가 버그의 존재를 설득력있게 보여줄 수는 있지만, 그 부재를 보여줄 수는 없다고 지적 된 지 20 년이 지났습니다 . 이 잘 알려진 발언을 솔직하게 인용 한 후, 소프트웨어 엔지니어는 요크의 연금술사와 마찬가지로 오늘의 순서로 돌아가서 그의 크리 소 코스 믹 정화를 계속 개선 한 테스트 전략을 계속 개선합니다.
- EW 데이 크 스트라 (강조는 추가 된 1988 년에 작성된 그것은 이상 2 년 지금은 상당히있었습니다..)
뿐만 아니라 메이슨의 대답 , 또 다른 문제가있다 : 범위가 않습니다 하지 코드가 테스트되었습니다 무엇을 말해,이 코드가 무엇인지를 알려줍니다 실행 .
100 % 경로 적용 범위를 가진 테스트 스위트가 있다고 가정하십시오. 이제 모든 어설 션을 제거하고 테스트 스위트를 다시 실행하십시오. Voilà, 테스트 스위트는 여전히 100 % 경로 커버리지를 가지고 있지만 전혀 테스트하지 않습니다.
ON ERROR GOTO
도 C와 마찬가지로 경로 if(errno)
입니다.
다음은 사물을 반올림하는 더 간단한 예입니다. 다음 정렬 알고리즘 (Java)을 고려하십시오.
int[] sort(int[] x) { return new int[] { x[0] }; }
이제 테스트 해 보자.
sort(new int[] { 0xCAFEBABE });
이제 (A)이 특정 호출이 sort
올바른 결과 를 반환하고 (B) 모든 코드 경로가이 테스트에 포함되었다는 것을 고려하십시오.
그러나 분명히 프로그램은 실제로 정렬되지 않습니다.
모든 코드 경로의 적용 범위가 프로그램에 버그가 없음을 보장하기에는 충분하지 않습니다.
abs
숫자의 절대 값을 반환 하는 함수를 고려하십시오 . 다음은 테스트입니다 (Python, 테스트 프레임 워크를 상상해보십시오).
def test_abs_of_neg_number_returns_positive():
assert abs(-3) == 3
이 구현은 정확하지만 코드 적용 범위는 60 %입니다.
def abs(x):
if x < 0:
return -x
else:
return x
이 구현은 잘못되었지만 100 % 코드 적용을 얻습니다.
def abs(x):
return -x
def abs(x): if x == -3: return 3 else: return 0
당신은 아마도 생략하다 수있는 else: return 0
부분을 100 % 적용 범위를 얻을 수 있지만, 함수는 단위 테스트를 통과 않는 경우에도 본질적으로 쓸모가있을 것입니다.
메이슨의 대답에 또 다른 추가 사항 , 프로그램의 동작은 런타임 환경에 따라 달라질 수 있습니다.
다음 코드에는 Use-After-Free가 포함되어 있습니다.
int main(void)
{
int* a = malloc(sizeof(a));
int* b = a;
*a = 0;
free(a);
*b = 12; /* UAF */
return 0;
}
이 코드는 정의되지 않은 동작이며, 구성 (release | debug), OS 및 컴파일러에 따라 다른 동작이 발생합니다. 경로 적용 범위가 UAF를 찾도록 보장하지는 않지만 테스트 스위트는 일반적으로 구성에 따라 가능한 다양한 UAF 동작을 다루지 않습니다.
다른 참고로, 경로 적용 범위가 모든 버그를 찾도록 보장하더라도 실제로는 모든 프로그램에서 달성 할 수는 없습니다. 다음 중 하나를 고려하십시오.
int main(int a, int b)
{
if (a != b) {
if (cryptohash(a) == cryptohash(b)) {
return ERROR;
}
}
return 0;
}
테스트 슈트가 이에 대한 모든 경로를 생성 할 수 있다면 축하합니다.
cryptohash
"충분히 작은"것이 무엇인지 말하기는 조금 어렵습니다. 수퍼 계산기에서 완료하는 데 이틀이 걸릴 수 있습니다. 그러나 네, int
조금 밝혀 질 것 short
입니다.
다른 답변에서 테스트에서 100 % 코드 적용 범위가 100 % 코드 정확성을 의미하지는 않으며 테스트를 통해 찾을 수있는 모든 버그가 발견 될 것입니다 (테스트가 포착 할 수없는 버그는 염두에 두지 마십시오).
이 질문에 대답하는 또 다른 방법은 연습 방법입니다.
실제 세계에는 실제로 컴퓨터에는 100 % 적용 범위를 제공하는 테스트 세트를 사용하여 개발되었지만 더 나은 테스트로 식별 할 수있는 버그를 포함하여 여전히 버그가있는 많은 소프트웨어가 있습니다.
따라서 다음과 같은 질문이 있습니다.
코드 적용 툴의 요점은 무엇입니까?
코드 범위 도구는 테스트를 소홀히 한 영역을 식별하는 데 도움이됩니다. 그것은 괜찮을 수도 있습니다 (테스트하지 않아도 코드는 명백히 정확합니다) 해결하기가 불가능하거나 (어떤 이유로 경로를 칠 수없는 경우), 또는 현재 또는 향후 수정 후에 큰 버그가있는 위치 일 수 있습니다.
어떤 방법으로 철자 검사는 비교할 수 있습니다 : 어떤 것은 철자 검사를 "통과"하고 사전에있는 단어와 일치하는 방식으로 철자가 틀릴 수 있습니다. 또는 올바른 단어가 사전에 없기 때문에 "실패"할 수 있습니다. 또는 그것은 말도 안되고 지나칠 수 있습니다. 맞춤법 검사는 교정에서 누락되었을 수있는 장소를 식별하는 데 도움이되는 도구이지만, 완벽하고 정확한 교정을 보장 할 수없는 것처럼 코드 범위는 완전하고 정확한 테스트를 보장 할 수 없습니다.
물론 맞춤법 검사를 사용하는 잘못된 방법은 제안 된 모든 제안과 함께 진행되는 것으로 유명합니다. 따라서 ewe가 대출을 떠난 경우 더킹 현상이 악화됩니다.
코드 적용 범위를 사용하면 특히 98 %에 가까운 경우 나머지 경로에 도달하도록 케이스를 채우려는 유혹을받을 수 있습니다.
그것은 맞춤법 검사와 함께 오른쪽 단어가 날씨이거나 모든 단어가 적절한 단어라는 것을 의미합니다. 결과는 더킹 혼란입니다.
그러나 커버되지 않은 경로가 실제로 필요한 테스트를 고려하면 코드 커버리지 도구가 작업을 수행했을 것입니다. 정확성을 약속하는 것이 아니라 수행해야 할 작업 중 일부 를 지적 합니다.
경로 적용 범위는 필요한 모든 기능이 구현되었는지 여부를 알려줄 수 없습니다. 기능을 남겨 두는 것은 버그이지만 경로 범위는 감지하지 않습니다.
이 문제의 일부는 100 % 적용 범위만으로도 단일 실행 후 코드가 올바르게 작동한다는 것만 보장한다는 것 입니다. 메모리 누수와 같은 일부 버그는 단일 실행 후 명백하지 않거나 문제를 일으킬 수 있지만 시간이 지남에 따라 응용 프로그램에 문제가 발생할 수 있습니다.
예를 들어, 데이터베이스에 연결하는 응용 프로그램이 있다고 가정하십시오. 아마도 한 가지 방법으로 프로그래머는 쿼리가 완료되면 데이터베이스에 대한 연결을 닫는 것을 잊어 버립니다. 이 방법에 대해 몇 가지 테스트를 실행하고 해당 기능의 오류를 찾을 수는 없지만,이 특정 방법이 연결을 닫지 않았고 열린 연결이 있어야하기 때문에 데이터베이스 서버가 사용 가능한 연결이없는 시나리오로 실행될 수 있습니다. 이제 시간이 초과되었습니다.
times_two(x) = x + 2
, 이것은 테스트 스위트에 의해 완전히 다루어 질 assert(times_two(2) == 4)
것이지만 여전히 버그가있는 코드입니다! 메모리 누수 필요 없음 :)
다른 대답은 훌륭하지만 "프로그램을 통과하는 모든 경로가 테스트됩니다"라는 조건 자체가 모호하다는 점을 덧붙이고 싶습니다.
이 방법을 고려하십시오.
def add(num1, num2)
foo = "bar" # useless statement
$global += 1 # side effect
num1 + num2 # actual work
end
당신이 주장하는 테스트를 작성한다면 add(1, 2) == 3
, 코드 커버리지 도구는 모든 라인이 실행되었음을 알려줍니다. 그러나 실제로 글로벌 부작용이나 쓸모없는 과제에 대해서는 아무것도 주장하지 않았습니다. 그 줄은 실행되었지만 실제로 테스트되지 않았습니다.
돌연변이 테스트는 이와 같은 문제를 찾는 데 도움이됩니다. 돌연변이 테스트 도구에는 코드를 "돌연변이"하고 테스트가 여전히 통과하는지 확인하기 위해 미리 결정된 방법 목록이 있습니다. 예를 들면 다음과 같습니다.
+=
에 -=
. 이 돌연변이는 테스트 실패를 유발하지 않으므로 테스트가 전체 부작용에 대해 의미있는 것을 주장하지 않는다는 것을 증명합니다.본질적으로 돌연변이 테스트는 테스트 를 테스트 하는 방법 입니다. 그러나 가능한 모든 입력 세트로 실제 기능을 테스트하지 않는 것처럼 가능한 모든 돌연변이를 실행하지 않으므로 다시 제한됩니다.
우리가 할 수있는 모든 테스트는 버그없는 프로그램으로 나아가는 휴리스틱입니다. 완벽한 것은 없습니다.
음 ... 네, 실제로 모든 경로가 프로그램을 "통과"하면 테스트됩니다. 그러나 이는 모든 변수를 포함하여 프로그램이 가질 수있는 모든 가능한 상태의 전체 공간을 통한 모든 가능한 경로를 의미합니다. 아주 간단한 정적으로 컴파일 된 프로그램 (예 : 이전 포트란 수 크 런처)도 가능하지는 않지만 적어도 상상할 수는 없습니다. 정수 변수가 두 개인 경우 기본적으로 포인트를 연결하는 모든 가능한 방법을 처리합니다 2 차원 그리드; 실제로 Traveling Salesman처럼 보입니다. 들어 n은 변수, 당신은 상대하고있는 N 차원 공간 때문에 실제 프로그램에 대한 작업이 완전히 untractable입니다.
더 나쁜 : 심각한 물건, 당신은 할 수 없습니다 원시 변수의 단지 고정 된 수 있지만, 함수 호출에 즉시 변수를 만들거나 튜링 완전한 언어로 가능한 한, 가변 크기 변수 ... 나처럼 아무것도. 이로 인해 상태 공간이 무한 차원으로 만들어져 엄청나게 강력한 테스트 장비가 제공되는 경우에도 모든 적용 범위에 대한 모든 희망을 산산조각냅니다.
사실 ... 사실은 그렇게 어둡지 않습니다. 이다 수 proove 올바른로 전체 프로그램을,하지만 당신은 몇 가지 아이디어를 포기해야합니다.
첫째 : 선언적인 언어로 전환하는 것이 좋습니다. 어떤 이유로 든 명령형 언어는 항상 가장 인기가 높았지만 실제 상호 작용과 알고리즘을 혼합하는 방식 은 "올바른" 이라는 의미 를 말하기조차 매우 어렵습니다 .
순전히 기능적인 프로그래밍 언어 에서 훨씬 더 쉽습니다 . 수학 함수 의 흥미로운 흥미로운 속성 과 실제로 말할 수없는 퍼지 된 실제 상호 작용을 명확하게 구분합니다 . 함수의 경우“올바른 동작”을 지정하는 것이 매우 쉽습니다. 가능한 모든 입력 (인수 유형에서)에 해당하는 원하는 결과가 나오면 함수가 올바르게 동작하는 것입니다.
자, 당신은 그것이 여전히 다루기 어려운 것이라고 말합니다. 결국 모든 가능한 논쟁의 공간은 일반적으로 무한 차원입니다. 사실 – 단일 기능의 경우, 순진한 적용 범위 테스트조차 명령형 프로그램에서 기대했던 것보다 훨씬 더 앞선 것입니다! 그러나 게임을 변화시키는 놀라운 강력한 도구가 있습니다 : 보편적 정량 / 매개 변수 다형성 . 기본적으로 이것은 매우 일반적인 종류의 데이터에 함수를 작성할 수있게하며, 간단한 데이터 예제에서 작동하면 가능한 모든 입력에 대해 작동 할 수 있습니다.
적어도 이론적으로는. 이 유형을 완전히 제시 할 수있는 너무 일반적인 유형을 찾는 것은 쉽지 않습니다. 일반적으로 종속 유형 언어 가 필요하며 사용하기가 어려운 경향이 있습니다. 그러나 파라 메트릭 다형성만으로 기능적 스타일로 작성하면 이미 "보안 수준"이 크게 향상됩니다. 모든 버그를 반드시 찾아야하는 것은 아니지만 컴파일러는이를 잘 숨겨야합니다.