E2E 테스트에선 브라우저를 사용할 수 있기 때문에 실제 애플리케이션에 가까운 테스트가 가능하다
브라우저 고유의 API를 사용하는 상황 또는 화면을 이동하며 테스트해야하는 상황에 잘맞음
E2E 테스트 프레임워크로 테스트할 때 다음 상황에 구분하지 않고 E2E 테스트라 한다.
브라우저 고유 기능과 연동된 UI 테스트
데이터베이스 및 하위 시스템과 연동된 E2E 테스트
E2E 테스트는 무엇을 테스트할지 목적을 명확히 세우는 것이 가장 중요함
E2E 테스트에서 DB 서버 또는 외부 저장소 서비스를 포함한 전체 구조에서 얼마나 실제와 유사한 상황을 재현할 것인지가 중요한 기준점이 됨
어떤 관점에서 어떤 선택을 내려야 할지 상황별로 파악해보자.
브라우저 고유 기능과 연동한 UI 테스트 (피처 테스트)
웹 애플리케이션은 브라우저 고유 기능을 사용함
특정 상황에선jsdom 에서 제데로된 테스트를 할 수 없다.
화면 간 이동
화면 크기를 측정해서 실행된는 로직
CSS 미디어 쿼리를 사용한 반응형 처리
스크롤 위치에 따른 이벤트 발생
쿠키나 로컬 스토리지 등에 데이터 저장
Jest에서 목 객체를 만들어 테스트를 작성할 수도 있지만 상황에 따라 브라우저로 실제 상황과 유사하게 테스트하고 싶다면 UI테스트를 하자.
UI 테스트는 브라우저 고유 기능으로 인터렉션할 수 있으면 충분
API 서버나 다른 하위 시스템은 목 서버로 만들어 E2E 테스트 프레임워크에서 연동된 기능을 검증하면 됨
이것을 피처 테스트라고도 부름
데이터베이스 및 서브 시스템과 연동한 E2E 테스트
일반적으로 웹 애플리케이션은 DB 서버나 외부 시스템과 연동하여 다음과 같은 기능을 제공한다.
DB 서버와 연동하여 데이터를 불러오거나 저장
외부 저장소 서비스와 연동하여 이미지 등을 업로드
레디스와 연동하여 세션 관리
이를 최대한 실제와 유사하게 재현해 검증하는 테스트를 E2E 테스트라 한다.
E2E 테스트 프레임워크는 UI 자동화 기능으로 실제 애플리케이션을 브라우저 너머에서 조작한다.
표현 계층, 응용 계층, 영속 계층을 연동하여 검증하므로 실제 상황과 유사성이 높은 테스트로 자리매김함
많은 시스템과 연동하기 때문에 실행 시간이 길고, 불안정하다 (연동한 시스템이 문제가 있으면 실패)
Doker Compose
도커 컴포즈로 여러 도커 컨테이너를 실행하며, 컨테이너 간 통신으로 시스템을 연동해 테스트
UI를 조작하면 영속 계층에 의도한 내용이 저장되고, 그 내용이 화면에 반영되는지를 검증
E2E 테스트에 도커 컴포즈를 도입하면 테스트 환경을 실행하고 종료하는 것이 편리해짐
CI에서 하나의 작업으로 실행할 수 있기 때문에 개발 워크플로에 포함시켜 쉽게 자동화 가능
Playwright
마이크로소프트가 공개한 E2E 프레임워크
크로스 브라우징 지원
다양한 기능 제공
디버깅 테스트
리포터
트레이스 뷰어
테스트 코드 생성기
설치 및 설정
$ npm init playwright@latest
# 타입스크립트와 자바스크립트 중 타입스크립트를 선택
? Do you want to use Typescript or Javascript? - Typescript
# 테스트 파일을 저장할 폴더를 설정. e2e라는 이름의 폴더를 사용
? Where to put your end-to-end tests? - e2e
# 깃허브 액션의 워크플로를 추가할지 말지 선택. - NO
? Add a GitHub Actions workflow? (y/N) - false
# 플레이라이트 브라우저를 설치
? Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) - true
설치가 완료되면 package.json에 필요한 모듈이 추가되고, 설정 파일 양식과 샘플 테스트 코드가 생성됨
처음 시작하는 E2E Test
브라우저 자동화 테스트는 테스트마다 브라우저를 열어 지정된 URL로 접속하는것으로 시작됨
page.goto 에 URL 지정
수동으로 브라우저를 조작하면서 애플리케이션 기능을 검증하는 것을 코드로 대체해 테스트를 자동화 가능
import { test, expect } from '@playwright/test';
test("has title", async ({ page }) => {
await page.goto("https://playwright.dev/");
// 페이지 제목에 "Playwright"가 포함됐는지 검증한다.
await expect(page).toHaveTitle(/Playwright/);
});
test("get started link", async ({ page }) => {
await page.goto("https://playwright.dev/");
// "Get started"라는 접근 가능한 이름을 가진 링크를 취득하고, 링크를 클릭한다.
await page.getByRole("link", { name: "Get started" }).click();
// 페이지 URL에 "intro"가 포함됐는지 검증한다.
await expect(page).toHaveURL(/.*intro/);
});
로케이터
플레이라이트의 핵심 API
현재 페이지에서 특정 요소를 가져온다.
1.27.0 버전에는 테스팅 라이브러리로부터 영향을 받은 접근성 기반 로케이터가 추가됨
플레이라이트도 접근성 기반 로케이터를 우선적으로 사용하는 것을 권장한다
테스팅 라이브러리와 다른 점은 대기 시간이 필요한지에 따라 findByRole 등을 구분해서 사용하지 않아도 된다.
인터렉션은 비동기 함수이기 때문에 await 로 인터렉션이 완료될 때까지 기다린 후 다음 인터렉션이 실행하는 방식으로 작동됨
import { expect } from '@playwright/test';
test("Locator를 사용한 단언문 작성법", async ({ page }) => {
// 특정 문자열을 가진 요소를 취득해서 화면에 보이는 상태인지 검증
await expect(page.getByText("Welcome John!")).toBeVisible();
// 체크박스를 취득해 체크됐는지 검증
await expect(page.getByRole("checkbox")).toBeChecked();
// not으로 진릿값 반전
await expect(page.getByRole("heading")).not.toContainText("some text");
})
import { expect } from '@playwright/test';
test("페이지를 사용한 단언문 작성법", async ({ page }) => {
// 페이지 URL에 "intro"가 포함됐는지 검증
await expect(page).toHaveURL(/.*intro/);
// 페이지 제목에 "Playwright"가 포함됐는지 검증
await expect(page).toHaveTitle(/Playwright/);
})
테스트할 애플리케이션 개요
$ npm i
$ brew install mino/stable/mc // MinIO Client 설치
$ docker compose up -d // 도커 컴포즈로 여러 컨테이너 실행하기
$ sh create-image-bucket.sh // 도커 컴포즈로 실행한 MinIO 서버에 버킷을 생성
$ npm run prisma:migrate // DB를 마이그레이션해서 테스트용 초기 데이터 주입
$ npm run dev // next.js 개발서버 실행 (docker compose up -d를 실행 한후 해야함)
Next.js
모든 페이지를 서버 사이드 렌더링으로 렌더링하며 인증된 요청인지 검사한다.
만약 로그인하지 않은 상황이면 로그인 화면으로 리다이렉트 시킨다.
Next.js는 레디스 서버와 연동하여 세션으로부터 사용자 정보를 취득한다.
Prisma
관게형 데이터베이스는 PostgreSQL 사용
Next.js 서버는 객체 관계 매핑(object-relational mapping, ORM) 도구로 프리즈마를 사용
프리즈마는 타입스크립트 호환성이 좋아서 인기가 많다.
ex: 이너 조인한 테이블의 응답을 타입 추론으로 획득 가능
S3 Client
외부 파일 저장소 서비스로 AWS S3를 사용
로컬 환경에선 실제 버킷을 사용하지 않고 AWS S3 API와 호환이 가능한 MinIO를 사용
로컬 환경에서 개발 및 테스트할 때 사용
기사의 메인 및 프로필 이미지를 저장하는 공간으로 활용됨
개발 환경에서 E2E 테스트 설정
개발환경에서 테스트 실행하려면 빌드된 Next.js를 실행해야함
$ npm run build && npm start
E2E 테스트를 실행하기 전 DB의 테스트 데이터를 초기화하자. (이후 테스트의 영향을 미치기 때문)
$ npm run prisma:reset
E2E 테스트 실행
초기 설정을 사용하면 헤드리스 모드로 E2E 테스트를 실행하기 때문에 브라우저는 나타나지 않음
$ npx playwright test
$ npx playwright test Login.spec.ts // 특정 테스트 파일만 테스트 실행
테스트 결과를 리포트로 생성하고 싶다면 npx playwright show-report 를 실행
생성된 리포트는 http://localhost:9223/ 에서 확인 가능
플레이라이트 검사 도구를 활용한 디버깅
E2E 테스트를 작성하다보면 기대한것과 다르게 테스트가 통과되지 않을 떄가 있음
이럴 땐 플레이라이트 검사 도구로 원인을 파악해야 한다.
테스트를 실행하는 커맨드에 --debug 옵션을 붙이면 headed 모드로 테스트가 시작됨
브라우저를 열어서 육안으로 자동화된 UI 테스트를 확인할 수 있는 모드
검사 도구를 사용하면 실행 중인 테스트 코드를 보면서 UI가 어떻게 조작되는지 확인 가능
좌측 상단에 녹색 삼각형 재생 아이콘을 클릭하면 UI 테스트 시작됨
재생 아이콘 우측에 녹색 화살표 형태의 스텝 오버 아이콘을 클릭하면 한 줄 씩 테스트 코드가 실행됨
$ npx playwright test Login.spec.ts --debug
도커 컴포즈를 사용하는 E2E 테스트
다른 테스트와 다르게 초기에 컨테이너를 빌드하는 시간이 필요
CI용으로 작성한 것
$ npm run docker:e2e:build && npm run docker:e2e:ci
test("다른 사람의 기사에는 Like 할 수 있다.", async ({ page }) => {
await page.goto(url("/login"));
await login({ page, userName: "Jpub" });
await expect(page).toHaveURL(url("/"));
// 여기서부터 기사 목록 페이지
await page.goto(url("/posts/10"));
const buttonLike = page.getByRole("button", { name: "Like" });
const buttonText = page.getByTestId("likeStatus");
// like 버튼이 활성화되고, 현재 Like 수는 0
await expect(buttonLike).toBeEnabled();
await expect(buttonLike).toHaveText("0");
await expect(buttonText).toHaveText("Like");
await buttonLike.click();
// like를 클릭하면 카운트가 1증가하고 이미 like 누른 상태가 된다.
await expect(buttonLike).toHaveText("1");
await expect(buttonText).toHaveText("Liked");
})
본인이 작성한 기사에는 like 버튼을 누를 수 없다.
test("본인이 작성한 기사에는 Like 할 수 없다.", async ({ page }) => {
await page.goto(url("/login"));
await login({ page, userName: "Jpub" });
await expect(page).toHaveURL(url("/"));
// 여기서부터 기사 목록 페이지
await page.goto(url("/posts/90"));
const buttonLike = page.getByRole("button", { name: "Like" });
const buttonText = page.getByTestId("likeStatus");
// like 버튼이 비활성화되고, Like 문자도 사라짐
await expect(buttonLike).toBeDisabled();
await expect(buttonText).not.toHavetext("Like");
})
신규 작성 페이지 E2E 테스트
이른바 CRUD라 불리는 기능의 페이지 테스트
CRUD 기능 테스트는 다른 테스트에 영향을 미치지 않는지 세심히 살펴봐야 한다.
기본적으로 Publish 기능 테스트는 새로운 기사를 작성한 후 해당 기사를 대상으로 CRUD해야 한다.