목록 보기
BackstopJS 적용 후기 (Visual Regression Test)
기타

BackstopJS 적용 후기 (Visual Regression Test)

글을 시작하기에 앞서 BackstopJS에 대한 설명과 설정 방법이 궁금하신 분들은 시각적 회귀 테스트 BackstopJS 적용하기 (Visual Regression Test)를 확인해 주시면 감사하겠습니다. 이번 글에서는 BackstopJS 적용 이후 6개월 동안 카카오엔터테인먼트 스토리 FE팀에선 BackstopJS를 활용하며 어떤 식으로 도움이 되었는지 살펴보고, 겪었던 이슈와 테스트에 자동화를 적용한 사례를 소개하도록 하겠습니다. BackstopJS 활용법 BackstopJS는 설정 파일에 정의한 url을 통해 헤드리스(headless) 브라우저를 직접 띄워서 테스트를 진행하게 됩니다. 따라서, 실제 서비스 페이지 단위별로 테스트가 가능합니다. 하지만 실제 서비스 단위의 페이지는 기획이나 디자인의 변경으로 인해 자주 바뀔 위험이 있습니다. 그러한 이유로 저희 팀에서는 스토리북의 빌드 결과물을 기반으로 BackstopJS를 구축하였습니다. 1. Organism 단위 테스트 저희 프로젝트는 현재 Atomic 디자인 시스템을 사용하고 있어서, Atomic 디자인 시스템의 Organism 단위로만 스토리북에서 테스트를 진행하고 있습니다. 아토믹 디자인에는 atoms, molecules, organisms 와 같은 구성 요소들로 이루어져 있습니다. 보통 atoms 와 같은 작은 단위의 컴포넌트(button, input )들은 변경사항이 적겠지만, 기획이 변경되면서 UI까지 바뀌는 단위는 보통 organisms 단위부터 크게 바뀌게 됩니다. 작은 컴포넌트들을 포함한 단위인 Organisms(유기체)가 복합적으로 기획의도까지 포함되어 있어서, 복합적인 UI 단위 테스트를 하기에 적합하다고 생각이 되어 organisms 단위로 테스트를 생성하게 되었습니다. 따라서 새로운 Organism 스토리북을 작성하게 되면, BackstopJS의 시나리오 설정 파일에 스토리북 url을 추가하여 테스트하고 있습니다. 원래 backstop.json에 설정값을 정의하는데, 테스트 시나리오가 많아져서 시나리오 리스트를 생성하는 createBackstoScenario.js를 따로 생성해서 사용하고 있습니다. BackstopJS의 설정 예시는 이전 블로그 글이나 공식 깃허브를 참고해 주세요 이전 블로그 : https://fe-developers.kakaoent.com/2023/230223-backstopjs-vrt/#—scenarios-설정 공식 깃허브 : https://github.com/garris/BackstopJS/tree/master/examples/simpleReactApp // backstop.js const createBackstopScenarios = require('./createBackstopScenarios.js')

