서론

최근들어 프론트엔드 개발에서의 테스트 방법을 생각해봤습다. 고민들에는 프론트엔드 테스트코드 실질적 효과와 효율성과 어떤 코드들을 테스트해야 하는지에 대해서 의문을 제기해왔습니다. 그러던 도중 이글을 만나게 되었습니다.

이 글은 Nick Gard"React Snapshot Testing: The Bad Parts"를 번역한 글입니다. (Nick Gard님께 번역 및 공유 허락을 받았습니다.)

Jest의 스냅샷 테스팅은 리액트 컴포넌트의 UI를 빠르고 쉽게 테스트할 수 있는 방법으로 환영받았지만, 스냅샷 테스팅을 사용해보면서 몇 가지 심각한 단점들이 드러났습니다. 나는 더 이상 스냅샷 테스팅을 사용하지 않고, 스냅샷 테스팅을 사용하는 것을 추천하지 않습니다. 여기 몇 가지 이유들이 있습니다.

TDD/BDD 가이드 라인을 따를 수 없다.

테스트 주도 개발행위 주도 개발의 핵심 규칙은 테스트를 먼저 작성하고, 필수적인 행위/API 명세의 윤곽을 그리는 것입니다. 테스트는 초기에 실패해야 합니다.(왜냐하면 테스트되어야 하는 것이 아직 작성되지 않았기 때문이죠.) 하지만 코드가 명세를 만족하게 되면 테스트는 통과될 것입니다.

스냅샷 테스팅은 초기에 실패합니다. 확인해봅시자. 이제 컴포넌트를 작성하고, 테스트를 해봅시다. 테스트는 절대로 성공하지 않습니다. 사실은, 스냅샷 테스팅은 jest --update-snapshots 를 실행하기 전까지 절대로 성공하지 않을 것입니다. 이 명령어는 컴포넌트가 완전하고 유효한 상태인지 여부에 관계없이 컴포넌트의 현재 마크업을 True Markup™으로 영구화 할 것입니다.

True Markup™의 의미를 작가님께 여쭤봤더니 실제로 그런 단어는 없고 과장된 표현이라고 말씀하셨습니다.
마크업이 항상 변경된다는 점을 지적하며 마지막 마크업에 따라 테스트 성공 여부가 결정된다는 점을 강조하셨습니다. 따라서 이해하실 때에는 최종적으로 완성된 마크업으로 이해하시면 되십니다.

코드의 변경없이 테스트를 "수정한다".

스냅샷 테스팅을 실패해본 경험이 있습니까? 괜찮습니다. jest --update-snapshots 를 실행하면 테스트는 통과됩니다. 여러분의 동료는 여러분께서 스냅샷 테스팅의 실패 출력물을 읽지 않고 무엇이 실패했는지 이해하지 않았지만, 사소한 CSS 변경으로 인한 일부 스냅샷 테스트 실패가 예상됩니다. 테스트를 쉽게 "고칠 수 있다면", 왜 테스트를 디버그하는데 시간을 쓸까요?

수수께끼 같은 실패

그래요, 여러분께서는 스냅샷 테스트 실패 출력물을 읽는데 시간을 쓸 것입니다. 아무튼, 그 출력물은 사람이 읽을 수 있고, 일반적인 HTML의 형태로 다른 부분을 보여줘야 합니다. 하지만, CSS-in-JS 라이브러리(Glamor)에서 CSS 규칙을 수정했다고 가정해봅시다. 출력물은 다음과 같습니다.

image.png

data-css-1lffealdata-css-hzy630으로 바뀌었네요. 뭐라고요? 스냅샷 테스팅은 리액트의 JSX 마크업의 출력을 검사하고 브라우저에서 실제로 렌더링하는 픽셀은 검사하지 않습니다. 하지만, 무의미한 CSS 변경 - 여백이나 속성 선언 순서 같은 변경 - 속성이나 클래스 이름 해시 변경의 결과 (대부분의 CSS-in-JS 라이브러리의 작동 방식)는 테스트 실패를 일으킬 것입니다. 이 해시 변경이 오직 여러분이 의도한 변경 사항만 포함하는지 확신할 수 있을까요?

막연한 단언문들은 막연한 수정을 일으키는 막연한 실패를 일으킨다.

빈약한 테스트 행위

