> For the complete documentation index, see [llms.txt](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/testing/undefined-6/undefined-5.md).

# 시각적 회귀 테스트

## 시각적 회귀 테스트

* 스타일 변화는 다양한 요소에 영향을 받으므로 코드만으로 예측하기는 어렵다.
* 이상적인 방법으로 브라우저별로 모든 페이지를 육안으로 확인하는 것은 어렵기에 불가능
* 이전에 작성한 CSS 코드를 수정하거나 삭제하다보면 의도치 않은 곳에서 변경이 되기도 함
  * 이같은 부작용을 대처하고자 '문제가 생기면 이전에 작성한 코드는 건드리지 않고 새로 작성한 코드에서 해결한다" 라는 소극적인 방침을 세우는 팀도 존재
  * 이런 소극적인 방침은 리팩터링을 불가능하게 만드는 대증치료에 불과함
* SPA는 작은 UI 컴포넌트를 조합하여 화면을 만듬
  * 마치 레고 블럭을 조립하는 방식과 비슷하며 컴포넌트 기반 개발이라 불린다.&#x20;
  * 로직과 중복되는 CSS 코드를 한곳에서 관리 할 수 있게 된다.
  * 많은 화면이 수많은 공통 컴포넌트를 공유하기 때문에 한 컴포넌트의 수정 여파가 크다는 단점도 있음

{% hint style="info" %}
시각적 회귀 테스트를 스냅샷 테스트로 대체할 수 있나?

* 스냅샷 테스트는 시각적 회귀 테스트의 한 방법이지만 전역에 정의한 스타일의 영향을 알긴 어렵다.
* 마치 단위 테스트에서 알아채지못한 문제를 통합 테스트에서 발견하는 상황과 비슷
* 스냅샷은 단순히 HTML 출력 결과를 비교할 뿐이라 모든 시각적 회귀 테스트를 대체할 수 없다.
  {% endhint %}

### 시각적 회귀 테스트 선택지

* 가장 신뢰도가 높은 방법은 브라우저에 렌더링된 화면을 비교하는 것
* 테스트할 화면을 브라우저에 렌더링하고 특정 시점에 캡처한 이미지들을 픽셀단위로 비교하는 것이 가장 기본 방법
* 시각적 회귀 테스트는 크로미엄같은 브라우저를 헤드리스 모드로 실행한 상태에서 실시함
  * 헤드리스 브라우저는 보통 E2E 테스트 프레임워크에 포함되어있음
  * E2E 프레임워크에선 공식적으로 시각적 회귀 테스트를 지원한다.
  * 페이지 단위로 캡처하여 스타일 변경 전후의 차이점을 발견
  * 단점으로 세세한 부분 (A가 변경되어 다른 영역에 영향을 끼친 경우 전부 변경됬다고 보게됨) 어디가 정확히 변경됬는지 알지 못함
* 세세하게 검출하고 싶다면 UI 컴포넌트 단위로 실시해야함
  * 컴포넌트 단위로 이미지를 캡처하면 영향을 받는 중간 크기의 컴포넌트를 검출할 수 있다.
  * 작은 크기의 컴포넌트와 중간 크기의 컴포넌트를 개별적인 스토리로 등록하면 컴포넌트 탐색기에서 확인할 수 있을 뿐만 아니라 시각적 회귀 테스트에서도 활용 가능

### reg-cli로 이미지 비교

* 시각적 회귀 테스트용 플랫폼으로 스토리북을 사용할 수 있지만 별도 시각적 회귀 테스트 프레임워크를 사용해보자
* `reg-suit` 는 AWS S3 같은 실제 버킷이 없어도 로컬에서 테스트 가능하다.
  * `reg-suit` 의 핵심 기능인 `reg-cli` 를 사용해서 이미지를 비교해보자.

### 스토리북 - 스토리캡 도입

