상품 등록이 중간에 실패하면 만들다 만 데이터가 남고 그것을 지우는 rollbackProduct() 가 있었다. 그 로직이 전부 지우는지 확인하는 일이었는데 처음에는 grep -rn "create(" 결과를 대조해 전부 커버한다고 보고했다.
Table of contents
Open Table of contents
문제 — 검색으로는 세울 수 없는 주장
그 방식으로는 그 주장을 할 수 없다는 지적을 받았고 맞는 지적이었다. grep 은 registerProduct() 가 호출하는 다른 서비스 안에서 일어나는 쓰기를 볼 수 없다.
create() 와 firstOrCreate() 와 save() 처럼 생성 계열 메서드의 이름이 여럿이고 hasMany 관계를 통해 자동으로 만들어지는 것도 있으며 조건부로만 생성되는 것도 있다. 반대로 검색에는 걸리지만 이 경로에서는 돌지 않는 코드도 함께 잡힌다.
원인 — 공통 함수 안에 있던 쓰기
두 가지 방법이 있었는데 검색 패턴을 생성 메서드 이름별로 늘리는 것과 registerProduct() 본문을 처음부터 끝까지 읽고 거기서 호출하는 메서드도 하나씩 열어 읽는 것이다. 뒤쪽을 골랐다. 이유는 패턴을 아무리 늘려도 이 경로에서 실제로 도는지는 검색이 답하지 못해서다. 대신 메서드 열몇 개를 손으로 따라가는 시간을 감수했다. 그러자 applyDefaults() 안에서 option_groups 하나를 더 저장하고 있다는 것이 드러났다.
검색만으로는 그 저장이 등록 경로에서 실제로 도는지 알 수 없다. CertificationHandler 안에서도 조건이 맞을 때 files 와 media 를 만들고 있었다.
없는 것을 지울 필요는 없다
트리를 따라가니 반대 방향도 정리됐는데 rollbackProduct() 에 없는 네 가지 항목이 있었다. 처음에는 누락으로 봤지만 등록 경로에서는 아예 생성되지 않고 나중에 다른 경로에서 만들어지는 것들이었다.
생성되지 않는 것은 지울 대상도 아니므로 보상 로직에 없는 것이 옳다. 쓰기 코드가 존재한다는 사실과 이 경로에서 그 코드가 돈다는 사실은 다르다.
판단 기준 — 남으면 무엇이 되는지로 정한 우선순위
전수로 확인한 결과 실제 누락은 인증 관련 항목 하나였고 그것도 조건부였다. 고아 레코드가 생기려면 두 조건이 동시에 성립해야 한다. 인증을 함께 등록하고 그 뒤 단계에서 실패하는 경우다.
남았을 때 무슨 일이 생기는지 보니 files 행일 뿐이고 판매 상태와 무관하며 조회에도 걸리지 않았다. 그래서 병합을 막지 않는 후속 과제로 분류했는데 누락이니 고쳐야 한다가 아니라 남으면 무엇이 되느냐로 판단한 것이다.
결론 — 근거가 정하는 주장의 강도
보고를 다시 낼 때 어떻게 확인했는지를 명시했다. 등록 경로와 그것이 호출하는 메서드까지 전이적으로 추적해 쓰기 집합을 확정했다고 적었다. grep 결과가 아니라는 것도 함께 적었다.
검색해 봤다는 것과 호출 트리를 따라갔다는 것은 결론이 같아도 서로 다른 주장이다. 앞의 근거로 전부 커버한다고 말하면 그 문장은 다음 사람에게 확인된 사실로 넘어간다.
정리
grep은 커버리지의 증거가 아니다- 호출한 메서드 안에서 일어나는 쓰기를 볼 수 없다
- 검색에 걸려도 그 경로에서 안 도는 코드가 있다
- 보상 범위는 호출 트리를 전이적으로 추적해 확정한다
- 보상 로직에 없다고 누락인 것은 아니다
- 생성되지 않는 것은 지울 대상도 아니다
- 남으면 무엇이 되느냐로 우선순위를 정한다
- 어떻게 확인했는지가 주장의 강도를 정한다