SW/테스팅

SW 테스팅 : 완벽한 테스팅은 가능한 것일까? : KMOOC

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

완벽한 테스팅의 불가능성

완벽한 테스팅이 불가능하다고 이야기하는 게 조금 이상하다고 느낄 수 있다. 테스팅에는 분명히 이론적인 한계가 있다. 오히려 이 한계가 무엇인지 정확히 이해할 때만 올바른 테스팅 기법을 적재적소에 사용할 수 있다.

 

 

 

Undecidable Problem

본질적으로 테스팅이 묻는 질문, 내가 작성한 소프트웨어가 올바르게 작동하는가?’라는 질문 자체가 답하기 어려운 한계를 가지고 있다. 이 질문에 대해서 정말 완벽한 답을 하려면 상당히 깊은 이론적인 논의를 해야 된다.

컴퓨터 알고리즘 그리고 무엇이 계산 가능한지에 대한 이론을 깊게 공부하시면 어떤 종류의 계산 문제는 알고리즘이 답을 하는 것 자체가 불가능한 문제들이 있다. undecidable한 문제라고 하고 아니면 결정 불가능한 문제라고 한다.

내 프로그램이 가능한 입력에 대해서 특정한 속성을 가지는지 가지지 않는지는 이론적으로 결정 불가능한 문제이다. 임의의 코드를 주고 완벽한 증명을 통해서내 프로그램은 절대 오작동을 하지 않는다.’라는 자동화된 분석이 불가능한 것이다.

이것은 명백한 컴퓨터과학의 이론적인 한계이기 때문에 이 점을 우회하려고 하는 시도는 아무런 가치가 없다. 아까 프로그램과 속성의 관계를 분석하는 것이 연역적인 분석이라고 한다면, 테스팅은 실제 입력을 가지고 프로그램을 실행해보는 것이기 때문에 귀납적인 분석이라고 할 수 있다.

‘내가 이러이러한 입력으로 프로그램을 실행해봤을 때 문제가 없었다. 따라서 나는 일반적으로 문제가 없을 것이라고 생각한다.’라는 것이다.

 

 

 

Exhaustive Testing

귀납적인 방법으로 프로그램 품질을 완벽하게 보장하는 나의 방법은 Exhaustive Testing, 말해서 가능한 테스트 입력을 모두 사용하는 것입니다.

그럼 말 그대로이 프로그램에 들어갈 수 있는 모든 입력을 내가 다 실행해보았는데 각각의 입력에 대해 한 번도 문제가 없었기 때문에 내 프로그램은 없다.’라고 절대적으로 단언할 수 있다.

매우를 간단한 방법입니다. 복잡한 어떤 이론도 사용할 필요가 없고, 프로그램이 주어졌을 때 이 프로그램에 대해서 사용할 수 있는 모든 입력을 하나씩 실행해보겠다는 굉장히 간단한 방법이 존재합니다.

 

 

 

Triangle Program

숫자 3개가 주어졌을 때 이 3개의 숫자를 각각의 변의 길이로 하는 삼각형을 만들 수 있는지 없는지를 결정하는 문제입니다. 따라서 정수 3개가 입력으로 주어지면이 정수 3개로는 정삼각형을 만들 수 있습니다.’ 아니면이등변삼각형을 만들 수 있습니다.’

어떤 경우에는직각삼각형을 만들 수 있습니다.’를 체크할 수도 있고요. 아니면이 숫자 3개로는 아무런 삼각형도 만들 수 없습니다.’라는 결론을 내릴 수도 있다.

아무튼 그렇게 복잡하지 않은 프로그램이에요. 프로그램 기초를 배울 때 전산학과에 입학한 1학년 학생들이 숙제로 할 정도의 간단한 프로그램이고 실제로 그렇게 길이가 길지 않다.

 

 

32bit Integer 3개를 받아서 Triangle Program을 만든다고 했을 때 가능한 입력의 개수는 많다. 컴퓨터에서 숫자를 어떻게 표현하는지 내부 구조를 이해하시는 분들은 계산하실 수 있겠지만, 32bit 정수 변수가 가질 수 있는 값은 ­2 31승부터 +2 31­1만큼입니다.

