# 시각적 회귀 테스트

## 시각적 회귀 테스트

* 스타일 변화는 다양한 요소에 영향을 받으므로 코드만으로 예측하기는 어렵다.
* 이상적인 방법으로 브라우저별로 모든 페이지를 육안으로 확인하는 것은 어렵기에 불가능
* 이전에 작성한 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="/files/EJLzs5AcP8AZ5Fp7JDxQ" 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;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/testing/undefined-6/undefined-5.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