* 스토리북을 활용한 시각적 회귀 테스트 방법
* 스토리캡은 스토리북에 등록된 스토리를 캡처하는 도구
* 스토리캡도 reg-suit를 중심으로 한 reg-viz 생태계에 속하지만, reg-suit 플러그인과 달리 별도로 설치해야한다.
* 스토리 단위로 캡처하기 떄문에 스토리북을 잘 활용할수록 테스트 효율이 높아진다.

```bash
$ npm i storycap --save-dev
```

#### 설정

* 스토리캡 설정을 스토리북 설정 파일에 추가
* 해당 설정들을 추가하면 프로젝트에 있는 모든 스토리 파일이 캡처 대상이되며 시각적 회귀 테스트를 할 수 있게됨

```typescript
// .storybook/preview.js
import { withScreenshot } from 'storycap';
export const decorators = [withScreenshot]

// .storybook/main,js
module.exports = {
  addons: [
    //...
    "storycap"
  ]
}
```

#### 실행

* 스토리를 캡처하기 전 스토리북을 빌드
  * 빌드를 해야 응답 속도가 빨라지므로 사전에 빌드하는 것을 권장

```typescript
// package.json
{
  //...
  "scripts": {
    "storybook:build": "storybook build",
    "storycap": "storycap --serverCmd \"npx http-server storybook-static -a localhost -p
6006\" http://localhost:6006",
  }
}
```

* 빌드가 끝나고 `npm run storycap` 을 실행하면 빌드된 스토리북을 정적 사이트로 실행해 모든 스토리를 캡처한다.
* 캡처가 완료된 이미지는 `__screenshots__` 디렉터리에 저장됨
  * 디렉터리 컨벤션에 맞추려면 `mv __screenshots__ expected`&#x20;
  * **actual**: 기존 이미지를 저장하는 디렉터리
  * **expected**: 비교할 이미지를 저장하는 디렉터리
  * **diff**: 비교 결과를 검출할 이미지를 저장하는 디렉터리
* 스타일을 수정 후 스토리북을 다시 빌드 & 스토리캡을 실행하면 `__screenshots__` 에 변경된 스타일이 반영된 스토리 이미지가 저장됨
  * 해당 디렉터리를 `actual` 로 변경

#### reg-cli로 이미지 간 차이 검출하기

* `expected` 와 `actual` 디렉터리가 만들어졌으므로 `reg-cli` 로 이미지 간 차이점을 검출해보자.
* HTML 리포트를 열어 차이점을 확인

```bash
$ npx reg-cli actual expected diff -R index.html
```

### reg-suit 도입하기

* `reg-cli` 를 사용하여 로컬 환경에서만 시각적 회귀 테스트를 진행했다.
* 시각적 회귀 테스트를 자동화해서 깃허브와 연동해보자.
* 깃허브와 연동하면 저장소에 push 할 때 마다 토픽 브랜치의 시각적 회귀 테스트가 실시되기 때문에 변경된 코드에서 어떤 차이점이 발생하는지 자동으로 리포트 받을 수 있다.

<figure><img src="https://1912740209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3GVYdZvmqQyBVq29ebqk%2Fuploads%2FPjP3rOxgUidMR8QFxRjW%2F%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202024-08-04%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.53.01.png?alt=media&amp;token=8d57dd27-3087-4d7a-ab22-755d51c74c45" alt=""><figcaption></figcaption></figure>

#### reg-suit 도입

* 프로젝트 저장소 최상위 경로로 이동 후 `npx reg-suit init` 실행
  * 개발 환경에 `reg-suit` 을 글로벌 설치했다면 `reg-suit init` 만 실행해도 됨
* 커맨드 실행 후 어떤 플러그인을 설치할지 물어보는데 기본값 세팅 그대로 엔터키 ㄱ&#x20;
  * 해당 플러그인들은 임의의 CI 환경에 `reg-suit` 를 도입시켜주는 편리한 라이브러리임
  * `reg-keygen-git-hash-plugin`, `reg-publish-s3-plugin`은 원격 환경에서 이미지를 비교하기 위한 라이브러리임
