SW/테스팅

SW 테스팅 : 결함, 에러, 실패 : 개념, 기법, 관계, 사례 : KMOOC

얇은생각 2020. 10. 3. 07:30
반응형

소프트웨어의 오류 결함

 

SW 테스팅 : 결함, 에러, 실패 : KMOOC

 

흔히 소프트웨어 오류, 결함이라고 우리말로는 하나의 개념으로, 하나의 단어로 많이 부르는데요. 실제로 기술적인 맥락을 따지면 Fault Error, Failure라는 세 가지 개념을 구별합니다. 물론 테스팅의 목적은 세 가지 종류의 결함 자체를 모두 다 없애는 것입니다. 

용어 자체의 정의를 보면 Fault는 소스코드 자체에 있는 비정상적인 요소이고 Error로 연결될 수 있는 요인입니다. 그다음 단계인 Error는 실제 실행 중에 소스코드에 존재하는 Fault가 실행이 돼서 Failure로 연결될 수 있는 요인입니다. 마지막으로 Failure는 소스코드에 존재하는 Fault가 실행돼서 에러상태를 낳았습니다. 이것이 프로그램 외부에서 관찰할 수 있는 상태까지 이어지는 것을 Failure라고 합니다.

여기서 중요한 것은 Fault Error 사이에 소스코드에 존재하는 오류 그리고 실행했을 때의 오류라는 개념이 구별된 것입니다.

 

 

소프트웨어 품질을 조사하는 기법

소프트웨어 품질에 대해서 조사하는 기법에 크게 두 가지 종류가 있다는 사실을 염두에 두어야 한다. 하나는 동적인 분석이고 또 하나는 정적인 분석입니다. 동적인 분석이라고 할 때는 분석의 대상이 프로그램의 실행 결과인 경우를 의미합니다.

반대로 정적인 분석이라고 할 때는 프로그램을 실행하지 않은 상태에서 프로그램 소스코드만을 재료로 삼아서 하는 분석을 정적 분석이라고 합니다. 테스팅의 경우에는 동적인 분석이기 때문에 프로그램을 실제로 실행하고 그 결과가 우리가 예측하는 행동과 부합하는지를 확인하는 것이 테스팅 활동입니다.

여기에 반대로 정적 분석은 프로그램이 옳은지를 소스코드의 내용만 보고 실행하지 않은 상태로 조사하는 것을 정적인 분석입니다.

 

 

IEEE 표준 Fault, Error 그리고 Failure 사이의 관계

이것은 IEEE에서 표준으로 제시하고 있는 Fault, Error 그리고 Failure 사이의 관계입니다. 화살표로 각각의 개념이 다음 개념으로 어떻게 연결되는지가 나와 있습니다.

일단 소스코드에 오류가 존재하면 Fault가 존재합니다. 그것이 실행을 통해서 잘못된 프로그램 상태로 연결됐을 경우에는 Error 상태가 됩니다. 그리고 Error가 사용자가 관측할 수 있는 외부적인 오동작으로 발현했을 때에 Failure라고 부릅니다.

 

IEEE 표준 Fault, Error 그리고 Failure 사이의 관계

 

Fault Error, Failure가 이렇게 3단계로 연결되는 것은 설명을 통해서 어느 정도 이해하였습니다. 중요한 점은 아래쪽으로 연결되는, 그러니까 성공적인 프로그램 실행으로 연결되는 화살표가 마저 존재합니다. 따라서 프로그램 소스코드에 Fault가 있어도 실행이 성공할 수 있습니다.

마찬가지로 Fault를 실행해서 Error 상태가 됐더라도 프로그램이 정상적인 동작으로 복귀한 뒤에 성공적으로 실행을 마칠 수 있습니다. 에러가 Failure로 무조건 연결된다는 것은 아니라는 점입니다.

 

 

IEEE 표준 Fault, Error 그리고 Failure 사이의 관계2

 

