13. 프런트엔드 테스트
13.1 좋은 테스트란 무엇인가?
테스트의 궁극적인 목적은 애플리케이션과 코드의 품질 향상이다.
테스트를 위한 테스트는 없는 게 낫다.
좋은 테스트는
- 단순하고 이해하기 쉽다: 테스트 코드만 보고 테스트하고자 하는 것과 그 결과를 알 수 있다.
- 유지보수가 가능하다: 서비스 코드의 유지보수에 맞춰 유지보수 가능할 수 있는 범위로 작성한다.
- 내부 구현보다는 인터페이스를 기준으로 테스트한다: 이 코드를 사용하는 사용자의 입장에서 테스트하는 것이 좋다
- 독립적으로 실행된다: 테스트의 실행순서가 실행결과에 영향을 끼치지 않는다.
- 일관성이 있다.
13.2 프런트엔드 테스트
프런트엔드의 경우 자바스크립트 코드뿐만 아니라, 이벤트핸들러의 동작, 화면의 출력이 잘 되는 지 등도 테스트해야 한다.
모든 경우에 대해 테스트하기보다는 효율적인 테스트를 위해 어떤 테스트가 적합하지 판단하여 적용하는 것이 좋다.
필수적으로 검증해야 하는 것들에 대해서 단위 테스트, 통합 테스트, E2E 테스트, 그리고 시각적 회귀 테스트 중에 어느 것이 맞을 지 생각해보고 테스트를 작성하는 것이 좋을 것 같다.
13.2.1 테스트의 종류
- 단위 테스트: 하나의 모듈이 독립적으로 잘 실행되는 지 테스트하는 것. 모든 것에 대한 단위테스트를 작성하기 보다는 핵심 코드(public한 인터페이스나 계산식 검증 등)에 대해서만 작성하는 것이 좋다.
- 통합 테스트: 여러 개의 모듈이 합쳐졌을 때 올바르게 동작하는 지 테스트하는 것. 최대한 목킹을 안 하는 것이 좋다. 목적은 어떤 행위에 의해 컴포넌트의 상태가 올바르게 변경되었는 지 확인하기 위함
- E2E 테스트: 실제로 애플리케이션을 실행해서 테스트하는 것. 사용자가 실제로 사용하는 시나리오대로 테스트를 작성하는 것이 좋다
13.2.3 테스트 피라미드
테스트 피라미드는 위로 갈 수록 느리고 비용이 많이 드는 테스트임을 나타낸다.
그렇다고 비용이 적게 드는 단위테스트만 작성하는 것은 옳지 않다.
검증하고자 하는 것의 속성에 따라 단위테스트, 통합테스트, E2E 테스트 중 맞는 것을 선택해야 한다.
13.2.4 jest
13.2.4.3 Setup과 Teardown
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('sample1', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('sample2', () => console.log('2 - test'));
});
🔽 실행 결과
1 - beforeAll
1 - beforeEach
1 - test
1 - afterEach
2 - beforeAll
1 - beforeEach
2 - beforeEach
2 - test
2 - afterEach
1 - afterEach
2 - afterAll
1 - afterAll
13.2.4.4 테스트 더블(Test Double)
테스트에서 실제 구현을 대체하는 개념들
테스트 더블의 장점
- 테스트 코드와 외부 의존성 모듈을 분리할 수 있다. 또한 테스트 더블을 사용하여 외부 의존성을 잘 추상화하였다면 실제 외부 의존성 모듈을 이용하여 검증할 때도 쉽게 대체할 수 있다.
- API 또는 인터페이스에 접근하는 것이 아니라 별도의 테스트 더블 모듈 또는 객체에서 데이터를 가져오기 때문에 테스트 속도가 개선된다.
- 다양한 데이터나 예외 처리에 대한 상황을 쉽게 재현하여 검증할 수 있다.
테스트 더블의 종류
- 더미(Dummy): 껍데기만 만든 객체
- 페이크(Fake): 결과값을 고정한 객체
- 스텁(Stub): 실제로 동작하는 대로 결과를 반환하도록 최소한만 구현한 것.
- 스파이(Spy): 실제 객체를 추적하여 어떤 매개변수로 몇 번 호출되는 지 확인할 수 있는 것
- Mock: 실제 객체와 동일한 동작을 하도록 만들어진 모의 객체
13.3 메모 애플리케이션
메모 애플리케이션의 테스트를 작성하며 프런트엔드 테스트에 대해 이해한다. (소스코드 링크: https://github.com/bjpublic/Front-end/tree/master/chapter13-test)
13.3.1 단위 테스트
컴포넌트의 핵심 기능이 무엇이고, 이것을 어떻게 테스트하면 좋을 지 생각한다.
Button 컴포넌트는 public한 인터페이스를 테스트했다.
MemoInputArea 컴포넌트는 핵심 기능인 addMemoItem() 함수에 대해 테스트했다.
@testing-library/dom에서 제공하는 screen.getByText(), screen.getByLabelText() 함수를 사용하여 엘리먼트를 조회할 수 있다. 사용자 관점에서 볼 수 있는 텍스트나 라벨 등으로 엘리먼트를 조회하기 때문에 테스트의 신뢰성을 높일 수 있다.
13.3.2 통합 테스트
여러 컴포넌트가 모였을 때 이것의 핵심 기능이 무엇이고, 이것을 어떻게 테스트하면 좋을 지 생각한다.
MemoList 컴포넌트는 핵심기능인 메모 추가와 메모 삭제에 대해 테스트했다. 메모 추가는 외부 컴포넌트를 클릭해야 발생해서 MemoList의 addMemoItem() 함수로 메모 추가를 트리거했다. 메모 삭제는 MemoList 컴포넌트 내부의 버튼 엘리먼트를 클릭해서 트리거할 수 있기 때문에 버튼 엘리먼트의 클릭 이벤트를 발생시켜 테스트했다.
13.3.3 Cypress
단위, 통합 및 E2E 테스트를 쉽게 작성하는 것을 목적으로 등장한 테스트 도구. 크로스 브라우징 테스트도 가능하다.
13.3.4 E2E 테스트
애플리케이션 전체의 워크 플로우를 검증한다.
여기서는 일반적인 사용자, 메모를 입력하지 않은 사용자, 메모 입력 조건을 지키지 않은 사용자의 3가지 시나리오를 검증했다.
예제에서 테스트하려는 동작과 검증하려는 내용을 추상화하여 표현했다는 점이 신선했다.
출처
- 이재성, 한정. 『기초부터 완성까지, 프런트엔드』. 서울: 비제이퍼블릭, 2021
- Martin Fowler, "TestPyramid", martinfowler.com, 2012-05-01, https://martinfowler.com/bliki/TestPyramid.html
'프론트엔드' 카테고리의 다른 글
CRA에 Lint와 pre-commit hook 설정하기 (0) | 2022.09.13 |
---|---|
『기초부터 완성까지, 프런트엔드』 15. 성능 (1) | 2022.02.02 |
『기초부터 완성까지, 프런트엔드』 13장. 14장 테스트 코드 예제 리뷰 (0) | 2022.02.02 |
『기초부터 완성까지, 프런트엔드』 14. 스냅숏 테스트와 시각적 테스트 (0) | 2022.01.19 |
『기초부터 완성까지, 프런트엔드』 8. 브라우저 렌더링 과정 (0) | 2022.01.18 |