* 커밋 해시로 파일명을 지은 스냅샷셋과 검증 결과 리포트를 외부 파일 저장소 서비스(AWS S3)에 전송한다.
* 토픽 브랜치의 기반이 되는 커밋에서 스냅샷셋을 추출하여 기댓값으로 두고, 이후 커밋에서 추출한 이미지와 차이점을 검출한다.
* 검증 결과를 PR에 알려주는 `reg-notify-github-plugin` 을 사용하면 통상적인 워크플로에 시각적 회귀 테스트를 도입할 수 있다.
  * 슬랙 등의 채팅 도구에 알림을 보내는 플러그인도 존재

#### reg-suit 설정파일 생성

```bash
# 기본적인 .reg인 상태로 엔터키를 누름
? Working directory of freg-suit. .reg
# 스토리캡 결과물을 저장할 디렉토리로 __screenshots__ 를 지정한다
? Directory contains actual images. __screenshots__
# 이미지 비교의 강도를 0에서 1 사이로 설정, 일단 기본값인 0인 상태로 엔터
? Threshold, ranges from 0 to 1, Smaller value makes the comparison more senstive. 0
# reg-suit Github App을 저장소에 추가하기 위해 Yes 선택
? notify-github plugin requires a client ID of reg-suit Github app. Open installation window in your browser Yes
# 이서서 수동으로 설치할 예정이므로 입력하지 않고 엔터키 누름
? This repository's Client ID of reg-suit GitHub app
# 버킷은 이후에 작성하므로 No
? Create a new S3 bucket No
# 마찬가지로 이후에 작성하므로 입력하지 않고 엔터
? Existing bucket name
# 설정 파일을 업데이트 하기 위해 YES
? Update configuration file yes
# 예시용 이미지는 불필요하므로 No
? Copy sample images to working dir No
```

* `reg-notify-github-plugin` 질문에 Yes를 선택하면 브라우저 창 열림
  * `reg-suit` 의 깃허브 애플리케이션 설치와 저장소 연동을 위한 것
  * 이렇게 설치하는 것만으로 PR에 검증 결과를 알려주게 됨
  * <mark style="color:purple;">**\[Configure]**</mark> 버튼을 클릭하면 저장소 연동화면으로 이동되며, 테스트를 실시할 저장소를 선택,
  * 연동이 완료되면 선택된 저장소가 화면에 표시됨
  * 연동한 저장소에 있는 <mark style="color:purple;">**\[Get Client ID]**</mark>  버튼을 클릭하면 모달이 열림
    * 해당 Client ID는 환경 변수로 설정할 것이므로 <mark style="color:purple;">**\[Copy to clipboard]**</mark>  버튼ㅇ르 클릭해서 복사

#### Client ID 취득

* 설치가 완료되면 reg-suit 설정 파일인 `regconfig.json` 이 생성됨
* 파일을 열어 `clientId` 에는 `"$REG_NOTIFY_CLIENT_ID"` 를 `bucketName` 에는 `"$AWS_BUCKET_NAME"` 를 입력
* 이값들은 깃허브 액션 실행 시에 환경 변수에서 참조된다.

```typescript
// regconfig.json
{
  "core": {
    "workingDir": ".reg",
    "actualDir": "__screenshots__",
    "thresholdRate": 0,
    "ximgdiff": {
      "invocationType": "client"
    }
  },
  "plugins": {
    "reg-keygen-git-hash-plugin": true,
    "reg-notify-github-plugin": {
      "prComment": true,
      "prCommentBehavior": "default",
      "clientId": "$REG_NOTIFY_CLIENT_ID", // 깃허브의 Screts에 별도로 값을 설정한다.
    },
    "reg-publish-s3-plugin": {
      "bucketName": "$AWS_BUCKET_NAME" // 깃허브의 Screts에 별도로 값을 설정한다.
    }
  }
}
```

#### 실제 운영 환경을 고려한 오차 범위 설정

