테스트와 디버깅
본문윤년을 판정하는 함수를 '4로 나누어떨어지면 윤년'이라고 짰다. 2024년을 넣으니 윤년, 2023년을 넣으니 평년이 나와 맞았다. 무작위로 고른 연도 100개를 넣어 보아도 대부분 맞았다. 그런데 이 함수는 틀렸다. 100으로 나누어떨어지는 해는 400으로도 나누어떨어져야 윤년이므로, 1900년과 2100년은 평년이다. 평범한 입력은 버그를 거의 드러내지 않는다. 어떤 입력으로 시험해야 버그가 드러나고, 드러난 버그는 어떻게 추적할까?
4.4.1버그는 경계에 숨는다
프로그램의 버그는 대개 평범한 입력이 아니라 조건이 바뀌는 경계에서 생긴다. 윤년 함수의 경계는 4의 배수, 100의 배수, 400의 배수이다. 리스트를 다루는 함수의 경계는 빈 리스트, 원소가 하나인 리스트, 첫 원소와 마지막 원소이다. 점수에 따라 등급을 매기는 함수라면 90점이 A인지 B인지 같은 구간의 끝점이 경계이다. 그래서 시험할 입력은 무작위로 고르기보다, 조건을 하나하나 살펴 경계와 그 바로 옆의 값을 일부러 고른다. 이것이 경계값 시험이다.
입력 가운데 비율 \(f\)에서만 틀리는 버그가 있을 때, 무작위 입력 \(n\)개로 시험해 하나라도 틀린 입력을 만날 확률은 \[1 - (1 - f)^n\]이다. \(f\)가 작은 버그는 시험을 아주 많이 해야 드러난다. 그런 버그는 대개 경계에 모여 있으므로, 경계값을 직접 고르면 시험 몇 개로 찾는다.
그림 1에서 시험 수를 바꾸어 보자. 틀린 연도는 1만 년 가운데 75개, 곧 0.75%이다. 무작위 연도 100개로 시험하면 버그를 찾을 확률이 약 53%로 동전 던지기와 비슷하다. 300개를 시험해도 약 90%이다. 그런데 경계값인 1900을 하나만 넣으면 함수가 '윤년'이라 답해 틀린 것이 바로 드러난다. 2000(400의 배수, 윤년)과 2100(평년)을 함께 넣으면 고친 함수가 맞는지도 확인할 수 있다.1
4.4.2디버깅
시험에서 틀린 결과가 나오면 그 원인을 찾아 고치는 디버깅을 한다. 먼저 틀린 결과를 늘 되풀이해 낼 수 있는 가장 작은 입력을 찾는다. 그다음 그 입력으로 프로그램을 따라가며, 변수의 값이 예상과 처음 달라지는 곳을 찾는다. 중간중간 값을 출력해 보거나, 디버거로 한 줄씩 실행하며 변수를 살핀다. 의심되는 범위를 반씩 좁혀 가면 긴 코드에서도 빠르게 찾는다. 원인을 고친 뒤에는 그 입력을 시험 목록에 영원히 남겨, 같은 버그가 다시 생기지 않는지 확인한다. 이것을 회귀 시험이라 한다.
4.4.3예제 풀이
0.75%의 입력에서만 틀리는 버그를 무작위 시험으로 95% 넘는 확률로 찾으려면 시험이 몇 개 필요한가? 윤년 규칙의 조건을 모두 확인하는 경계값 시험은 몇 개면 충분한지 정하고, 고친 함수 y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)를 그 시험으로 확인하여라.
\(1 - 0.9925^n > 0.95\)에서 \(0.9925^n < 0.05\)이고, 양변에 로그를 취하면 \(n > \dfrac{\ln 0.05}{\ln 0.9925} \approx \dfrac{-3.00}{-0.00753} \approx 398\)이다. 무작위 시험이 약 400개 필요하다.
경계값 시험은 규칙의 네 경우에서 하나씩, 곧 4의 배수가 아닌 해(2023), 4의 배수이지만 100의 배수가 아닌 해(2024), 100의 배수이지만 400의 배수가 아닌 해(1900), 400의 배수(2000)의 네 개면 된다. 고친 함수는 2023에 거짓, 2024에 참, 1900에 \(100\)으로 나누어떨어지고 400으로 나누어떨어지지 않아 거짓, 2000에 참을 돌려주어 네 시험을 모두 통과한다. 즉 시험 네 개가 무작위 시험 400개보다 확실하다. 그래서 시험은 많이 하는 것보다 조건의 경계를 빠짐없이 고르는 것이 중요하다.
아래 탐구 카드에서 함수의 경계값을 찾아 시험을 설계하고, 버그를 추적해 고쳐 보자.
- 1900년을 윤년으로 잘못 처리한 버그는 실제로 1980년대 표 계산 프로그램 로터스 1-2-3에 있었다. 엑셀은 그 파일과 호환되도록 일부러 같은 버그를 남겨 두었고, 지금도 엑셀에는 존재하지 않는 1900년 2월 29일이 있다.
버그는 대개 조건이 바뀌는 경계에 숨어 있어, 드물게 틀리는 버그는 무작위 시험으로 \(1 - (1-f)^n\)의 확률로만 드러난다. 그래서 조건마다 경계와 그 옆의 값을 골라 시험하고, 틀린 결과가 나오면 가장 작은 입력으로 되풀이하며 범위를 좁혀 원인을 찾은 뒤 회귀 시험으로 남긴다. 처음 질문의 윤년 함수는 2024와 2023만으로는 믿을 수 없고, 1900과 2000 같은 경계값까지 넣어 보아야 버그가 드러난다.