테스트들은 우리에게 주는 정보로서 유용합니다. 좋은 테스트 스위트(Test Suite)는 코드 변경이 회귀 버그를 일으키지 않았다고 알려줍니다. 이것이 코드 커버리지가 중요한 이유입니다. (코드 커버리지는 상대적으로 버그가 없는 상태에 있는 어플리케이션이 얼마나 많은지 알려줍니다.) 그리고 또한 버그 수정은 반드시 버그가 발생한 테스트와 동반되어야 하는 이유입니다. (이로 인해 이후의 변경으로 버그가 다시 발생하지 않겠죠.)

유효성좋은 테스트의 4가지 핵심 특성들 중 하나입니다. (다른 특성들은 확실성, 객관성, 유용성입니다.) 유효한 테스트는 테스트가 의미하는 것을 측정할 뿐만 아니라 ("실행이 아닌 행위"라고 생각해라) 테스트된 부분이 실패한 경우에만 실패를 등록합니다. 만약 테스트가 통과했을 때, 버그가 존재한다면, 그것은 위양성(음성이지만 양성으로 나온 결과 즉, 모순되는 결과)을 띄고 있는 것입니다. 만약 테스트 중인 부분에서 버그가 실제로 존재할 경우에만 테스트가 실패를 등록한다면, 테스트는 유효한 것입니다. 스냅샷 테스팅은 이 두 가지 유효성 정의에 모두 실패합니다.

image.png

테스팅 스냅샷은 테스트하는 UI를 추측합니다. 하지만 실제로는 이전과 현재 컴포넌트가 렌더링하는 최종 마크업 (HTML)을 비교합니다. 마크업은 오직 UI를 구성하는 일부분일 뿐 입니다. 만약 여러분이 스냅샷 테스팅의 동작을 무시하더라도, 스타일링은 여전히 고려되지 않았다. 스냅샷 테스팅을 "UI 테스트"라고 부르는 것은 오해의 소지가 있습니다.

컴포넌트의 render 메서드의 결과를 테스팅함으로서, 스냅샷 테스팅은 전부가 아닌 일부분의 라이프사이클 메서드를 트리거할 수 있습니다. 이렇게하면 이 메소드들의 모든 라인, 함수, 그리고 브랜치 커버리지 통계까지 실행합니다. 하지만, 렌더링되는 출력에 상관없는 메소드들에 부수효과(side-effects)가 있을 수 있습니다. - 리스너를 등록하거나 데이터를 가져오는(fetching data) 등 - 그리고 저런 행위들이 스냅샷 테스팅에 포착이 되지 않습니다. 테스트 커버리지 리포트들은 이 코드들이 커버됐다고 표시하지만, 이것은 양음성입니다.

이전에 언급했던 것처럼, 스냅샷 테스트는 버그가 발생하지 않는 변경사항에서 쉽게 실패할 수 있습니다. 이것은 양음성입니다. 스냅샷 테스트가 유용 할 수는 있지만 너무 자주 유효하지 않습니다.

UI는 정적이지 않다

스냅샷 테스팅은 컴포넌트를 위한 True Markup™이 있고 바뀌지 않아야 한다는 전제를 기반합니다. 하지만 UI 변경은 우리가 웹 개발자로서 하는 일입니다. 월드컵이 다가오는 주에 헤더는 다른 색이어야 합니다. 이 공백은 새로운 디자이너의 생각에는 너무 좁습니다. 마크업은 쓰여진대로 접근하기 너무 힘듭니다. 따라서, aria-* 속성을 필요로 합니다. 잠시만… aria 속성은 잘 지원되는 속성이 아닌데 -이 속성으로 바꿔야겠습니다.

우리가 모호하게 그리고 일반적으로 아무것도 변경되지 않았다는 것을 확인할 때, 오히려 일부만이 변경되지 않았다는 것을 의미합니다. 우리는 결국 허술한 테스트로 끝나게 됩니다. 아무튼, 불변하는 것은 변화뿐입니다. 이것은 프론트엔드 웹 개발에서 특히 그렇습니다.


후기

평소 프론트엔드 테스팅에 대해서 관심이 많았습니다. 그래서 인터넷에서 테스팅에 관한 자료들을 이것저것 찾아보고 따라서 연습도 해보고 프로젝트에 적용을 해나가던 도중 이 글을 읽게 되었습니다. 지금까지 해왔던 저의 모습을 돌아보니, 아무런 생각없이 "테스트하면 좋은거지!" 이런 마인드로 그냥 무작정 진행해왔던 모습이 떠올랐습니다.

이런 좋은 글을 읽게 되고 번역하면서 테스트에 대한 지식들을 더 많이 알게 된 것 같습니다. 앞으로는 무엇을 하더라도 하는 목적과 이유를 생각하면서 해야 된다는 반성을 하게 됐습니다... 긴 글 읽어주셔서 감사합니다!