화면에 복잡하지 않은 매우 간단한 C 프로그램 함수가 적혀 있습니다. 이 함수는 Int*rgInt라는 Array를 받아서 Array 값을 한 칸씩 옆으로 옮겨서 회전하는 것이 기능입니다. 첫 번째 칸부터 마지막 칸까지는 한 칸씩 움직이는데, 첫 번째 칸에 있던 값을 반대쪽으로 되돌려서 넣는 부분이 빠져 있는 것이 소스코드에 있는 오류입니다.

그러면 소스코드에 Fault가 있는 것이 확실하지만, 이것이 항상 에러로 연결되는지 보기 위해서 테스트 입력을 가정해보겠습니다. 첫 번째 입력으로 비어있는 Array, RgInt에 아무 값도 없는 상태의 Array를 입력으로 주고 Size 0이라고 했을 경우에 이번에는 Error가 발생하지 않습니다.

소스코드 안에 Fault가 있지만 Error가 발생하지 않는 이유는 Size 0이기 때문에 RotateLeft라는 함수 안에 있는 Loop가 실행되지 않습니다. 따라서 Rotate Array 자체가 없습니다. 그렇기 때문에 Fault가 있더라도 전혀 이상한 상태가 발현하지 않고, 따라서 Error는 관측되지 않습니다.

그럼 같은 프로그램에 대해서 새로운 테스트 입력을 가정해보겠습니다. 이번에는 입력으로 사용한 Array rgInt [0, 1], 두 개의 Integer 값을 가지고 있는 Array인데, C언어에 친숙하신 분들은 C에서 사용하는 배열이 메모리의 임의의 구간에 인접한 주소를 모아놓은 것입니다.

그렇기 때문에 어떤 가정을 할 거냐 하면, Rgint Array [0, 1] 뒤에 우연히 0이 있다고 가정하겠습니다. 그리고 Size 값은 2라고 할 때 Output Loop Array를 왼쪽으로 한 칸씩 옮기기 때문에 1이 되고 그다음에 Array 외부에 있는, 실제로는 읽어서 안 되는 값인 0, 우연히 맞아떨어진 값인 0 Array 안으로 복사해오기 때문에 출력이 [1, 0]이 됩니다.

그럼 이 경우에는 Error가 발생했지만 Failure가 발생하지 않습니다. 우연한 기회에 그렇게 된 것입니다. Error가 발생하려면 반환한 Array 값이 RotateLeft의 결과와 달라야 합니다. 우연히 Array 바로 다음 주소에 0 값이 있었기 때문에 반환된 값이 [1, 0]으로 옳은 값이 돼서 심지어 Error가 있었음에도 불구하고 Failure로 관찰되지 않는 결과를 낳습니다.

정말 이 코드에 오류가 있다는 것을 외부에서 관찰해서 Failure로 인지하기 위해서는 세 번째 테스트 입력이 필요합니다. 보시다시피 Rgint의 값이 [0, 1]. 그리고 이번에는 그다음 번 메모리 주소에 전혀 다른 값인 66이 들어 있다고 가정하겠습니다. 그러면 RotateLeft 함수가 실행된 뒤에 Array는 한 칸씩 왼쪽으로 옮겨져서 1이 되고, Array 바깥에서 읽어온 값 66 Array 안으로 들어옵니다. 따라서 이 경우에는 반환된 값을 보면 프로그램에 오류가 있다는 것을 테스팅을 통해서 인지할 수 있습니다.

이 경우에는 Failure가 발생했다고 합니다. 여기서 재미있는 점은 사용자가 인지하는 프로그램의 잘못된 행동은 오직 Failure뿐이라는 것입니다. 그렇기 때문에 같은 Failure에 대해서 사용자가 Fault라고 인지하는 프로그램 코드 상의 오류 또한 다를 수 있습니다.

 

 

RotateLeft 예시에 대한 가능한 Fault의 정의

예를 들어 여기에 몇 가지 가능한 Fault의 정의를 적어놨습니다. Rgint Array를 읽는 Loop 자체가, Array Bound 바깥을 일는 것 자체가 오류라고 이야기할 수도 있고 틀린 말이 아닙니다. 하지만 두 번째 항목을 보시면 Array의 첫 번째를 값을 다른 데로 옮겨주지 않는 것 자체가 오류입니다.

