다른 글 보기

테스트에 대한 주저리

제가 이해하기로는 테스트는 이 코드가 어떻게 동작해야 정상 동작인지를 기술해두는 것입니다. 코드라고 말했지만 크게는 어플리케이션이고 그것을 수동으로 행할 수도 있고 기계적으로 자동으로 돌게끔 할 수 있습니다.

만약 기계적으로 자동으로 돌게끔 한다면 코드에 변경 사항이 생겼을 때 그것이 원래 의도한 동작을 깨뜨리지는 않는지 감지하기 쉽게 해줍니다 (regression). 만약 A를 수정하였을 때 그 이유가 A의 스펙이 바뀌었기 때문이라면 A의 테스트가 바뀌는 것은 맞지만, 그렇지 않다면 테스트가 그것을 감지해주는 것이죠.

크기가 작은 코드베이스라면, 혹은 비상한 해커(?)—머리 속에서 상태 계산을 하나도 틀림 없이 모두 해낼 수 있으며, 그 컨텍스트를 유지할 수 있는—라면 괜찮겠지만 제 경우에는 그렇지 않기 때문에 당시의 의도를 테스트로 남기고 인지를 다른 쪽으로 돌려야 합니다. 협업 측면에서 테스트의 장점이라고 생각하는 부분은 두 가지입니다.

우선 코드 리뷰할 때 용이합니다. 앞서 말했듯이 테스트는 코드가 어떻게 동작해야하는지 의도를 담기 때문에 저는 실질적으로 코드 리뷰를 할 때 PR description에 적혀있는 의도와 함께 테스트 코드를 주로 보았습니다. PR에 적힌 의도에 부합하게 테스트 코드가 충분히 커버하는지, 그리고 테스트 코드가 상정한 상황 밖에 다른 상황은 없는지 고민하며 리뷰했었습니다.

그리고 자동화된 테스트라면 GitHub Actions(CI) 같은 곳에서 테스트를 수행하도록 하여 함께 작업하는 분들에게도 문맥을 완전히 전하지 않고도 테스트를 통해 기존 구현의 의도에 대한 정보값(피드백)을 제공할 수 있다는 점입니다.

사실 이 부분은 지금 같이 일하고 있는 동료일 경우 크게 와닿지 않지만 다른 분에게 프로젝트를 인수인계하거나, 새로운 분이 들어올 때는 당연히 전체적인 문맥이 없기 때문에 이런 자동화된 테스트는 상당히 도움이 된다고 느꼈습니다.


앞의 서론이 생각보다 길었지만 이 글 파일을 생성했을 때 적고 싶었던 내용은 스냅샷 테스팅과 프로퍼티 테스팅에 관한 내용이었습니다.

우선 스냅샷 테스팅은 제가 이해한 바로는 어떤 값이 변하지 않았는지 검사하는 것입니다. 어떤 값이라고 말했지만 보통 무언가의 평가 값이고 이것이 변하면 안 될 때 사용하는 것이라고 이해하고 있습니다.

처음 접했던 것은 HTML 스냅샷 테스트 같은 것이었고 코드를 리팩터링을 하다가 UI 표현이 깨지는 등의 이슈를 막는다.. 같은 거였던 것 같습니다. 사실 테스트에 코드로 기대값을 기술하는 방식도 가능은 하지만 그렇게 상세히 기술하는데 드는 비용이 보통 더 커지고, 그 안의 값이 정확히 무엇인지가 크게 중요하지 않은 경우에 사용한다고 이해하고 있습니다.

눈으로 스냅샷 간에 바뀐 값을 보고 새로운 스냅샷을 새로운 정본으로 받아들이고 사용하거나 아니면 의도하지 않은 변경사항이니 원래의 스냅샷과 같은 결과가 나오게끔 코드를 다시 수정해야 합니다.

저는 전에 일할 때 직렬화 결과에 스냅샷 테스트를 붙였었습니다. 해시 값이 중요한 특성 상 직렬화 결과가 바뀌어서는 안 되었는데 여기에 스냅샷 테스트가 적절하다고 생각했습니다. 혹은 유닛테스트 쪽에 하드코드해서 직렬화 결과를 넣을 수도 있겠지만 그것보다는 별도로 스냅샷 파일이 분리되어 있는 것이 좋았습니다.

바이너리 값의 경우 더욱 그렇고, 짧은 문자열의 경우에는 스냅샷 파일로 분리하기 보다 임베딩하여 일반 유닛테스트처럼 쓰는 것이 오히려 테스트 코드를 보는데 용이했습니다.

프로퍼티 테스팅의 경우 이유는 모르겠지만 저는 특정 범위의 입력을 랜덤하게 받아서 어떤 연산의 멱등성을 보여주는 예제로 처음 배웠던 것으로 기억합니다. 처음에는 이거를 어디에 쓰지? 라는 생각이었습니다. 멱등성 예제에 꽂혀서 인지 실제로 그런 경우가 잘 없다고 생각했었기 때문입니다.

그러다가 최근에 fuzz 테스트를 붙일 때 프로퍼티 테스트 도구를 유용하게 활용하였습니다. bencodex-rs JSON 인코딩 쪽에 버그가 있었는데 이때 fuzz 테스트를 만들어서 랜덤한 Bencodex 값을 생성하고 이것들을 유효한 JSON으로 잘 인코딩하는지 테스트하도록 하였습니다.

그리고 SIMD 기능을 넣을 때도 유용하게 활용하였습니다. SIMD에 대해 깊은 지식 없이 코딩 에이전트에게 맡겨 기능을 추가했지만 아래와 같은 테스트들을 추가하여 기능 호환성을 갖췄다고 믿을 수 있었습니다.

프로퍼티 테스트의 본질은 “어떤 정해진 범위 내에서의 동작은 이럴 것이다”를 기술하는 것이라고 느꼈습니다. 커버리지를 넓게 가지기 위해서 여러 조합에 대한 테스트 코드를 모두 작성하고는 해야 하는데 이를 쉽게 달성할 수 있도록 도와주는 것입니다.


여담으로 오픈소스에 버그를 찾아 기여할 때는 항상 regression 테스트를 추가하는 것이 좋습니다. regression 테스트를 추가해둬야만 해당 버그가 모종의 이유로 재발했을 때 다시 알아차릴 수 있기 때문입니다. 때문에 기여할 때 가능한 regression 테스트를 추가하고자 노력합니다. regression 테스트라고는 하지만 사실 부족한 테스트 케이스를 채워넣는 것과 동치입니다.

https://github.com/denoland/deno/pull/31711