버그를 고치고 테스트를 추가했더니 통과했습니다. 그럼 버그가 고쳐졌다고 말할 수 있을까요?

얼쑤의 연주 비교 기능을 고치면서, 답은 고치기 전에 그 테스트가 실패했는지 봐야 안다는 걸 배웠습니다.

다시 연주한 구간을 비교하는 기능

얼쑤는 국악기 연주를 녹음하면 음표마다 맞게 연주했는지 알려 주는 앱입니다. 틀린 구간만 골라 다시 연주하면, 앱이 이전 연주와 이번 연주를 비교해 어떤 음을 고쳤고 어떤 음이 아직 틀렸는지 보여 줍니다.

저는 이 비교 결과를 계산하는 서버 기능을 만들었습니다.

리뷰에서 나온 버그

코드 리뷰에서 이런 지적을 받았습니다.

이번 연주보다 나중에 한 연주를 “이전 연주"로 넣어도 비교가 된다.

이전과 이번이 뒤바뀌면 결과도 거꾸로 나옵니다. 새로 틀린 음이 “고친 음"으로 보이는 식입니다. 이런 요청은 서버가 비교하지 않고 잘못된 요청(400)으로 거절해야 했는데, 정상 처리(200)를 하고 있었습니다.

고치기 전에 테스트부터 실패시키기

코드를 고치기 전에, 이 상황을 그대로 재현하는 테스트를 먼저 썼습니다.

// 21번: 이번 연주(12번)보다 1분 늦게 한 연주
save(21, 2, 3, 50, startedAt = time.plusSeconds(60))

// 21번을 "이전 연주"로 넣으면 400(잘못된 요청)이 와야 한다
mvc.perform(get("/api/practice-sessions/12/comparison").param("baselineSessionId", "21"))
    .andExpect(status().isBadRequest)

그리고 고치기 전 코드에서 돌렸습니다. 테스트는 실패했고, 메시지는 400을 기대했는데 200이 왔다였습니다.

여기서 중요한 건 실패한 이유입니다. 테스트 데이터를 잘못 넣어도, 준비 과정에서 오류가 나도 테스트는 실패합니다. 그런 테스트는 나중에 통과해도 아무것도 증명하지 못합니다. 실패 메시지가 정확히 고치려던 버그를 가리키는지 확인하고 나서야 코드를 고쳤습니다.

// 이전 연주가 이번 연주보다 늦게 시작했으면 잘못된 요청으로 처리
if (baseline.startedAt.isAfter(current.startedAt)) {
    return SectionComparisonLookup.InvalidBaseline
}

같은 테스트를 다시 돌리자 통과했습니다.

고치기 전엔 그 버그 때문에 실패하고, 고친 뒤엔 통과한다. 이 두 가지를 모두 확인해야 테스트를 수정의 증거라고 말할 수 있습니다.

처음부터 통과한 테스트는 따로 표시하기

같은 테스트에서 “다른 사람의 연주를 넣으면 거절해야 한다"도 함께 검사했는데, 이 부분은 고치기 전에도 통과했습니다. 원래 맞게 동작하던 부분이었기 때문입니다.

이런 테스트도 앞으로 동작이 깨지지 않게 지켜 주니 쓸모가 있습니다. 다만 이번 수정의 증거는 아니라서, 작업 기록에 둘을 나눠 적었습니다.

구분고치기 전고친 뒤
수정을 증명하는 테스트실패통과
기존 동작을 지키는 테스트통과통과

새 테스트를 추가할 때 확인할 세 가지

코드를 고치기 전에 새 테스트를 한 번 돌려 보고, 다음을 확인해 보세요.

  1. 실패하는가?
  2. 실패한 이유가 바로 그 버그인가?
  3. 처음부터 통과했다면, 수정을 증명하는 테스트인가, 기존 동작을 지키는 테스트인가?

물론 이것만으로 모든 게 증명되지는 않습니다. 기대값을 느슨하게 적으면(예: “비어 있지 않다"만 검사) 엉뚱한 결과도 통과할 수 있고, 응답 형식이 팀의 약속과 맞는지는 리뷰로 따로 봐야 합니다. 그래도 이 세 가지만 지켜도, 테스트가 몇 개인지보다 각 테스트가 무엇을 증명하는지를 말할 수 있게 됩니다.