Storybook에서 평소랑 다를 것은 없습니다.
하지만, 기본적으로 CSS를 변경하는 등의 상황에 발생하는 문제를 미리 감지하고자하는 목적이기에, 가능한 많은 상황에 대한 Story를 미리 작성해 두시면 좋습니다.
BackstopJS
애플리케이션 기본 설정
먼저, apps/visual-regression 폴더에 시각적 회귀 테스트를 위한 애플리케이션을 제작해야 합니다.
backstopjs, @types/backstopjs 두 패키지만 설치하면 기본적으로 설치해야할 패키지는 끝입니다.
npx backstop init 명령어를 입력하시면 위와 같이 설정 파일과 함께 backstop_data 폴더에 BackstopJS가 사용할 파일들이 생성됩니다.
위와 같이 불필요한 폴더들을 .gitignore에 추가해 주고
혹시 필요하다면, cookies.json에 쿠키를 추가해 줄 수 있습니다.
다음으로, package.json에 위와 같이 BackstopJS에서 사용할 명령어들을 추가했습니다.
마지막으로, 프로젝트의 root에 있는 package.json에 test:visual-regression 명령어를 추가해 주시면, turbo를 통해 실행할 수 있습니다.
Express 서버 구성
Storybook에 작성한 문서를 확인해야 하기에, Express 서버를 구성해야 합니다.
필요한 기능이라곤 Storybook 빌드 결과물을 그대로 서빙해주는 것 뿐이기에, 위와 같이 아주 간단하게 구성할 수 있습니다.
Storybook의 빌드 결과물이 반드시 필요하기에, turbo.json에 위와 같이 의존성을 추가해 주시면 실수할 여지를 많이 줄일 수 있습니다.
마지막으로 서버를 기동할 명령어를 추가해 주시면 됩니다.
Storybook 빌드 결과 가져오기
이제 테스트를 진행할 시나리오들을 backstop.config.js에 추가해야 합니다.
Storybook 빌드 결과물에는 index.json 파일이 있습니다.
해당 파일에는 Storybook에 작성한 문서들에 대한 정보가 있기에, 이를 이용해 시나리오를 추가할 수 있습니다.
위와 같이 index.json 파일을 불러와 시나리오를 추가해 주시면 됩니다.
문서 파일은 두 번 검사할 필요가 없으니 제거하고, 혹시 테스트가 필요 없는 경우에는 blackList에 추가할 수 있도록 구성했습니다.
Storybook 내부에 사용되는 iframe.html 파일을 사용하면 별다른 노력을 들이지 않아도 접속할 수 있는 주소를 얻을 수 있습니다.
테스트 실행
여기까지 완료되었으면, 테스트를 실행해볼 수 있습니다.
로컬에서 테스트를 실행하려면, backstop:reference 명령어를 실행해 기준이 될 파일들을 생성하고, test:visual-regression을 실행하면 됩니다.
GitHub Actions 파이프라인 구성
매번 로컬에서 테스트를 실행할 순 없으니, GitHub Actions를 통해 자동화하였습니다.
더불어 컨테이너 환경에서 실행되기에, 별도로 이미지를 생성하지 않아도 일관된 결과를 얻을 수 있습니다.
변경 사항 테스트
Pull Request를 생성했을 때, 변경 사항을 테스트하기 위한 파이프라인을 구성했습니다.
기존에 생성되어있던 Reference 스크린 샷과, 변경된 스크린 샷을 비교해 결과를 확인하고, PR에 댓글로 결과를 추가해 줍니다.
제 개인 블로그에 간단하게 적용한 파이프라인이라, 테스트 결과를 단순히 GitHub Artifact에 업로드하는 방향으로 구성했습니다.
여러 사람이 작업하는 경우에는, S3에 파일을 업로드하고 주소를 댓글로 추가하는 방향으로 구성해보실 수 있습니다.
Reference 파일 업데이트
PR이 승인되고, master 브랜치가 업데이트되면 이제 Reference 파일을 업데이트해야 합니다.
먼저, Reference 파일을 업데이트하고, 이를 커밋하기 위한 스크립트를 추가했습니다.
다음으로, PR을 확인할 때와 마찬가지로 테스트를 수행하는 파이프라인을 추가했습니다.
다만, PR을 확인할 때와 달리, 테스트를 진행한 뒤 그 결과를 바탕으로 Reference 파일을 업데이트하고, 이를 커밋하는 작업을 추가했습니다.
댓글에 상세하게 테스트 결과 추가
여기까지 진행한다면, 위와 같이 아주 간단한 형태로 성공 / 실패 여부만 댓글로 추가됩니다.
하지만 좀 더 자세하게 각 컴포넌트별로 테스트 결과를 확인하고 싶다면, 추가적인 작업을 진행하실 수 있습니다.
테스트가 완료되면 backstop_data/bitmaps_test/$TEST_ID/report.json 파일에 위와 같이 테스트 결과가 저장됩니다.
이를 파싱하여 PR 댓글에 추가해주는 작업을 추가해보겠습니다.
위와 같은 스크립트를 작성하면 가장 최근에 생성된 리포트를 파싱해 테스트 결과를 출력할 수 있습니다.
Multiline string을 사용하기에 output보다는 env가 훨씬 편해서 TEST_RESULT라는 환경 변수에 결과가 저장되도록 했습니다.
사족으로, ::set-output 사용하던 시절에는 0%A로 개행 문자를 추가할 수 있었는데, >> "$GITHUB_OUTPUT"으로 output 파일에 추가하는 방식으로 업데이트되고 개행 문자 처리가 아주 귀찮아졌습니다...
출력할 결과에는 컴포넌트 명, 컴포넌트 스토리 이름, 성공 여부, 뷰포트, 부정합 정도를 포함해 구성했습니다.
만약 추가적인 정보가 필요하시다면, tableHeads와 tableData를 수정하시면 됩니다.
만약 수정하신다면, 위와 같이 테이블 헤드와 데이터의 길이가 같은지 확인하는 코드를 추가해 주시면 좋습니다.
이제 yaml 파일에서 해당 스크립트를 실행하고 환경 변수에 저장하는 작업을 추가하고, body를 구성할 때 ${{ env.TEST_RESULT}}를 추가해 주시면 됩니다.
결과
여기까지 완료했다면, PR을 생성했을 때나, master에 커밋했을 때 위와 같이 Visual Regression Test 수행 결과를 댓글로 등록해 주고,
Master 브랜치가 업데이트되면 자동으로 reference 파일을 업데이트 해주게 됩니다.
마치며
항상 프론트엔드에서의 테스팅에 대해 고민이 많았습니다.
백엔드에서 코드를 작성할 땐 나름 입출력이 정확하고, 환경을 제가 제어할 수 있으니 명확하게 테스트할 수 있었지만, 프론트엔드는 거대하게 상태가 관리되고, 브라우저라는 외부 환경에서 동작하기에 테스트가 어려운 경우가 많았습니다.
더군다나, 로직 수정 중에 발생하는 레이아웃 관련 Side Effect는 눈으로 확인해야만 하는 경우가 꽤 많았습니다.
하지만 시각적 회귀 테스트를 도입하며 UI에 발생하는 문제를 사전에 감지하고, 효율적으로 관리할 수 있게 되었습니다.
또한, CI 파이프라인에 이 과정을 추가할 수도 있기에, 개발 프로세스 전반에서 발생할 수 있는 UI 관련 버그를 줄일 수 있게 되었습니다.
입출력이 정확한 함수들에 유닛 테스트를 추가해 두고, 핵심 로직에 Playwright 등을 사용해 E2E 테스트를 추가한 뒤, 시각적 회귀 테스트까지 추가해 두니 코드 변경에 대한 부담이 여러 방면에서 많이 줄어든 것 같습니다.
여담으로, 이렇게까지 복잡할 필요가 없는 애플리케이션인데 블로그에 push 한 번 하면 돌아가는 CI 파이프라인이 이렇게나 많아져 버렸습니다.