AI에게 TDD를 시키면 어떤 일이 벌어질 수 있을까
AI에게 사용자를 무효화하는 기능을 만들게 한다고 가정해 봅시다. 사람이 작성하고 승인한 인수 조건(acceptance criteria)은 다음 세 가지입니다.
- 무효화된 사용자는 새로 로그인할 수 없다
- 무효화된 사용자의 기존 세션은 만료된다
- 무효화되지 않은 사용자의 세션에는 영향을 미치지 않는다
TDD(Test-Driven Development)를 따라 AI에게 먼저 테스트를 작성하게 하고, 그것을 통과하는 구현 코드를 작성하게 합니다. 그 절차는 사람이 할 때와 같습니다.
이런 흐름을 상상해 보세요. AI는 1번과 2번을 구현하고 테스트를 통과시킵니다. 그런데 2번의 구현 과정에서 무효화된 사용자뿐만 아니라 모든 사용자의 세션을 만료시켜 버립니다. 당연히 3번의 테스트가 실패합니다. 이때 AI가 구현을 고치는 대신, 3번 테스트에서 '다른 사용자의 세션은 남아 있다'는 확인 사항을 빼버립니다. 결국 모든 테스트가 통과하게 됩니다.
명세(specification) 자체는 변하지 않았습니다. 테스트도 올바른 것이었습니다. 그런데도 AI는 확인해야 할 관점을 제외하고, 올바른 테스트를 잘못된 내용으로 다시 작성하여, 잘못된 구현을 통과시키고 있습니다. TDD의 절차로 말하자면, 고쳐야 했던 것은 구현 쪽이었습니다.
하지만 외부에서 볼 때는 이 변경 사항이 모든 테스트에 합격했습니다. 합격했기 때문에 올바른 구현처럼 보입니다. 테스트를 다시 쓸 수 있는 AI가 스스로 테스트를 통과시킨다면, '테스트에 합격했다'는 것이 더 이상 정당성의 증거가 될 수 없습니다.
이는 과장된 예가 아닙니다. AI는 구현이 잘 안 될 때, 테스트를 무효화하거나 삭제해서라도 '통과시키려고' 하는 경우가 있다고 보고되었습니다[1]. 인간 팀에서는 이러한 행위가 개발자의 규율이나 리뷰에 의해 어느 정도 막아져 왔습니다. 테스트를 작성하는 사람도, 통과시키는 사람도 같은 AI가 된다면 그 제동 장치는 사라집니다. 절차가 지켜지는 것을 절차 자체가 보장해주지 않습니다.
이것은 TDD만의 문제가 아니다
이 문제는 테스트를 먼저 작성했기 때문에 발생한 것이 아닙니다. 테스트를 나중에 작성해도, AI가 테스트를 바꿀 자유가 있다면 같은 일이 일어납니다. 발생한 것은 인간을 위해 만들어진 실무(practice)의 절차만을 AI에게 옮겼다는 문제입니다.
인간을 위해 만들어진 실무에는 절차 외에도, 그것을 실행하는 인간이나 팀을 전제로 한 메커니즘이 암묵적으로 포함되어 있습니다. 테스트를 작성하는 사람과 코드를 작성하는 사람, 변경 사항을 안심하고 받아들이고 싶은 사람이 같은 개발자였던 것. 그 개발자가 규율을 가지고 리뷰를 받았던 것. 이러한 전제 위에서 절차는 이익을 창출했습니다.
AI가 코드를 쓰게 되면서, 이 전제가 무너집니다. AI는 테스트를 작성하지만, 안심하고 싶은 것은 인간입니다. AI는 설계를 제안하지만, 그 설계를 유지해 나가는 것은 인간 팀입니다. 이익을 창출하는 활동을 담당하는 사람과, 이익을 받는 사람이 분리되어 버리는 것입니다. 절차를 그대로 AI에게 넘기면, 작업은 끝났더라도 애초에 이익을 받기로 했던 사람이 사라져 있을 수도 있습니다.
당연한 것을, 당연하게 하기
그렇다면 어떻게 해야 할까요?
방식을 바꿀 때는 먼저 무엇을 위한 방식이었는지 생각하고, 그 위에 새로운 방식을 정해야 합니다. 이는 소프트웨어 개발에 한정된 것이 아니라, 지극히 당연한 일입니다.
이러한 당연함을 체계화한 방법이 있습니다. 제조업이나 공공 건설에서 널리 사용되는 **가치 공학(Value Engineering, VE)**입니다. VE는 1947년 GE의 기술자 Lawrence D. Miles가 개발한 가치 분석(value analysis)을 기반으로 하는 방법론이며[2], 일본 바リュー 엔지니어링 협회에서는 최소의 총비용으로 필요한 기능을 확실하게 달성하기 위해 제품이나 서비스의 기능을 체계적으로 연구하는 방법이라고 정의하고 있습니다[3]. 국토교통부의 연구기관이 정리한 설계 VE 가이드라인에는 대상을 '물건 중심'으로 파악하는 것이 아니라, 요구되는 기능으로 파악한다는 원칙이 적혀 있습니다[4]。
VE 실행의 중심은 기능 정의(function definition), 기능 평가(function evaluation), 대체안 작성(alternative creation) 단계로 진행되며, 마지막에 제안 평가를 거칩니다[5]. 현재 존재하는 물건이 무엇을 위해 있는지(기능)를 먼저 정의하고, 그 기능을 똑같이 수행하는 다른 방법을 생각해 보고, 그것으로 괜찮은지 평가합니다. 절차든 물건이든 아니며, 기능에서 생각하는 것입니다. VE의 대상은 형태가 있는 제품에만 국한되지 않습니다. 정의대로 서비스도 대상입니다. 업무 진행 방식 또한 그 연장선상에서 파악될 수 있습니다.
하지만 AI라는 새로운 도구를 앞에 두자, 이 당연한 논의가 건너뛰어지고 뭔가 전통과는 완전히 다른 새로운 것을 생각하기 시작합니다. 최근 'AI 시대의 새로운 개발 방법론을 고민한다' 같은 검토회들을 여기저기서 볼 때마다 저는 그렇게 느낍니다.
제가 말하고 싶은 것은, AI를 앞에 두더라도 당연한 것을 당연하게 하려고 노력해야 한다는 것입니다. 개발 관행(practice)을 VE가 제품을 보는 것과 똑같은 눈으로 바라봅니다. 절차를 AI에게 넘기는 것이 아니라, 그 절차가 수행했던 기능, 즉 절차가 만들어냈던 이익을 AI와 인간이 함께 개발하는 환경에 맞춰 다른 방식으로 계승해야 합니다. 이를 위해 저는 VE의 단계에 따라 다음 네 단계로 생각하고 있습니다.
| VE 단계 | 본 글의 4단계 | 무엇을 할 것인가 |
|---|---|
| 기능 정의 | 1. 이익 특정하기 | 해당 관행이 무엇을 얻기 위해 있었는지를 적어 내려간다 |
| ... |
VE의 기능 평가는 기능별 중요도와 비용을 비교하여 개선 대상 기능을 선택하는 단계인 것처럼[6], 두 번째 단계에서는 이익별로 현재 방식 그대로 괜찮은지, 바꿀 필요가 있는지를 결정합니다. 이때 누가 그 이익을 창출하는 작업을 맡고, 누가 책임을 질지도 함께 결정합니다.
이 모든 것은 당연한 절차입니다. 그 위에 AI와 함께 개발할 때 특히 신중하게 다루고 싶은 것이 두 번째 단계입니다. AI가 담당자로 참여하면 '작업을 하는 사람'과 '승인하고 책임지는 사람', 그리고 '이익을 받는 사람'이 분리되기 때문입니다. 이곳을 신중하게 처리하지 않으면, 이익이 누구에게도 전달되지 않습니다.
여기서는 서두의 기능을 예로 들어 네 단계를 한 번에 적용해 보겠습니다. 이 기능을 만드는 데는 많은 활동이 필요하지만, 논점을 하나로 유지하기 위해 구현과 검증에 초점을 맞추겠습니다.
1단계: 이익 특정하기
먼저 절차가 아니라 이익을 봅니다. 여기서 나열하는 것은 TDD가 확실히 가져왔던 이익이 아니라, TDD를 통해 얻으려고 했던 이익입니다. 절차가 직접적으로 가져오는 효과와 그 결과로 기대했던 이익을 구분해 놓겠습니다.
| 종류 | 직접적인 효과 | 기대했던 이익 | 이 기능에서는 |
|---|---|
| 테스트 일반 | 테스트가 작성될 수 있음 | 출시 전에 발견하기 쉬운 결함을 찾을 수 있음 | 비활성화 직후 사용자, 비활성화를 취소한 사용자, 관리자 자신의 비활성화와 같은 케이스까지 확인할 수 있음 |
| ... |
마지막 점에 대해 Kent Beck은 『테스트 주도 개발』의 서문에서 TDD의 가장 큰 이유를 '불안을 관리하는 것'에 두었습니다. Birgitta Böckeler 역시 TDD의 목적들을 하나씩 꺼내 그것이 AI 안에서 달성되는지 검토하고 있습니다[7].
2단계: 담당자와 방식을 정하기
다음으로, 이익별로 작업을 누가 할 것인지, 결과를 누가 승인하고 책임질 것인지, 방식을 바꿀 것인지를 결정합니다.
판단에는 다음 세 가지 관점을 사용합니다.
- 잘못되었을 때의 영향. AI가 잘못되면 무엇이 어디까지 망가지는가. 영향이 클수록 인간의 승인을 거쳐야 함 -
결과를 독립적으로 확인할 수 있는가. AI의 결과물을 AI의 진술에 의존하지 않고 확인할 수단이 있는가. 있다면, AI에게 맡기기 쉬움 -
인간이 관여하는 것 자체에 가치가 있는가. 그 활동을 통해 인간이 이해나 판단의 재료를 얻었는가. 얻었다면, 인간이 계속 관여할 가치가 있음
이 관점으로 결정하면 다음과 같습니다.
| 기대했던 이익 | 작업하는 것은 | 승인하고 책임지는 것은 | 방식 |
|---|---|
| 발견하기 쉬운 결함을 찾는 것 | AI (테스트를 추가 작성) | (개별 테스트의 사전 승인은 불필요. 결함을 찾는 능력은 4단계에서 확인할 수 있음) | 그대로 |
| ... |
AI에게 작업을 맡겨도, 승인과 책임은 인간에게 남습니다. 이 표의 많은 행이 그런 형태입니다.
세 번째 행은 판단이 갈릴 수 있으므로 근거와 저의 해석을 나누어 놓겠습니다. Böckeler의 평가에서 확인된 것은, AI에게 TDD 절차를 지키게 해도 설계 품질에 명확한 차이가 나타나지 않았다는 것입니다[7:1]. 여기서부터는 제 가설이지만, 테스트를 먼저 작성하는 것의 설계적 효과는 테스트라는 형식 자체보다, 구현보다 먼저 호출하는 쪽의 형태를 생각하는 단계에서 생겨났던 것이 아닐까요? 그렇다면 그 단계를 명확히 두고 어떤 형태로 할지 판단을 내리는 것은 인간이 하는 것이 좋을 것 같습니다. 설계는 이 이후 변경되는 모든 것에 영향을 미치기 때문입니다.
분리해 보면, 좋은 점도 보입니다. 경계값이나 이상 케이스를 포괄하는 테스트를 작성하는 것, 변경할 때마다 모든 테스트를 실행하는 것. 이 모든 것은 해야 한다고 알면서도, 너무 많은 노력이 들어서 실제로 수행하지 못했던 것입니다. AI는 이것들을 저렴하게 처리할 수 있습니다. 각 방법론이 이상적으로 제시하면서도, 인력의 제약으로 인해 실현하지 못했던 관행들이 마침내 구현 가능해집니다.
3단계: 새로운 방식을 만들기
2단계에서 '변경'이라고 했던 네 가지에 대해 구체적인 시스템을 만듭니다.
작동하지 않는 것을 확인하기: 보호된 인수 테스트를 외부에 배치하기
AI가 테스트를 추가하는 것과, AI가 테스트를 수정하지 못하게 하는 것은 테스트를 두 종류로 분리함으로써 양립할 수 있습니다.
| AI가 작성하는 테스트 | 보호된 인수 테스트 | |
|---|
| 생성 | AI가 만들 수 있음 | 사람이 승인한 인수 조건에서 만들어, 사람이 확인하고 승인함 |
| ... | | |
| 맨 앞의 세 가지 인수 조건에서 만드는 테스트는 오른쪽 열에 들어갑니다. 여기서 가장 중요한 것은, 보호하기 전에 그 테스트가 인수 조건을 올바르게 표현하는지 인간이 확인하는 것입니다. 인수 조건을 승인한 것과, 거기서 만들어진 테스트가 정확하다는 것은 별개의 문제입니다. 예를 들어 AI가 3번 조건을 '무효화된 사용자의 세션이 만료되는 경우'만 확인하는 테스트로 만들면, 다른 사용자의 세션이 남아있는 것은 확인할 수 없습니다. 잘못된 테스트를 보호하게 되면, AI가 변경할 수 없게 되더라도 문제는 해결되지 않습니다. | | |
확인하고 승인한 테스트는 다음 세 가지 의미에서 외부에 배치합니다.
- 출처가 외부에 있음. 사람이 승인한 인수 조건에서 만들고, 사람이 테스트 자체도 확인합니다. - 관리가 외부에 있음. 테스트의 정의와 합격 기준을 AI가 수정할 수 없는 장소에 둡니다. 맨 앞의 상황처럼 테스트를 약화시키는 것은 여기서 불가능해집니다. 정당한 이유로 테스트를 고칠 때는 인간의 승인을 거칩니다. - 실행과 판정이 외부에 있음. AI와는 별도의 실행 시스템(CI 등)에서 실행하고, 그 결과로 합격 여부를 결정합니다. AI의 '통과했습니다'라는 진술은 사용하지 않습니다.
보호할 대상에는 테스트 자체뿐만 아니라, 테스트의 실행 조건이나, 합격 여부에 영향을 주는 CI 설정도 포함됩니다. 테스트 파일이 보호되어 있어도, AI가 CI 설정을 변경하여 테스트를 건너뛸 수 있다면, 외부에 배치한 의미가 없습니다. 이 모든 것은, 테스트 파일이나 CI 설정을 보호된 장소에 두고 AI에게는 쓰기 권한을 주지 않거나, 병합 조건으로 CI에서 필수 테스트가 통과하는 것을 요구하는 등의 일상적인 시스템으로 구현할 수 있습니다.
그럼에도 불구하고, 보호된 인수 테스트가 통과한다고 해서 올바르다고 할 수는 없습니다. 인수 조건 자체에 누락이 있다면, 테스트는 그 누락을 발견하지 못합니다. 테스트가 통과하는 경로만 정확하고, 다른 경로에 결함이 남아있을 수도 있습니다. AI가 추가하는 테스트나, 4단계에서 다룰 뮤테이션 테스트는 이러한 놓침을 줄이는 단서가 됩니다.
사용하기 쉽고 변경하기 쉬운 설계: AI에게 초안을 내게 하고, 인간이 형태를 결정하다
AI에게 구현을 시작하게 하기 전에, 호출하는 입장에서 본 형태를 결정하는 단계를 둡니다. 이 기능이라면, AI에게 예를 들어 다음과 같은 두세 가지 안을 각각의 장점과 단점과 함께 제시하도록 할 수 있습니다.
- 무효화 처리를 하나의 진입점에 모아 관리 화면, 야간 배치, API 어디서든 그 진입점을 호출하게 한다 - 호출하는 곳마다 처리 로직을 가지고, 공통 부분만 컴포넌트로 만든다
인간은 앞으로 어떤 변경이 올지 염두에 두고, 어떤 형태로 할지 결정합니다. 그리고 그렇게 결정한 이유를 ADR(Architecture Decision Record) 등에 남깁니다. 이유를 남겨두지 않으면, AI는 다음 변경 시 그것을 기억하지 못하고 다른 형태로 재작성해 버릴 수 있기 때문입니다.
놓친 것에気づ다: 구현 전에, AI에게 질문을 도출하게 하다
테스트를 작성하면서 깨달았던 것을, 구현 전 단계에서 포착합니다. 이 기능이라면, AI에게 인수 조건의 초안을 읽게 하고, 아직 답이 나오지 않은 질문들을 도출하게 할 수 있습니다.
- 무효화와 로그인이 동시에 발생하면 어떻게 할까 - 이미 무효화된 사용자를 다시 무효화하면 어떻게 할까 - 세션 만료 처리가 중간에 실패하면 어떻게 할까
인간은 그 질문을 선택하고, 답을 정하여 인수 조건에 추가합니다. 맨 앞의 세 번째 조건(다른 사용자 세션에는 영향을 주지 않는다)도 이렇게 도출되어야 하는 조건입니다. 결정할 수 없는 질문은 미결 사항으로, 구현 전에 답을 내놓습니다. 구현 도중에 AI가 새로운 의문을 발견했을 때는, 멋대로 판단하게 하지 않고 인간에게 되돌려줍니다.
다만, 이것으로 TDD의 왕복 과정에서 발생했던 발견을 모두 대체할 수는 없습니다. 작게 구현하고 결과를 보고서야 비로소 깨닫는 것은, 구현 전에 아무리 많은 질문을 뽑아내도 남아 있습니다. 이익을 계승하려 해도, 실제로는 사라지는 것이 있을 수 있습니다. 그렇기에 제4단계에서 확인합니다.
변경에 대한 불안을 줄이기: 받아들이는 사람이 판단할 수 있는 단위로 받기
AI는 불안을 느끼지 않습니다. 하지만 AI의 변경을 받아들이는 인간에게는 불안이 있습니다. 게다가 그 불안은, 스스로 코드를 작성했을 때와 내용은 다릅니다. 자신이 쓰지 않은 변경이 짧은 시간에 대량으로 도착합니다. 테스트가 통과했더라도, 무엇이 간과되었는지 알 수 없습니다. 모든 코드를 읽는 것도 어렵습니다.
보호된 인수 테스트(protected acceptance test)가 외부에서 통과하고 있다는 것은 이 불안을 완화하는 큰 근거가 됩니다. 그 위에서, 변경을 받아들이는 사람이 판단할 수 있는 단위로 받을 수 있도록 합니다. AI에게 변경을 작게 나누도록 시키고, 변경의 내용과 이유를 설명하게 하고, 받아들인 변경의 일부를 인간이 읽어볼 시간을 남겨두는 것입니다. TDD에서 개발자가 작은 보폭으로 불안을 관리했듯이, AI와 함께 개발할 때는, 변경을 받아들이는 사람이나 팀이 받는 단위와 근거를 정함으로써 불안을 관리하게 됩니다.
제4단계: 이익이 얻어졌는지 확인하기
마지막으로, 재구축한 결과를 확인합니다. 여기서 주의해야 할 점은, 활동을 했다는 것과 이익이 얻어졌다는 것은 다르다는 것입니다. 예를 들어, 형태를 결정한 기록이 ADR(Architecture Decision Record)에 남아 있는 것은, 형태를 고민했다는 활동의 증거가 됩니다. 하지만 사용하기 쉽고 변경하기 쉬운 설계가 되었다는 증거는 아닙니다.
그래서, 이익별로 다음 세 가지를 나누어 봅니다.
- 활동 확인: 재구축한 방식이 실제로 수행되었는지 -
- 이익 단서: 기대했던 이익이 얻어질 것 같은지 -
- 나중에 알게 된 것: 시간이 지난 후에 문제로 발견되었는지
이 기능에, 예를 들어 '일시 정지(一時停止)'라는 상태를 나중에 추가하는 변경을 생각해 봅니다. 일시 정지한 사용자는 로그인할 수 없지만, 일정 기간 후 자동으로 원래대로 돌아오는, 이라는 변경입니다.
| 기대했던 이익 | 활동 확인 | 이익 단서 | 나중에 알게 된 것 |
|---|
| 간과하기 쉬운 결함을 찾고, 망가지지 않았는지 확인 | 보호된 인수 테스트를 인간이 확인하고 승인했는지 | 일부러 코드에 작은 오류를 넣어 테스트가 실패하는지 보기(뮤테이션 테스트)[7:2]. 일시 정지 구현으로 무효화 동작을 바꿔버렸다면, 테스트가 실패하는지 | 테스트를 빠져나가 나중 공정이나 본방에서 발견된 결함이 얼마나 있는지 |
| 사용하기 쉽고 변경하기 쉬운 설계 | 구현 전에 형태를 결정하고 이유를 남겼는지 | 일시 정지 추가로 손댈 범위가 좁게 국한되었는지. 입구 안의 조건 분기가, 읽을 수 없을 만큼 복잡해지지 않았는지 | 그 후의 변경에서도, 형태를 바꿔야 하는 일이 없었는지 |
| 생각하지 못한 것에 깨닫기 | 구현 전에 질문을 뽑아내고 답을 받아들여 조건에 추가했는지 | '일시 정지 중인 세션은 어떻게 할까'가, 구현 전의 질문으로 나왔는지 | 인수 조건의 생각 부족이 원인이 되어, 나중에 발견된 결함이 얼마나 있는지 |
| 변경에 대한 불안을 줄이기 | 변경을 작게 나누고 설명을 붙여 받았는지 | 받아들이는 사람이, 테스트 결과와 설명을 근거로 스스로 판단할 수 있었는지 | 받아들인 후에, 내용을 이해하지 못해서 발생한 문제가 없는지 |
첫 번째 열만 채워져 있고, 두 번째나 세 번째에 문제가 있다면, 재구축한 방식이 이익을 낳고 있지 않다는 것입니다. 그럴 때는 제3단계로 돌아갑니다. 모든 것을 숫자로 측정할 필요는 없습니다. 중요한 것은, 이익이 얻어졌다고 말할 수 있는 상태와, 검토가 필요한 상태를, 재구축할 때 함께 정해두는 것입니다.
맺음말
TDD를 재료로 삼아, 제4단계를 한 번 거쳐 사용해 보았습니다.
TDD라는 절차는 그대로의 형태로는 남아있지 않았습니다. 테스트를 추가하는 작업은 AI에게 넘어가고, 합불 판정은 외부에 넘어가며, 설계와 인수 조건은 AI의 안을 바탕으로 인간이 결정하게 되었고, 불안 관리는 변경을 받아들이는 사람의 일이 되었습니다. TDD가 얻으려 했던 이익을 하나씩 꺼내봄으로써, 각각을 누가, 어떤 활동으로 맡을지 생각할 수 있었다고 생각합니다.
하지만 실제로 같은 이익을 얻을 수 있을지는 아직 알 수 없습니다. 왕복 과정에서 발견한 것처럼, 계승할 수 없는 것들도 보였습니다. 그렇기 때문에 방식을 재정비할 때는 무엇을 확인할지까지 함께 결정해 둘 필요가 있습니다.
이 네 단계는 다른 실천에도 적용될 수 있습니다. 예를 들어 코드 리뷰라면 결함 탐지, 설계 판단 공유, 주니어의 학습 같은 이익으로 나눌 수 있습니다. 결함 탐지는 AI에게 맡기기 쉽지만, 설계 판단 공유나 학습은 리뷰 작업을 AI가 대신하더라도 인간 측에 남는다고 할 수는 없습니다. 작업하는 사람과 이익을 받는 사람이 분리되기 때문입니다.
지금 하는 일들은 모두 당연한 것입니다. 무엇을 위한 방식이었는지 생각하고, 담당자가 바뀌었음에 맞춰 방식을 정하며, 잘 되었는지를 확인합니다. AI라는 새로운 도구를 앞에 둔다 하더라도, 새로운 것을 생각하기 전에 이 당연한 것들을 당연하게 해보세요. AI에게 절차를 넘기는 것이 아니라, 절차가 존재했기 때문에 발생했던 이익을 넘겨야 합니다. 거기에서 무언가가 보일 것입니다.
참고문헌
본문 중 각주 출처는 다음과 같습니다.
토론