전사 — 디버깅에서 테스트로, 체계화 전야의 30년

1950년대, 컴퓨터 여명기의 프로그래머에게 「디버깅」과 「테스트」는 같은 것이었다. 1968년 NATO 소프트웨어 공학 회의가 들이민 「소프트웨어 위기」, 데이크스트라의 「테스트는 버그의 부재를 증명할 수 없다」는 경구, 그리고 1979년 글렌포드 마이어스 『The Art of Software Testing』이 이룬 최초의 체계화까지. 「테스트」가 독립된 지적 영역이 되기까지의 30년을 따라간다.

testinghistorydebuggingwaterfalldijkstrasoftware-engineering

「테스트」라는 말이 아직 의미를 갖지 않았던 시대

오늘날 「소프트웨어 테스트」라고 불리는 활동은 처음부터 독립된 공정으로 존재했던 것이 아니다. 1950년대, 컴퓨터 여명기에는 「프로그램을 돌려 보고, 안 되면 고친다」는 디버깅(debugging)과, 「프로그램이 올바르게 동작하는지 확인한다」는 테스트(testing)가 거의 구별되지 않았다.

이 글에서는 이 미분화된 시대부터, 1979년 글렌포드 마이어스의 저서 『The Art of Software Testing』으로 테스트가 하나의 지적 영역으로 체계화되기까지의 약 30년을 따라간다. 시리즈 overview에서 제시한 중심 물음 — 「프로그램이 올바르다는 것을 어떻게 확인하는가」 — 가 아직 누구에게도 뚜렷한 윤곽을 부여받지 못했던 시대의 이야기다.

여정은 크게 세 개의 매듭을 지난다. 먼저 1960년대, 소프트웨어의 규모 확대가 「소프트웨어 위기」로서 사회 문제화된다. 다음으로 1970년 전후, 워터폴이라는 공정관 안에 테스트가 자리매김되고, 동시에 데이크스트라가 그 공정관의 한계를 날카롭게 찌른다. 그리고 1979년, 마이어스의 저서가 테스트를 처음으로 한 권의 이론으로 체계화한다. 이 세 도약을 하나씩 따라가 보자.

1947년, 나방이 끼인 이야기 — 「버그」라는 말의 유래

본격적인 전사에 들어가기 전에, 하나의 유명한 일화를 짚고 넘어가고 싶다. 1947년, 하버드 대학의 Mark II 릴레이식 계산기가 오작동을 일으켜 조사해 보니, 릴레이 접점에 실제 나방(moth)이 끼어 있었다는 사건이 있었다. 그레이스 호퍼가 이끌던 팀은 이 나방을 로그북에 붙이고 「버그의 실제 사례 제1호(first actual case of bug being found)」라고 기록했다고 전해진다.

이 일화가 「버그(bug)」라는 말을 소프트웨어 분야에 퍼뜨린 계기가 되었다고 여겨지는 경우가 많다. 다만 정확히는, “bug”라는 말 자체는 이보다 훨씬 이전인 19세기 에디슨의 서신에도 기술적 결함을 가리키는 말로 등장하고 있어, 호퍼의 팀이 그 말 자체를 발명한 것은 아니다. 그렇다 해도 이 나방 일화가 「결함에는 구체적인 원인이 있고, 찾아서 제거할 수 있다」는 발상을 선명하게 상징한다는 점은 틀림없으며, 테스트 역사의 첫 페이지를 장식하기에 어울리는 한 장면이다.

1950년대 — 디버깅과 테스트가 구별되지 않던 시대

초기 컴퓨터(ENIAC, EDSAC 등)를 위한 프로그래밍에서는, 프로그램을 쓴 본인이 기계 앞에서 동작을 확인하고, 잘 안 되면 그 자리에서 수정하는 사이클이 전부였다.

당시에는 「테스터」라는 직능도, 「테스트」라는 독립된 개념도 존재하지 않았고, 올바르게 동작하는 프로그램을 만드는 것 자체가 목표였으며, 그를 위한 시행착오는 통틀어 「디버깅」이라 불렸다. 테스트 계획서나 테스트 케이스 설계 같은, 오늘날에는 당연한 산출물도 아직 일반적이지 않았다. 프로그램의 올바름은 개발자 본인의 머릿속에 있는 「명세의 이해」와 실제 출력을 맞춰 보는 것으로만 확인할 수 있었다.