* <mark style="color:red;">실제 운영 환경에 자동화된 시각적 회귀 테스트는</mark> <mark style="color:red;"></mark><mark style="color:red;">**불안정한 테스트**</mark>(코드나 테스트에 변경 사항이 없는데도 상황에 따라 성공하기도 실패하기도 하는 테스트)로 변질되곤 한다.
* <mark style="color:red;">이는 브라우저에서 여러 요소가 트리 구조로 계층화될 때</mark> <mark style="color:red;"></mark><mark style="color:red;">**안티에일리어싱**</mark><mark style="color:red;">(위신호 제거)이 발생하는 과정에 차이점이 검출</mark>되는것이 원인
* <mark style="color:blue;">**불안정한 테스트가 발견되면 차이점을 검출하는 강도를 낮추는것을 검토해야함**</mark>
  * **thresholdRate:**  차이점이 검출된 곳의 픽셀 수를 전체 대비 비율로 계산
  * **thresholdPixel**: 차이점이 검출된 픽셀수의 절대값
  * 을 조정함녀서 안정적인 운용이 가능한 임계치를 찾아야함

```json
// regconfig.json
{
  "core": {
    "workingDir": ".reg",
    "actualDir": "__screenshots__",
    "thresholdRate": 50, // 차이점으로 검출될 부분을 허용하는 임계치
    "ximgdiff": {
      "invocationType": "client"
    }
  }
}
```

### 외부 스토리지 서비스 설정

* 스냅샷 에셋과 검증 결과 리포트를 저장할 외부 스토리지 서비스를 설정
* `reg-publish-s3-plugin` 을 선택했으므로 AWS S3 버킷을 생성

#### 버킷 생성

* AWS Management Console 에 로그인해서 S3에 새 버킷을 생성
* `reg-suit` 깃허브 애플리케이션이 생성된 버킷에 검증 결과 리포트를 전송, 열람할 수있도록 일부 권한을 설정
* 우선 객체 소유권에서 ACL(접근 제어 목록)access control list을 활성화한다.
* 버킷의 퍼블릭 액세스 차단 설정에서 일부 차단 설정을 해제
  * 새 ACL을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 - 해제
  * 임의의 ACL을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 - 해제

#### IAM 사용자 생성

* 버킷에 접근할 사용자를 IAM에서 생성해보자.
* 원하는 사용자명을 입력 후 <mark style="color:purple;">**\[다음]**</mark> 클릭
* AmazonS3FullAccess 권한을 연결해서 사용자가 S3 버킷에 자유롭게 접근할 수 있도록 설정
* 사용자 생서을 완료 후 액세스 키와 비밀 액세스 키를 취득&#x20;
  * .csv 파일을 저장하거나 다른 곳에 복사
  * 액세스 키들은 절대 저장소에 커밋하지 않도록 주의

### 깃허브 액션에 reg-suit 연동하기

* 깃허브 액션이 완료되면 PR을 생성할 때 마다 자동으로 시각적 회귀 테스트가 실시되며, 테스트가 끝나면 검증 결과를 PR에 알린다.

#### 크리덴셜을 Actions Screts에 등록

* 지금까지 메모한 크리덴셜을 저장소의 Actions Scretes에 설정한다.
  * <mark style="color:purple;">**\[Settings]**</mark>  -> <mark style="color:purple;">**\[Secrets and variables]**</mark>  -> <mark style="color:purple;">**\[Actions]**</mark>  -> <mark style="color:purple;">**\[New repository Secret]**</mark>  버튼을 클릭
  * `AWS_ACCESS_KEY_ID` : IAM 사용자의 액세스 키
  * `AWS_BUCKET_NAME` : S3 버킷 이름
  * `AWS_SECRET_ACCESS_KEY`: IAM 사용자의 비밀 액세스 키
  * `REG_NOTIFY_CLIENT_ID`: `reg-suit`의 Client ID
* AWS\_BUCKET\_NAME, REG\_NOTIFY\_CLIENT\_ID는 최종적으로 `regconfig.json`에 적용된다.
  * 이 값들은 중요한 정보가 아니므로 `json` 에 직접 작성해도 되지만, 저장소별로 설정해야하는 값이므로 편의상 환경 변수에서 불러오도록 세팅

