Skip to content
isdnetworks
Go back

테스트가 없는 코드를 고칠 때

오래된 PHP 코드를 고쳐야 했는데 테스트가 하나도 없다. 고치면 무엇이 깨지는지 알 방법이 없다.

Table of contents

Open Table of contents

먼저 현재 동작을 기록했다

PHPUnit 으로 테스트를 새로 짜려니 무엇이 맞는지 몰랐다. 명세가 없고 코드가 곧 명세다.

그래서 지금 동작을 그대로 기록했다. 입력 여러 개를 넣고 출력을 JSON 파일로 저장했다.

input_001.json → output_001.json
input_002.json → output_002.json
...

JSON 이 맞는지는 모른다. 다만 지금은 이렇게 동작한다는 사실이고 그것이 기준이 된다.

고친 뒤 비교했다

고치고 나서 같은 입력을 넣고 출력을 diff 로 비교했다. 달라진 것이 있으면 둘 중 하나다.

의도한 변경이다      → 저장된 출력을 갱신한다
의도 안 한 변경이다   → 버그다

diff 에 뜬 것이 의도한 것인지 아닌지를 가르는 것이 이 방식의 전부였다. 지금 동작이 이상해 보여도 일단 그대로 기록하니 옳고 그름을 판단하지 않아도 된다.

무엇이 달라졌는지를 아는 것과 모르는 것의 차이가 이 작업의 안전을 갈랐다.

입력을 어디서 구하나

가짜 입력을 만들면 실제와 다르므로 실제 데이터를 써야 했다. access_log 에 요청 본문이 남아 있는 곳이 있어서 거기서 가져왔다.

개인정보가 있는 것은 JSON 구조는 유지하고 값만 바꿨다. 여러 종류를 골고루 뽑았는데 정상 케이스뿐 아니라 이상한 것도 넣었다.

필드가 빠진 것
값이 비정상적으로 큰 것
인코딩이 이상한 것

이상한 입력이 특히 중요했다. 고치다가 그런 케이스의 처리를 바꾸기 쉽고 손으로 만들면 정상적인 경우만 넣게 된다.

함수 하나부터

전체를 한 번에 감싸려니 어려웠다. MySQL 도 붙고 외부 호출도 있다.

// 원래
function process($id) {
    $data = $db->fetch($id);
    $result = /* 복잡한 계산 */;
    $db->save($result);
}

// 분리
function calculate($data) {   // ← 이 부분만 테스트 가능
    return /* 복잡한 계산 */;
}

process 에서 순수한 계산 부분부터 떼어냈다. 입출력이 있는 부분과 계산하는 부분을 나누면 calculate 쪽은 쉽게 테스트된다.

calculate 로 떼어내면서 코드가 읽기 쉬워지기도 했는데 부수효과가 있었다.

판단 기준 — 어디까지 감쌀지

시간이 한정돼 있어서 우선순위를 뒀다. 고칠 PHP 파일과 그 주변만 감쌌고 안 건드릴 곳은 안 했다.

그리고 자주 고치는 파일을 우선했는데 한 번 만들어 두면 계속 쓰기 때문이다. 어디가 자주 고쳐지는지는 svn log 에서 셌다. 전부 감싸겠다고 하면 시작도 못 한다.

이 작업 중에 기존 버그를 하나 찾았다. 특정 입력에서 출력이 이상했고 고치려던 것과 무관한 부분이었다.

고칠지 말지 고민했다. 고치면 그 동작에 의존하는 곳이 있을 수 있고 안 고치면 알면서 두는 것이다.

확인하고 나서 결정했다. 그 출력을 쓰는 곳을 grep 으로 찾아보니 실제로 문제가 되고 있어서 고쳤다.

svn commit 을 따로 했고 원래 하려던 작업과 섞지 않았다. 나중에 문제가 생기면 어느 쪽인지 갈려야 하니까.

정리


Share this post on:

Previous Post
방화벽 규칙을 넣었는데 안 막혔다
Next Post
기록의 계정으로 사람을 특정할 수 없었다