이 시대의 상황을 상징하는 것이, 1957년 찰스 L. 베이커가 쓴 서평 속에서 제시했다고 알려진 지적이다. 그는 「프로그램의 디버깅」과 「프로그램의 테스트」가 별개의 활동이라고 처음으로 명확히 구별한 인물 중 한 명으로 일컬어진다. 이후 1970년대 말에 걸쳐, 테스트를 독립된 공정으로 보는 시각이 서서히 퍼져 갔다.

1960년대 — 소프트웨어 위기와 테스트 연구의 태동

1960년대에 들어서면서 소프트웨어의 규모와 복잡성이 급속히 증대하여, 개발 지연·예산 초과·품질 저하가 사회 문제화되었다. 하드웨어의 성능 향상에 따라 프로그램의 야심도 부풀어, 개인이나 소규모 팀의 경험칙만으로는 통제할 수 없는 규모의 시스템이 잇달아 태어났다.

이 상황은 1968년, 독일 가르미슈에서 열린 NATO 소프트웨어 공학 회의(NATO Software Engineering Conference)에서 「소프트웨어 위기(software crisis)」라는 말로 표현되었고, 소프트웨어 공학(software engineering)이라는 분야가 공식적으로 명명되는 계기가 되었다. 이 회의의 의사록은 피터 나우어와 브라이언 랜델의 편집으로 보고서로 정리되어, 훗날 업계에 큰 영향을 주었다. 품질과 테스트의 부족이 규모 확대에 따른 실패의 한 원인으로 거듭 지적된 점은, 테스트 역사의 관점에서도 중요하다.

이 위기에 대한 반응은 크게 두 방향으로 갈렸다. 하나는 로버트 플로이드와 C.A.R. 호어 등이 추진한 형식적 검증(formal verification), 즉 프로그램이 수학적으로 올바르다는 것을 증명하려는 접근이다. 다른 하나는 실제로 프로그램을 돌려 오류를 찾아내는, 경험적인 테스트 접근이다. 후자의 계보에서 초기의 중요한 작업으로, J. C. 밀러와 C. J. 말로니가 1963년에 발표한 논문 「Systematic Mistake Analysis of Digital Computer Programs」가 있다. 그래프 이론을 사용해 프로그램의 구조를 분석하고 체계적으로 테스트 케이스를 설계하려 한 이 연구는, 훗날 경로 테스트(path testing)와 커버리지(coverage) 개념의 선구로 자리매김된다.

거대 프로젝트의 고투 — OS/360과 The Mythical Man-Month

소프트웨어 위기를 상징하는 사례로 자주 인용되는 것이, IBM의 메인프레임용 OS 「OS/360」의 개발이다. 프로젝트 매니저를 맡았던 프레드 브룩스는 그 경험을 1975년의 저서 『The Mythical Man-Month(맨먼스 미신)』에 정리했다.

이 책 자체는 매니지먼트론을 주제로 하고 있지만, 대규모 시스템에서 테스트와 통합 작업이 어떻게 견적을 넘어 비대해지는지, 그리고 일정 지연에 대해 인력을 추가 투입하는 것이 왜 역효과를 내기 쉬운지(이른바 「브룩스의 법칙」)를 구체적으로 그리고 있다. 테스트와 통합 단계의 어려움이 널리 공유된 것도, 훗날 테스트를 독립된 공정·전문 영역으로 다룰 필요성을 업계에 각인시킨 한 요인이 되었다.

워터폴과 V모델 — 테스트는 「공정의 마지막」에 자리매김되었다

1970년, 윈스턴 로이스는 논문 「Managing the Development of Large Software Systems」에서 요건 정의 → 설계 → 구현 → 테스트 → 운용이라는 단계를 순서대로 밟는 개발 모델을 제시했다. 이것이 훗날 「워터폴 모델(waterfall model)」이라 불리게 되는 도식이다.

역설적이게도, 로이스 본인은 이 논문 속에서 반복과 피드백의 필요성을 강조하고 있었지만, 후세에는 테스트가 공정의 말미에 놓인 일직선의 프로세스로 단순화되어 퍼졌다. 이 구조 아래서 테스트는 「다 만든 것이 올바른지 마지막에 한꺼번에 확인하는 검수 작업」으로 취급되기 쉬웠다.