module.exports = ( ... scenarios: createBackstopScenarios, ) createBackstopScenarios.js에서는 중복으로 유지되는 시나리오 설정값들과 여러 시나리오에 따라 바뀌는 url을 넣어주게 됩니다. // createBackstopScenarios.js module.exports = [ ( label: label, url: http://127.0.0.1:8080/iframe.html?id=organisms-$(url)&ampviewMode=story, referenceUrl: '', readySelector: '', delay: 500, hideSelectors: ['#backstopHideSelector'], removeSelectors: [], hoverSelector: '', clickSelector: '', postInteractionWait: 0, selectors: [], selectorExpansion: true, expect: 0, misMatchThreshold: 0, requireSameDimensions: true, ), ... ] 2. 사이드이펙트 방지 기획 변경과 리팩토링 등의 다양한 이유로 코드가 수정될 수 있습니다. 이때 BackstopJS를 사용할 경우 모든 컴포넌트의 변경사항을 확인할 수 있어 사이드이펙트를 쉽게 발견할 수 있습니다. 사이드이펙트의 예시로 날짜 공통 코드를 사용한 컴포넌트들을 살펴보겠습니다. 다음 이미지들은 날짜 포맷을 변경해 주는 로직을 2023.01.01 → 23.01.01 으로 수정했을 때의 BackstopJS 테스트 결과입니다. 원래 의도는 공지사항의 날짜만 수정하고자 했는데, 의도하지 않은 변경점이 발생한 것을 확인할 수 있습니다. 의도된 변경점 첫 번째 이미지인 공지사항 날짜만 수정하기 위해 날짜 포맷 함수 로직을 수정했습니다. 그리고 git push 때 실행된 BackstopJS 테스트 결과물을 보니 해당 로직을 사용하는 다른 컴포넌트에서 테스트가 실패하여 컴포넌트가 깨지는 부분을 확인할 수 있었습니다. 이런 방식으로 테스트가 깨지는 컴포넌트들을 빠짐없이 발견할 수 있어 사이드 이펙트를 미리 잡을 수 있었습니다. 3. 스토리북 문서화 BackstopJS는 스토리북과 연결되어 있으므로, 스토리북 자체가 깨지거나 업데이트되지 않을 경우도 방지할 수 있게 해줍니다. 스토리북 수정을 하지 않아 깨지는 경우 이미지 예시 스토리북이 깨질 경우 에러 화면 자체가 테스트 스크린샷으로 잡히기 때문에, 두 개의 BackstopJS 테스트가 실패한 것을 확인할 수 있습니다. 또한, 효율적인 BackstopJS 테스트를 위해 여러 케이스(각 뷰포트 별 케이스, 특정 컴포넌트의 기획 조건에 따른 다양한 상황들) 별로 정의해서 스토리북에 추가한다면 테스트 자동화도 가능하고 케이스별 문서화가 될 수 있습니다. 다음은 배너를 PC/Foldable/Mobile 케이스별로 스토리북을 정의하고 BackstopJS에 테스트를 추가한 예시입니다. // 테스트 케이스별 정리된 스토리북 이미지 예시 // createBackstopScenarios.js const organisms = [ ( label: 'FoldableStandaloneTopBanner', url: 'bigbanner--foldable-standalone-top-banner-story&ampglobals=colorMode:light', ), ( label:'FoldableTopBanner', url:'bigbanner--foldable-top-banner-story&ampglobals=colorMode:light', ), ( label:'MobileTopBanner', url:'bigbanner--mobile-top-banner-story&ampglobals=colorMode:light', ), ( label:'PcResponsiveBanner', url:'bigbanner--pc-responsive-banner-story&ampglobals=colorMode:light', ), ( label:'PcStandaloneTopBanner', url:'bigbanner--pc-standalone-top-banner-story&ampglobals=colorMode:light', ), ( label:'PcTopBanner', url: 'bigbanner--pc-top-banner-story&ampglobals=colorMode:light', ), ]