그런데 이 프로그램은 숫자를 3개를 받는다. 그래서 이 가능한 3개의 32bit Integer의 조합을 모두 계산해보면, 대략 8 28승만큼의 가지 수가 나온다.  두 가지 크기를 대략적으로 비교해봤는데, 왼쪽에 있는 회색 원이 지금 현재 추산하기로 우주에 존재하는 별의 개수입니다.

반대로 오른쪽에 있는 회색 원이 방금 말씀드린 32bit 정수 3개를 입력으로 받는 프로그램의 가능한 입력의 개수이다. 이걸 아무리 빨리 실행해본다고 하더라도 우리 생 안에이 프로그램의 모든 입력을 실행해봤더니 옳다.’라는 결론은 내릴 수 없다.

 

 

“이렇게 간단한 프로그램이라고 할지라도 모든 입력을 시도해봄으로써 내 프로그램에 오류가 없다는 것을 밝히는 것은 불가능하다.” 이걸 우리말로 옮겨보면테스팅은 내 프로그램에 오류가 있다는 건 증명할 수 있을지 몰라도 없다는 건 증명할 수 없다.’

테스팅 했을 때 오류를 발견했으면 그것은 프로그램에 오류가 있었다는 명백한 증거지만, 반대로 내가 테스팅을 아무리 열심히 해봤더라도 모든 입력을 다 테스트에 사용할 수 없기 때문에 어딘가는나는 실행해보지 않았지만 오류를 일으키는 입력이 있을 수 있다.’

 

 

다익스트라와 테스팅 사이의 관계

다익스트라와 테스팅 사이의 관계

 

일단 코드는 한 줄이다. 두 개의 Integer를 받아서 x/y 값을 return한다. 그런데 테스팅을 통해서 오류의 존재를 논의하고 싶기 때문에 실제 테스트 입력 값을 생각해야 한다. 첫 번째 테스트 입력은 x=2, y=1이다. 그럼 이 경우에 2 1로 나누는 데는 아무런 문제가 없죠. 그래서 Error도 발생하지 않고, Failure도 관찰되지 않고, 문제가 없다.

두 번째 테스트 입력 값은 x=1, y=2. 그럼 이 경우에도 1÷2 Return하게 된다. 이 경우에는 약간의 문제가 있을 수 있는데, 왜냐하면 x y가 둘 다 Integer이기 때문에 1÷2를 하면 정수형으로 표현했을 때는 0이 된다. 그래서이게 의도한 계산이냐?’라는 질문을 할 수 있겠지만, 프로그램 자체에는 문제가 없다.

세 번째, 가장 심각한 Input x=1이고 y=0일 때인데, 아마 아까 코드를 보고 오류가 있다고 생각하셨던 분들은 이런 예를 생각한다. y=0이면 0으로 나누는 건 불가능하기 때문에 Division By Zero 오류가 난다. 그럼 이 경우에프로그램에 오류가 있었느냐?’ 그다음에테스팅으로 그걸 찾아낼 수 있었느냐?’라는 질문을 다시 해볼 수 있다.

과연테스팅은 오류가 있는 건 찾을 수 있지만, 오류가 없다는 걸 보일 수는 없다.’라고 했던 말이 맞는지 생각해보면 이론적으로 틀린 말은 아니다. 분명히 테스팅은 모든 입력에 대해서 수행할 수 없기 때문에 테스팅을 통해서 증명을 하는 것은 불가능하다.

하지만 반대로 고려해봐야 될 건 테스팅의 반대급부, 그러니까 연역적인 분석을 통해서 프로그램에 대해 어떤 속성을 증명하는 것 또한 굉장히 어렵다. 이 점을 염두에 두면 테스팅이 왜 어려운지에 대한 논의로 나아갈 수 있다. 테스팅은 불가능하고, 즉 가능한 모든 입력을 테스팅에 사용하는 것은 불가능하다.

그렇기 때문에 테스팅을 통해서 할 수 있는 것은 매우 작은 수, 전체 입력공간을 생각했을 때 매우 작은 숫자의 입력만을 샘플링해서 사용하는 것이다. 테스팅이 어려운 이유는 효과적인 샘플링을 하는 방법이 명백하게 알려져 있지 않기 때문이다.

 

 