각 개발 단계에 대응하는 테스트 단계(단위 테스트, 결합 테스트, 시스템 테스트, 인수 테스트)를 짝지어 도식화하는 「V자 모델(V-model)」이라는 정리는, 워터폴적인 공정관을 더 명시적으로 그려낸 것이다. 다만 이것이 체계적인 표준으로 퍼진 것은 실은 이 전사보다 훨씬 뒤인 1980년대 중반 이후의 일이다. 서독 국방부가 주도한 V-Modell은 1986년에 개발이 시작되어, 1992년에 국방 분야에서의 사용이 의무화되었고, 1997년에는 연방 정부 표준으로 “V-Modell 97”이 공식화되었다.

그렇다 해도, 「개발의 각 단계에 대응하는 테스트의 계층이 있다」는 발상의 토대는, 이 글에서 다루는 1970년 전후의 워터폴적 공정관에 이미 존재하고 있었다. 테스트가 공정의 마지막에 배치된다는 구조는, 훗날 「테스트는 재작업 비용이 큰 단계에서만 실시된다」는 비판을 불러오게 된다. 이 문제의식은 나중에 제11화에서 다룰 「일찍, 자주」라는 테스트의 현대적 원칙으로 이어져 간다.

데이크스트라의 비판 — 「테스트는 버그의 존재를 보여줄 수 있지만, 부재는 보여줄 수 없다」

워터폴적인 「마지막에 한꺼번에 테스트한다」는 공정관에 대해, 컴퓨터 과학자 에츠허르 W. 데이크스트라는 근본적인 의문을 제기했다. 그의 말로 널리 알려진 것이 다음 한 구절이다.

“Program testing can be used to show the presence of bugs, but never to show their absence!” (프로그램 테스트는 버그의 존재를 보여주는 데는 쓸 수 있지만, 그 부재를 보여주는 데는 결코 쓸 수 없다)

이 주장은 1969년 NATO 소프트웨어 공학 회의의 의사록에서 보이는 초기의 변주를 거쳐, 1970년 전후에 집필된 미발표 노트 「Notes on Structured Programming」(EWD249) 속에서 명확한 형태를 얻었다고 여겨진다.

데이크스트라가 담고자 한 뜻은, 「테스트로는 전면적인 올바름의 증명이 불가능하다. 유한 개의 테스트 케이스를 아무리 쌓아도, 테스트하지 않은 입력에 대한 오류의 부재는 보장할 수 없다」는 점에 있다. 그 자신은 이 한계를 근거로, 테스트보다 구조화 프로그래밍(structured programming)이나 형식적인 올바름의 논의를 중시해야 한다고 주장했다. 이 비판은 오늘날에도 소프트웨어 테스트의 본질적인 한계를 이야기할 때 반드시 인용되는, 테스트 역사상 가장 유명한 한 구절이다.

동시에, 이 한계를 인정한 위에서도 「테스트는 유용하다」는 입장을 체계적으로 제시한 것이, 다음에 이야기할 글렌포드 마이어스의 작업이었다. 데이크스트라가 이론적인 한계를 들이밀었다면, 마이어스는 「한계가 있더라도, 테스트는 체계적으로 하면 충분히 가치가 있다」는 실천적인 입장에서 답을 돌려준 셈이다.

글렌포드 마이어스 『The Art of Software Testing』(1979) — 테스트의 첫 체계화

1979년, IBM에서 소프트웨어 공학에 종사하던 글렌포드 J. 마이어스는 저서 『The Art of Software Testing』(John Wiley & Sons)을 출판했다. 이는 소프트웨어 테스트만을 주제로 한, 최초기의 체계적인 서적 중 하나로 여겨진다.

마이어스가 이 책에서 제시한 가장 유명한 정의는 다음과 같다.

테스트란, 오류를 찾을 의도(intent)를 갖고 프로그램을 실행하는 과정이다.

이는 당시 널리 믿어지던 「테스트란 프로그램이 명세대로 올바르게 동작함을 보여주기 위한 작업이다」라는 이해를 전환시키는 것이었다. 마이어스에 따르면, 「올바르게 동작함을 확인한다」는 마음가짐으로 임하는 테스트는 무의식중에 느슨해진다. 오히려 「이 프로그램 어딘가에 반드시 버그가 있다, 그것을 찾아내겠다」는 파괴적인 의도를 갖고 테스트를 설계·실행할 때야말로, 테스트는 효과를 발휘한다.

