이 글에선
처음 접해보는 리액트 네이티브에서는 어떻게 테스팅을 진행해야할까 고민했고, 참여하고 있는 번역 활동에서 리액트 네이티브 테스팅에 관한 공식 문서를 번역하기도 했습니다. 이에 기초하여 리액트 네이티브에서 E2E테스트와 유닛테스트를 하는 방법을 소개하고자 합니다. 자동화 테스트 라이브러리 비교부터 Sentry, Maestro, Appium 중 Maestro을 선택한 이유, 실제 예제 코드 그리고 유닛 테스트 작성까지의 과정을 다룹니다.
자동화 테스트 라이브러리 비교
리액트 네이티브 앱의 E2E 테스트를 위해 여러 라이브러리를 고려해볼 수 있습니다. 그 중 저는 Sentry, Appium, Maestro를 표로 비교해보겠습니다.
Sentry |
Appium |
Maestro |
|
주 용도 | 애플리케이션 모니터링 및 오류 추적 | 모바일 애플리케이션의 UI 테스트 자동화 | 모바일 앱의 UI 테스트 |
기능 | - 오류 보고, 성능 모니터링 - 릴리스 및 배포 추적 |
- 다양한 언어로 스크립트 작성 가능 | - 간편한 설정, 빠른 테스트 실행 |
장점 | - 실시간 오류 추적: 애플리케이션에서 발생하는 오류를 실시간으로 감지하고 보고함 - 상세한 오류 보고서 제공: 스택 트레이스, 오류 발생 위치 등의 상세 정보를 제공함 - 성능 모니터링: 애플리케이션의 성능 문제를 추적할 수 있음 |
- 실제 기기 및 에뮬레이터/시뮬레이터에서 테스트 가능: 다양한 환경에서 테스트를 실행할 수 있음 - 다양한 언어 지원: Java, Python, JavaScript 등 다양한 언어로 테스트 스크립트를 작성할 수 있음 |
- 간단한 설정 및 사용 용이: 복잡한 설정 없이 쉽게 사용할 수 있음 - 빠른 테스트 실행 속도 |
단점 | - 테스트 자동화 기능은 없음: Sentry는 주로 오류 추적에 초점을 맞추고 있으며, 자동화된 UI 테스트 기능은 제공하지 않음 - 제한된 무료 기능 |
- 초기 설정 및 초기 구성이 복잡함 - 테스트 실행 속도가 느릴 수 있음: 특히 복잡한 테스트 시나리오에서는 실행 속도가 느릴 수 있음 |
- 제한적 기능: Appium에 비해 기능이 제한적임 - 고급 기능 및 복잡한 테스트 시나리오 지원 부족 |
Maestro를 선택한 이유
저는 이 중 Maestro가 제 프로젝트에 적합하다 생각하여 마에스트로를 선택하게 되었습니다. Maestro는 설정이 간편하고 사용하기 쉬워서 빠르게 UI 테스트를 작성하고 실행할 수 있기 때문입니다. YAML 파일 형식을 사용하여 테스트 시나리오를 정의하기 때문에, 읽기 쉽고 유지보수가 용이합니다. 제 프로젝트 중 MyOrdersScreen 컴포넌트를 테스트하는 방법을 예시로 보여드리겠습니다.
Maestro 예제 코드
Maestro 설정 및 설치
먼저, Maestro CLI를 설치합니다.
yarn add -g maestro-cli
테스트 스크립트 작성
그리고 프로젝트 루트에 maestro 디렉토리를 만들고 myOrdersScreenTest.yaml 파일을 추가합니다.
myOrdersScreenTest.yaml
appId: com.example.myapp # 애플리케이션의 패키지 이름
onFlowStart:
- startRecording: moa-testing-recording # 테스트 과정 녹화 설정
onFlowComplete:
- stopRecording
flows:
- auth/*
- orders/*
- store/*
# 위 flow 안의 yaml 파일 테스트 내용
tests:
- name: MyOrdersScreen Tests
steps:
- launchApp: # 앱 실행
clearState: true
- waitForElement:
id: flat-list
- assertVisible:
id: flat-list
- assertVisible:
text: "배송 대기" # 첫 번째 주문 아이템 텍스트
- assertVisible:
text: "배송중" # 두 번째 주문 아이템 텍스트
- swipe:
direction: up
- waitForElement:
text: "배송 완료" # 스와이프 후 새로운 주문 아이템 텍스트
- assertVisible:
text: "배송 완료"
- pullToRefresh:
id: refresh-control
- waitForElement:
text: "배송 대기" # 리프레시 후 새로운 주문 아이템 텍스트
- assertVisible:
text: "배송 대기"
- appId: 테스트할 애플리케이션의 패키지 이름입니다.
- onFlowStart: 테스트 시작 시의 세부 기능을 정의합니다.
- onFlowCompleted: 테스트 종료 시의 세부 기능을 정의합니다.
- launchApp: 앱을 실행하고, 이전 상태를 초기화합니다.
- waitForElement: 특정 요소가 나타날 때까지 기다립니다.
- assertVisible: 특정 요소가 화면에 보이는지 확인합니다.
- swipe: 화면을 위로 스와이프하여 더 많은 아이템을 로드합니다.
- pullToRefresh: 새로고침 동작을 수행합니다.
테스트 실행
Maestro CLI를 사용하여 테스트를 실행합니다.
maestro test maestro/myOrdersScreenTest.yaml
이 외에도 Maestro Studio 기능을 사용하여 직접 script를 작성하지 않고도 UI 콘솔을 활용해 테스트를 작성할 수도 있습니다. 스튜디오의 자세한 방법과 기능은 공식 홈페이지를 참고하실 수 있습니다.
유닛 테스트
사실 프론트엔드에서 유닛 테스트까지 작성하기는 시간 비용적 측면에서 망설여질 수 있습니다. 하지만 유닛 테스트는 모든 테스트의 기초이기도 하고, 개별 컴포넌트와 함수를 테스트하여 코드의 신뢰성과 어플리케이션에 대한 자신감을 높일 수 있는 중요한 과정이기에 이번 프로젝트에서 작성하게 되었습니다. React Native의 유닛 테스트는 React와 마찬가지로 Jest와 React Testing Library를 사용하여 설정할 수 있습니다.
유닛 테스트 설정
필요한 패키지를 설치합니다. 기본으로 설치되어 있는 경우, 이 과정을 생략합니다.
yarn add --save-dev jest @testing-library/react-native @testing-library/jest-native
package.json에 Jest 설정을 추가합니다.
{
"name": "MoA",
"version": "1.0.0",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest" # 이 부분
},
"jest": {
"preset": "react-native",
"setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect"
]
}
#, ...
}
유닛 테스트 작성
__tests__/orders/MyOrdersScreen.test.js 파일을 생성하고 다음과 같이 작성합니다.
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import MyOrdersScreen from '../path/to/MyOrdersScreen';
import useOrder from '../../../hooks/order/useOrder';
jest.mock('../../../hooks/order/useOrder');
describe('MyOrdersScreen', () => {
it('컴포넌트를 렌더링한다', () => {
useOrder.mockReturnValue({
orderInfiniteQuery: {
pages: [
{ content: [{ orderId: 'moa-20240301123', item: '배송 대기' }, { orderId: 'moa-20240228332', item: '배송중' }] }
]
},
orderFetchNextPageQuery: jest.fn(),
refetchOrderInfiniteQuery: jest.fn()
});
const { getByText, getByTestId } = render(<MyOrdersScreen />);
expect(getByTestId('flat-list')).toBeTruthy();
expect(getByText('배송 대기')).toBeTruthy();
expect(getByText('배송중')).toBeTruthy();
});
it('화면을 당겨 refresh가 될 경우, refetchOrderInfiniteQuery가 호출된다.', () => {
const refetchOrderInfiniteQueryMock = jest.fn();
useOrder.mockReturnValue({
orderInfiniteQuery: {
pages: [
{ content: [{ orderId: 'moa-20240301123', item: '배송 대기' }, { orderId: 'moa-20240228332', item: '배송중' }] }
]
},
orderFetchNextPageQuery: jest.fn(),
refetchOrderInfiniteQuery: refetchOrderInfiniteQueryMock
});
const { getByTestId } = render(<MyOrdersScreen />);
const refreshControl = getByTestId('refresh-control');
fireEvent(refreshControl, 'refresh');
expect(refetchOrderInfiniteQueryMock).toHaveBeenCalled();
});
});
유닛 테스트 실행
다음 명령어를 실행하여 유닛 테스트를 실행합니다.
yarn test
결론
이처럼 Maestro를 사용하면 간편한 설정과 빠른 테스트 실행을 통해 리액트 네이티브 앱의 UI 테스트를 쉽게 자동화할 수 있습니다. 아마 앱의 기능이 복잡하거나 더 상세한 테스트가 필요했다면 Maetro보다는 Appium이 더 나은 테스팅 경험을 제공했을지 모르겠습니다. 그러나 MoA 프로젝트의 경우, MVP 기준 Maestro만으로도 충분히 만족스러운 테스팅 경험을 할 수 있었습니다. 특히, 간단한 yaml 스크립트 작성과 이마저도 studio 콘솔 기능을 통해 스크립트를 작성할 수 있다는 점이 가장 만족스러웠습니다.
또한, 이번 프로젝트에선 Jest와 React Testing Library를 사용하여 컴포넌트와 훅을 독립적으로 유닛 테스트해 볼 수 있었는데, 시간적인 여유가 없어 모든 파일에 대한 테스트를 작성하기가 많이 힘들었습니다. 그래서 나중에 테스트 코드를 작성하는 것이 아닌, 기능을 구현해가며 항상 테스트도 함께 작성해야겠다 반성했습니다!
참고문헌:
https://maestro.mobile.dev/
Medium: Pokedex UI Testing Series
'React > ReactNative' 카테고리의 다른 글
React Native에서 딥링크 처리 및 푸시 알림 최적화하기 (0) | 2024.05.28 |
---|---|
[ReactNative] react-query refetch가 안되는 문제와 해결 방법 (2) | 2024.03.06 |
[ReactNative] ios .env파일 수정 후 적용이 안되는 현상 (0) | 2023.12.15 |
React Native에서 웹뷰(react-native-webview)를 사용할 때, 고려해야할 것들(#삽질, #Next.js) (0) | 2023.12.14 |
[React Native] WebView 적용하는 방법(localhost 안될때,nsurlerrordomain) (0) | 2023.05.19 |