const organismsBackstopList = organisms.map((label, url) => (( label: label, url: http://127.0.0.1:8080/iframe.html?id=organisms-$(url)&ampviewMode=story, referenceUrl: '', readySelector: '', delay: 500, hideSelectors: ['#backstopHideSelector'], removeSelectors: [], hoverSelector: '', clickSelector: '', postInteractionWait: 0, selectors: [], selectorExpansion: true, expect: 0, misMatchThreshold: 0, requireSameDimensions: true, )))

module.exports = organismsBackstopList BigBanner라는 하나의 컴포넌트에서 상황에 따라 여러 가지 다른 디자인을 케이스로 정리해 테스트할 수도 있습니다. 배너의 여러 가지 스토리북 케이스 PC 반응형 배너 모바일 상단 배너 PC 상단 배너 폴더블 상단 배너 스토리북으로 인한 갑작스러운 에러 전체적으로 패키지를 업데이트하게 되면서 스토리북 버전 또한 6에서 7로 업데이트하게 되었는데, 업데이트를 하고 나니 BackstopJS 테스트가 전부 뜨지 않아 모든 테스트가 실패로 처리되는 상황이 발생했습니다. 알고 보니 스토리북 7버전에서 파일시스템을 더 이상 지원하지 않아, 직접 서버를 띄워야 한다는 내용이 업데이트 내용에 적혀있었습니다. Dropped support for file URLs In 6.x it was possible to open a Storybook build from the file system. ESM requires loading over HTTP(S), which is incompatible with the browsers CORS settings forfile://URLs. So you now need to use a web server as described above. 기존 구조는 스토리북 빌드 결과물인 storybookOutput 디렉터리를 전부 도커에 복사한 후, 도커 컨테이너로 서버를 띄웠을 때 도커 내부 파일시스템에 복사된 storybookOutput 디렉터리에 접근해 BackstopJS test를 진행하는 방식이었습니다. 더 이상 파일시스템을 지원하지 않으므로, 도커 내부에서 http 서버를 띄워서 테스트를 실행하도록 수정했습니다. -) url: .storybookOutput/iframe.html?id=organisms-$(key[1])&ampviewMode=story, +) url: http://127.0.0.1:8080/iframe.html?id=organisms-$(key[1])&ampviewMode=story, 테스트를 관리하기 귀찮은 분들을 위한 자동화 1. PR에서 바로 테스트 결과를 확인할 수 있도록 자동화 BackstopJS 테스트 후 결괏값은 backstop_data 폴더에 생성됩니다. Docker는 backstop test 커맨드의 결과물을 /storybookOutput 디렉터리에 복사한 후, 해당 디렉터리를 URL로 접근 가능하도록 http-server를 띄워주는 역할을 수행합니다. 그 이후 저희 팀 Steve가 작업해 주신 PR 봇과 젠킨스를 통해서 BackstopJS의 성공과 실패 개수, 스토리북 및 backstop report 링크를 PR 코멘트로 작성되도록 하여 쉽게 확인할 수 있었습니다. 2. PR이 머지 된 이후 테스트 결과를 반영할 수 있도록 자동화 PR이 머지 된 이후 develop에 push가 되면, 젠킨스에서 도커를 통해 BackstopJS 테스트 결과가 저장되어 있던 backstop_data에서 approve(테스트 결과로 이미지를 교체) 해주고, 변경사항을 backstop approve라는 커밋을 남긴 후 git push를 하게 됩니다. 이렇게 되면 의도적으로 수정한 부분이지만 기존 reference 이미지와 달라 실패했던 backstop 테스트들을 자동으로 갱신할 수 있습니다. 젠킨스에서 develop에 push 될 경우 BackstopJS 테스트 결과를 커밋하고 푸시 해주는 파이프라인 코드는 다음과 같습니다. # Jenkins Pipeline

pipeline ( agent any

stages (
    stage('Checkout') (
        steps (
            git branch: 'develop', credentialsId: (아이디), url: (깃허브 주소)
        )
    )
    
    stage('Docker Pull') (
        steps (
            sh '''
            DOCKER_IMAGE_TARGET_REMOTE_NAME=(도커 이미지)

            docker pull $DOCKER_IMAGE_TARGET_REMOTE_NAME
            id=$(docker create $DOCKER_IMAGE_TARGET_REMOTE_NAME)
            
            rm -rf packages/backstop_data/
            docker cp $id:packages/backstop_data packages
            docker rm $id
            docker rmi -f $DOCKER_IMAGE_TARGET_REMOTE_NAME
            '''
        )
    )
    
    stage('Backstop Approve') (
        steps (
            sh '''
            cd packages/backstop_data
            
            cd bitmaps_test
            report=$(ls -1 | tail -n 1)
            
            cd ../
            
            rm -rf bitmaps_reference 
            mkdir bitmaps_reference

            cp -r bitmaps_test/$report/* bitmaps_reference
            
            cd bitmaps_reference || exit
            
            find . -name "failed_diff*" -type f -delete
            find . -name "report.json" -type f -delete
            '''
        )
    )
    
    stage('Git Push') (
        steps (
            withCredentials([gitUsernamePassword(credentialsId: (아이디), gitToolName: 'git-tool')]) (
                sh '''
                git config --local push.default matching
                git config --local user.name (유저 이름)
                git config --local user.email (유저 이메일)
                git remote set-url origin (깃허브 주소)
                git fetch
                git pull origin develop
                git status
                git status -s --porcelain --untracked-files=no && (
                    git add --all
                    git commit -m "backstop approve"
                    git push origin develop
                ) || echo 'git diff clean'
                '''
            )
        )
    )
)

) 팀에서 계속해서 시각적 회귀 테스트(BackstopJS)를 사용하는 이유 문서화는 사실 유지 보수하기가 쉽지가 않습니다. 하지만, BackstopJS를 활용해서 스토리북 테스트 시나리오를 추가하다 보면 스토리북 자체가 문서화가 될 수 있고, 해당 시나리오들이 자동으로 테스트를 돌면서 스토리북(문서)이 깨지지 않게 유지 보수 할 수 있다는 장점이 있는 것 같습니다. 새로운 기능 개편을 하거나 작업을 하면서 리팩토링을 하게 되어 관련 없는 코드를 같이 수정하게 될 경우, 혹여나 사이드이펙트가 발생하게 될지 불안하게 됩니다. 이러한 것들을 방지해 주는 기능으로 시각적 회귀 테스트를 도입해 보는 게 큰 도움이 되는 것 같아 적용한 경험을 소개 드립니다.

댓글 0

댓글을 작성하려면 로그인이 필요합니다.

댓글을 불러오는 중...