이 사고방식으로부터, 「성공한 테스트 케이스란, 버그를 찾아낸 테스트 케이스다」라는 역설적인 평가 기준이 도출되었다. 이 평가 기준은 테스트의 성과를 「버그가 안 나와서 안심」이 아니라 「버그를 찾아냈는가」로 재는 발상의 전환이며, 오늘날 테스트 설계 사상의 출발점 중 하나가 되고 있다.

마이어스는 나아가 「테스트의 심리학(psychology of testing)」이라는 장을 두어, 프로그램의 저자 본인이 그 프로그램을 테스트하는 것의 위험성을 지적했다. 저자는 자신이 쓴 코드의 맹점을 알아차리기 어렵기 때문에, 독립된 제3자에 의한 테스트나 리뷰, 워크스루(walkthrough), 인스펙션(inspection)이 유효하다고 논한다. 더불어 경계값 분석(boundary value analysis)이나 동치 분할(equivalence partitioning) 같은, 한정된 시간 안에서 효과적인 테스트 케이스를 설계하기 위한 기법도 소개하고 있다. 이들은 거의 무한에 가까운 입력의 조합을, 한정된 시간 속에서 어떻게 대표적인 케이스로 좁혀 갈 것인가라는 실천적인 물음에 답하기 위한 기법이며, 오늘날에 이르기까지 테스트 기법의 기본 어휘로 남아 있다.

검증(Verification)과 타당성 확인(Validation)

같은 1979년, 배리 보엠은 논문 속에서 다음의 대비를 정식화했다고 알려진다.

개념 물음 의미
검증(Verification) Are we building the product right? 명세대로 올바르게 만들고 있는가
타당성 확인(Validation) Are we building the right product? 애초에 만들어야 할 것을 만들고 있는가

단위 테스트·통합 테스트의 대부분은 「검증」의 영역(명세에 대한 적합을 확인하는)에 해당한다. 한편 사용자 인터뷰나 인수 테스트는 「타당성 확인」의 영역에 가깝다. 「테스트를 전부 통과시켰다」는 것은 검증의 달성이지, 타당성 확인의 달성을 의미하지 않는다 — 올바르게 만들어진 「잘못된 것」은, 테스트가 초록이어도 실패작이 될 수 있다. 이 구별은 시리즈 overview에서 언급한 4개의 실 중 첫 번째이며, 이후의 모든 화에서 거듭 되돌아가게 된다.

결함 수정 비용 체증설 — 테스트에 대한 투자는 왜 정당화되었나

「버그는 발견이 늦어질수록 수정 비용이 뛰어오른다」는 주장은, 테스트에 대한 투자를 정당화하는 근거로 오랫동안 인용되어 왔다. 보엠은 1976년 논문, 그리고 1981년의 저서 『Software Engineering Economics』에서, 요건 정의 단계에서 발견되는 결함에 비해 실제 운용 후에 발견되는 결함의 수정 비용이 수십 배에서 백 배에 달한다고 도식화했다 — 이른바 「결함 수정 비용 곡선」이다. 이 주장은 테스트를 공정의 이른 단계로 앞당겨야 한다는 발상의 이론적 지주로서 오랫동안 업계에 침투했다.

다만 이 「곡선」의 실증적 근거는 최근 들어 의문시되고 있다. 로랑 보사비는 저서 『The Leprechauns of Software Engineering』(2015)에서 보엠의 주장의 근거가 된 조사를 정밀히 살펴, 「학생을 대상으로 한 소규모 조사 결과를 일반화하고 있다」 「인용이 재인용에 재인용을 거듭해 원 데이터를 추적할 수 없다」 같은 문제를 지적하고, 「경험적으로 뒷받침된 사실이라기보다, 보엠의 의견이 도식화된 것에 지나지 않는다」고 결론지었다. 이는 「일찍 발견하는 편이 고치기 쉽다」는 직관 자체를 부정하는 것은 아니지만, 「실제 운용의 결함은 요건 단계의 백 배 비용이 든다」 같은 구체적인 숫자를 그대로 믿어서는 안 된다는 건전한 회의를 업계에 가져왔다. 테스트 역사를 따라가는 데 있어서는, 정설로 퍼진 주장이라도 훗날 다시 검증되는 일이 있다는 점도 기억해 둘 만하다.