#### 깃허브 액션 설정

* 깃허브 액션 워크플로 작성
* **`fetch-depth: 0`** <mark style="color:red;">**반드시 추가해야 한다.**</mark>
  * 이 값이 없으면 부모 커밋을 취득하지 못해 테스트가 실패하게 됨

```yaml
// .github/workflows/vrt.yaml

name: Run VRT

on: push

env:
  REG_NOTIFY_CLIENT_ID: ${{ secrets.REG_NOTIFY_CLIENT_ID }}
  AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
  
jobs: 
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # 이 값이 없다면 비교를 진행 불가
      - uses: actions/setup-nod@v3
        with:
          node-version: 18
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@master
        with: 
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2
      - name: Install dependencies
        run: npm ci
      - name: Build Storybook
        run: npm run storybook:build
      - name: Run StroyCap
        run: npm run vrt:snapshot
      - name: Run reg-suit
        run: npm run vrt:run     
```

```json
// package.json
{
  "scripts": {
    "storybook:build": "storybook build",
    "vrt:snapshot": "storycap --serverCmd \"npx http-server storybook-static -a localhost 
-p 6006\" http://localhost:6006",
    "vrt:run": "reg-suit run",
  }
}
```

#### 연동 확인

* 깃허브 액션에서 시각적 회귀 테스트가 실행되는지 확인해보자.
* PR 생성 후 깃헙 액션이 완료되면 reg-suit 봇이 코멘트를 남김
  * **뻘건색** 동그라미: 차이점이 검출된 아이템
  * **흰색** 동그라미: 새롭게 추가된 아이템
  * **검은색** 동그라미: 삭제된 아이템
  * **파란색** 동그라미: 차이점이 발견되지 않은 아이템
* 봇이 남긴 코멘트에 있는 \[this report] 링크를 클릭하면 S3에 저장한 검증 결과 리포트를 확인 가능
* 차이점이 없어지거나 리뷰어가 PR을 승인하면 체크 상태가 녹색으로 변경됨

### 시각적 회귀 테스트를 활용한 적극적 리팩토링

* 회귀 테스트는 릴리스 전에 실시한다고 생각하지만, 반복 작업을 줄이고 싶다면 개발 과정에 도입하는 것이 좋음

#### 반응형 디자인에 활용

* 반응형 프로젝트는 효율적으로 시각적 회귀 테스트를 활용할 수 있다.
* 프로젝트 사정상 PC용 디자인을 먼저 완성하고 이후 모바일 용 디자인을 구현해도 시각적 회귀 테스트가 설정됐다면 실수를 줄일 수 있다.
* reg-suit는 CI를 도입하지 않았다고 해도 유용하게 사용할 수 있다.
* 수동으로 편리하게 실시할 수 있을 정도로만 환경을 세팅해놓고 미디어 쿼리로 반응형 대응을 하거나 리팩터링 가능

#### 릴리스 직전의 리팩터링에 활용

* 스타일 관련 전역 코드를 하나씩 삭제하면서 시각적 회귀 테스트를 통해 플젝에 영향을 미치는 영향을 확인 가능
* 실제로 필요한 CSS 코드만 남기는 리팩터링은 시각적 회귀 테스트가 없다면 불가능

#### 스토리 커밋 습관화로 시작하는 시각적 회귀 테스트&#x20;

* 스토리를 만들면 컴포넌트 단위로 시각적 회귀 테스트를 실시하는 것은 쉽다.
* 등록된 스토리의 숫자만큼 세밀한 검증이 가능하기 때문에 습관적으로 스토리를 커밋할것을 권장
* 릴리스 직전에 스토리를 커밋하면 반응형 디자인이 필요할 때 여유롭게 대응하는것이 힘들어 도입 자체가 좌절되기 쉽다.
* <mark style="color:blue;">**스토리가 정말로 필요하지 않다고 느껴지지 않는 한, 처음부터 커밋**</mark>

### &#xD;