이것도 해당 오류에 대한 정당한 기술 중에 하나입니다. 세 번째로는 Rgint [0], 그러니까 첫 번째 값을 나중에 쓰기 위해서 저장을 안 해놓은 것이 오류라고 했을 때 이것도 마찬가지입니다. 이 프로그램을 올바르게 고쳐가기 위해서 각각 다 필요한 개념입니다. 

똑같은 Failure에 대해서도 소스코드 상에 오류가 무엇이라고 진단하는지는 사람에 따라서 그리고 Failure에 대한 이해에 따라서 다를 수 있다는 점입니다. 그리고 이게 더 재미있는 이유는 Fault가 뭐라고 진단했느냐에 따라서 실제 소스코드에 어떤 수정을 가하는지가 달라질 수 있습니다. 따라서 같은 Failure를 관찰했음에도 불구하고 코드를 수정하는 방법이 서로 다를 수 있습니다.

 

 

Test Input, Test Oracle

Test Input, 즉 테스트 입력이라고 할 때는 테스팅을 목표로 하고 주어진 프로그램을 실행하기 위해서 사용하는 입력 값 전체를 지칭합니다. Test Oracle은 방금 같은 경우에 RotateLeft라는 함수의 의미를 이해하고 주어진 테스트 입력을 사용했을 때 반환된 출력 값이 맞는지 틀리는지를 결정하는 모든 메커니즘입니다.

따라서 Test Oracle은 사람이 될 수도 있습니다. 프로그램에 따라서는 자동으로 관찰한 실행 값이 맞는지 틀린지를 결정하는 자동화된 Oracle을 작성할 수도 있습니다. 하지만 일반적으로, Test Oracle이야말로 테스팅 과정에서 가장 많은 비용을 발생시키는 부분입니다.

왜냐하면 대부분의 경우에 복잡한 프로그램 로직에 대해서 반환된 결과 값이 맞는지 틀린지 완벽하게 판별할 수 있는 것은 사람뿐이기 때문에 그렇습니다.

 

 

Test Case, Test SUite, Test Effectiveness

Test Case라는 표현을 많이 쓰는데, 이것은 테스트 입력만을 가리켜서는 안 됩니다. 테스트 입력과 주어진 입력에 대해서 반환된 결과 값이 맞는지 틀리는지 결정할 수 있는 메커니즘, Oracle까지를 포함했을 때 둘을 합해서 Test Case라고 부릅니다.

 

Test Case, Test SUite, Test Effectiveness

 

더 나아가서 Test Suite Test Case의 집합입니다. 프로그램을 하나의 테스트 입력과 하나의 Oracle만 가지고 모든 기능을 보통 검증할 수 없습니다. 다양한 기능 그리고 다양한 측면에 대한 테스트를 작성한 뒤에 이 Test Case들을 모두 모아놓은 것을 Test Suite이라고 부릅니다.

Test Effectiveness라는 표현 또한 많이 씁니다. 자기가 작성한 Test Suite 또는 Test Case가 실제로 프로그램 상에 오류가 있을 때 찾아낼 확률이 얼마나 되는지. 더 높으면 높을수록 더 효과적인 테스트라고 부릅니다.

 

 

Testing or Debugging

그리고 마지막으로 보통 Testing이나 Debugging이라는 용어를 명확한 구별 없이 섞어서 쓸 수 있습니다. Testing이라고 할 때는 프로그램 코드 안에 Fault가 있다는 것을 밝혀내는 것까지의 행동을 Testing이라고 합니다.

 

Testing or Debugging

 

Debugging Testing 결과를 바탕으로 소스코드 자체의 품질을 향상시키고 결함을 제거하는 행위를 Debugging이라고 합니다. 따라서 실제 업무 상에 있어서는 Testing Debugging은 밀접하게 연관되어 있습니다.

반응형