그럼에도 테스트를 해야하는가?

그러면 Dijkstra가 말했던 것처럼 프로그램에 오류가 없다는 것을 증명도 못하는데 왜 아직도 테스팅을 해야 할까? 

 

그럼에도 테스트를 해야하는가?

 

첫 번째 비행기는 항법 소프트웨어를 아무도 이론적으로 증명해본 적은 없지만, 실제로 몇 회에 걸친 시험비행을 통과했다.  두 번째 비행기는 실제로 아무도 날려보지는 않았지만 거기에 탑재한 항법 소프트웨어를 누군가 이론적으로 그리고 정적으로 맞다고 분석을 했다.

실제 내가 타야 하는 비행기라면 아마 이론적인 증명보다는 실제로 비행에 참여해본, 시험비행을 해본 비행기를 고를 확률이 높을 것이다.  그 이유는, 소프트웨어가 이론적으로 맞는 것만 해도 기능적인 옳고 그름이 끝나지 않기 때문이다.

 

 

항법 소프트웨어

항법 소프트웨어라는 건 기계의 나머지 부분, 비행기의 나머지 부분과 함께 작동을 잘하는지를 검증해야 되는 것이다. 따라서 이걸 총체적으로 관측할 수 있는 것은 증명보다는 실제 시험비행이라는 뜻이다.

어떤 면에서는 자기가 작성한 소프트웨어를 실제 실행을 통해서 맞는지 안 맞는지 관찰하려고 하는 건 인간의 기본적인 본능에 가까운 것이다. 이론적으로만 답을 얻기보다는 실제 실행해봤을 때 문제가 없었다는 것을 관측하려는 욕구가 있다.

그리고 어떤 종류의 문제는 오직 테스팅을 통해서만 검증할 수가 있다. 예를 들어 내가 만든 모바일 앱이 휴대폰 배터리를 너무 급격하게 소모시키지는 않는지. 이런 종류의 테스팅 질문은 이론적으로 증명할 수 없다. 왜냐하면 실제 배터리가 무엇인지는 수학이 기술할 수가 없기 때문이다.

 

 

 

Test Oracle

그리고 입력의 방대함이 테스팅의 어려움으로 연결되는 데에는 마찬가지로 Test Oracle이라는 개념이 다시 한 번 등장한다. 아까 예로 들었던 코드에서 우리가 바로 뭔가가 잘못됐다는 걸 알 수 있는 이유는 컴퓨터는 y=0으로 뒀을 때 나누기를 하지 못한다는 점을 알고 있기 때문이다.

이것은 어떤 컴퓨터에서 실행하더라도 Division By Zero Error이기 때문에 그렇다. 그런데 이렇게 명백한 그리고 누구나 다 합의할 수 있는 Error가 아니고 프로그램이 원하는 결과 값보다 약간만 다른, 약간만 틀린 결과를 낸다고 할 때는 누가 과연 이것을 제대로 검증할 수 있는지 생각해볼 수도 있다.

 

Test Oracle

 

과연 실행할 모든 입력에 대해서 자동으로 옳고 그름을 판별해줄 수 있는 Oracle이라는 메커니즘을 만들 수 있다. 최대한 많은 입력을 샘플링해서 테스팅을 통해서 품질을 검증하고 싶은 욕구와 테스팅을 많이 하려고 하면 할수록 Oracle을 개발하는 데 드는 비용이 올라간다는 비용 문제 사이의 긴장관계를 이해할 수 있다. 

어떤 종류의 Oracle은 매우 값이 쌉니다. 예를 들어서 무한루프라든지내 프로그램이 종료하지 않는다. 아니면 방금 봤던 거와 같이 Division By Zero, ‘절대 할 수 없는 연산을 시도한다.’라는 것은 자연적으로 따라오는 매우 값싼 Oracle임에 비해 기능적인 올바름을 판별하는 Oracle이 작성하기가 매우 어렵기 때문에 가능하면 많은 입력을 보려는 테스팅의 목표와 가능하면 cost를 줄이려는 소프트웨어 개발상의 목표가 Oracle를 통해서 충돌하게 되는 것이다.

반응형