초기 테스트 자동화의 싹

이 시대의 테스트는 기본적으로 수작업이었다. 메인프레임 상의 배치 처리에서는 테스트 데이터를 담은 카드나 테이프를 준비해, 프로그램을 실행하고 출력을 눈이나 손으로 기대값과 맞춰 보는 방법이 일반적이었다. COBOL이나 FORTRAN 시스템 개발 현장에서는 테스트 케이스를 종이 문서로 관리하고, 담당자가 체크리스트에 따라 수동으로 확인하는 체제가 주류였다. 테스트 실행 결과는 종이 장표로 출력되어, 사람의 눈으로 하나하나 대조하는 것이 당연한 광경이었다. 같은 테스트를 재실행하는 것만으로도 많은 수고가 들었고, 「테스트를 몇 번이고 반복한다」는 비용 의식 자체가 오늘날과는 크게 달랐다.

그렇다 해도, 자동화의 싹이 전혀 없었던 것은 아니다. 밀러와 말로니(1963) 같은, 프로그램의 구조를 그래프로 분석해 체계적으로 테스트 케이스를 도출하려 한 연구는, 훗날의 커버리지 계측 도구나 테스트 케이스 생성 도구의 선구로 볼 수 있다.

또한 대형 메인프레임 벤더 내부에서는, 회귀 확인을 위해 과거 출력과 새 출력을 기계적으로 비교하는 간이 장치가 쓰이기 시작하고 있었다. 하지만 오늘날의 xUnit 같은 「누구나 쓸 수 있는 공통 단위 테스트 프레임워크」는 아직 존재하지 않았고, 테스트 코드를 쓰는 것 자체가 표준적인 개발 작업으로 인식되기에는 이르지 못했다.

체계화 전야 — 1970년대 말의 도달점

1970년대 말까지, 다음과 같은 토대가 만들어져 있었다.

관점 1950년대의 상황 1979년 시점의 상황
테스트의 목적관 디버깅과 미분화, 돌아가면 됐다 오류를 찾을 의도를 가진 독립된 활동
실시자 프로그래머 본인이 임기응변으로 확인 저자 이외에 의한 검증의 유효성이 논의된다
기법 특별히 없음, 그때그때의 확인 경계값 분석·동치 분할 등의 기법이 정리된다
자리매김 개발과 일체화된 작업 개발 공정 안에 명시적으로 자리매김된 공정
한계의 자각 거의 의식되지 않음 데이크스트라의 비판으로 원리적 한계가 널리 공유된다

이 시점에서 테스트는 드디어 「독자적인 이론과 기법을 가진 전문 영역」으로서 윤곽을 갖기 시작했다. 하지만 테스트를 일상적으로, 자동으로, 반복해서 실행하기 위한 도구는 아직 없었다.

이 30년을 돌아보면, 테스트라는 영위가 「개념조차 존재하지 않는 상태」에서 「이론과 기법을 갖춘 전문 영역」으로 착실히 걸음을 옮겨 왔음을 알 수 있다. 하지만 그 걸음은 어디까지나 이론의 정비에 머물러 있었다. 마이어스의 책을 읽고 경계값 분석을 배운 프로그래머라도, 테스트 케이스를 쓰는 작업 자체는 여전히 수작업이었고, 한번 쓴 테스트를 몇 번이고 같은 수고를 들여 재실행해야만 했다. 「좋은 테스트란 무엇인가」라는 물음에는 답이 나오고 있었지만, 「그 테스트를 일상적으로, 마찰 없이 계속 실행하려면 어떻게 해야 하는가」라는 물음에는 아직 아무도 답하지 못하고 있었다.

이 공백을 메운 것이, 1990년대 중반 켄트 벡이 Smalltalk용으로 만든 작은 프레임워크 SUnit이었고, 거기서 태어난 JUnit이었다. 다음 제2화에서는 이 전환점을 — 전설의 비행기 안에서 쓰였다는 JUnit의 일화와 함께 — 자세히 살펴본다.

← Back to 소프트웨어 테스트의 계보