Detox - React-Native E2E test framework

heyj·2024년 12월 15일
0

React-Native

목록 보기
1/1

들어가며

사이드 프로젝트로 전 세계 유저를 대상으로 하는 습관형성앱을 운영중입니다. 구글 플레이 스토어와 앱스토어만 살펴봐도 습관형성을 위한 앱이 정말 많은데, 저희가 운영중인 앱은 습관에 대한 인증은 사진으로 하고 서포트와 댓글 기능으로 다른 유저들과 상호작용을 할 수 있는 소셜 기능이 들어간 것이 특징입니다.

최근 몇 개월 사이에 카메라 라이브러리 이슈로 몇몇 기기가 카메라 화면에 접근을 하지 못했던 이슈, 특정 페이지에 가면 앱이 크래쉬 나는 현상 등 문제가 있었습니다. 개발자들끼리 이전부터 단위테스트, 통합테스트, E2E테스트 등을 도입하자는 의견이 있었는데, 아무래도 사이드 프로젝트이고 기능 개발이 우선되다 보니 계속 미뤄지게 되었어요.

그러다가 최근 서버 장애로 한동안 로그인이 안되는 문제가 있었는데 한참 뒤에서야 발견을 해 버그를 수정했던 일이 있었습니다. 이 일이 계기가 되어 sentry적용, 단위테스트 작성, e2e 테스트 추가 등을 고려하게 되었습니다.

Detox framework

E2E(End-to-End) 테스트는 애플리케이션의 전체 흐름을 실제 사용자 관점에서 테스트 하는 방법으로, 페이지에서 텍스트가 제대로 출력이 되었는지, 버튼을 클릭 했을 때 올바른 동작으르 수행하는 지 등을 테스트 합니다.

앞서 언급했던 이유로 React-Native E2E test framework를 찾던 중 Detox를 알게 되었어요. Detox는 React-Native앱을 위한 Gray Box E2E(End-to-End) 테스트 프레임워크로 모바일 앱 테스팅에 최적화되어 있습니다.

Gray Box test란?
Black Box 테스트는 앱 내부 코드에 접근하거나 이해할 필요가 없습니다. 반면 White Box 테스트는 내부 코드, 구조, 알고리즘에 대해 파악이 필수 입니다. Gray Box test는 이 두 가지가 결합된 형태로, 테스터가 내부 세부사항과 외부 세부사항에 대해 부분적인 지식을 가지고 수행하는 테스트 방식입니다. 이 테스트 방법은 시스템 통합 테스트, 애플리케이션 테스트, 보안 테스트, 비즈니스 도메인 테스트에서 유용하게 활용됩니다.

  • 기능 테스트와 비기능 테스트를 모두 활용하여 제품의 품질을 향상시키는 것이 목적
  • 애플리케이션의 내부 상태, 아키텍처, 동작을 평가
  • 애플리케이션의 외부 동작 방식(비기능적)을 점검, 테스터가 앱과 버그를 효율적으로 수정하는 방법에 대해 완벽한 지식을 얻을 수 있음

Detox는 webdriver나 test tool에 의존하지 않고, 안드로이드의 에스프레소나 iOS의 얼그레이와 같이 모바일 기기의 네이티브 층과 직접 소통하는데 이로써 실제 유저가 어떠한 딜레이도 없이 앱을 사용하는 것처럼 테스트 할 수 있게 됩니다.

자바스크립트로 만들어진 Detox는 앱이 예상대로 동작하고 높은 품질을 유지하는지 확인하는 데 도움이 되는 도구와 특별한 API를 제공합니다.

Detox를 이용하면 다양한 유저 시나리오를 작성해 자동화 테스트를 진행할 수 있습니다.

1. Detox Architecture

Detox 테스트가 실행될 때, 실제로는 mobile app과 tester이 각각 나란히 실행됩니다.

모바일앱은 일반적으로 시뮬레이터/에뮬레이터에 실행이 되는데, 우리가 xcode나 안드로이드 스튜디오를 이용할 때 처럼 일반적인 네이티브 빌드가 수행되고 앱이 기기에 설치되고 실행됩니다. 앱은 보통 테스트가 시작하기 전 한 번 빌드가 됩니다.

tester는 Jest와 같은 테스트 러너를 사용해 node.js에서 실행이 됩니다. 테스트는 본질적으로 비동기적이기 때문에 테스트는 async-await가 많이 사용될 수 밖에 없습니다.

이 두 파트는 각각 별도의 프로세스로 실행되고, 다른 기기에서 실행하는 것도 가능하다고 하네요. 이 둘은 웹소켓을 통해 통신을 하게 됩니다.

실제로 통신을 더 안정적으로 만들기 위해 이 두 파트는 모두 클라이언트로 구현되고 프록시 역할을 하는 Detox서버가 중간에서 통신을 합니다. 이런 구조는 만약 시뮬레이터 부팅이나 앱 재시작 등이 일어나도 다른 쪽의 연결이 끊어지거나 상태를 잃지 않는 등이 가능하도록 합니다. 일반적으로 tester는 무작위로 생성된 세션ID와 사용 가능한 포트에서 서버를 시작하고, 이 세션과 포트를 실행 인자로 클라이언트 앱에 전달합니다.

2. Key Feature of Detox

Detox의 핵심 기능들은 다음과 같습니다.

2-1. 동기식 테스팅

다른 테스팅 프레임워크들이 비동기식일 수 있는 것과 달리 Detox는 테스트를 동기식으로 실행하여 각 단계(예: 버튼 탭하기, 데이터 로딩 등)가 다음 단계를 시작하기 전에 완전히 완료되도록 보장합니다.

2-2. 정밀한 동기화

Detox는 다음 동작으로 진행하기 전에 애니메이션, 네트워크 요청 및 기타 비동기 이벤트가 완료될 때까지 자동으로 기다립니다. 이는 불안정한 테스트를 방지합니다.

2-3. 기기 시뮬레이션

네트워크 연결성이 좋지 않거나, 다양한 스크린 사이즈 등 실제 기기 조건을 시뮬레이션 할 수 있어, 앱이 다양한 조건에서도 잘 동작하는지 확인할 수 있습니다.

2-4. Mocking과 Stubbing

Detox는 API호출을 모킹하고, 다양한 상태를 시뮬레이션하며, 응답을 스터빙할 수 있게 도와주어 앱의 기능을 외부 서비스로부터 격리시켜 테스트 할 수 있게 해줍니다.

2-5. 스냅샷 테스팅

스크린샷을 찍고 기준 이미지와 비교해 UI 변화를 감지할 수 있는 시각적 테스팅을 지원합니다.

Detox 사용

환경세팅

  1. detox-cli
npm install detox-cli --global
  1. (MacOS만 해당) ios 시뮬레이션을 위해
brew tap wix/brew
brew install applesimutils
  1. jest 설치
npm install "jest@^29" --save-dev
  1. detox 설치 및 초기화
npm install detox --save-dev
// root 폴더에서
detox init

detox init을 하면 root 폴더에
.detoxrc.js,
e2e/jest.config.js,
e2e/starter.test.js
파일들이 생긴 것을 확인할 수 있습니다.

  1. 앱 설정에서 앱 이름 등 변경하기
  • ios
.detoxrc.js내에서

   apps: {
     'ios.debug': {
       type: 'ios.app',
-      binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/YOUR_APP.app',
+      binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/example.app',
-      build: 'xcodebuild -workspace ios/YOUR_APP.xcworkspace -scheme YOUR_APP -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
+      build: 'xcodebuild -workspace ios/example.xcworkspace -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
     },
     'ios.release': {
       type: 'ios.app',
-      binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/YOUR_APP.app',
+      binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/example.app',
-      build: 'xcodebuild -workspace ios/YOUR_APP.xcworkspace -scheme YOUR_APP -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
+      build: 'xcodebuild -workspace ios/example.xcworkspace -scheme example -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
     },

  • android
module.exports = {
  apps: {
    'android.debug': {
      type: 'android.apk',
      build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
      binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk'
    },
    'android.release': {
      type: 'android.apk',
      build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
      binaryPath: 'android/app/build/outputs/apk/release/app-release.apk'
    },
    // ...
  },
  // ...
};
  1. device 설정

.detoxrc.js 파일 내에서 시뮬레이터, 에뮬레이터 타입을 변경합니다.
만약 어떤 것들이 설치되어 있는지 모른다면 아래 명령어를 통해 확인할 수 있습니다.

  • ios
xcrun simctl list devicetypes
  • android
emulator -list-avds

테스트 하고 싶은 device type을 결정해 아래 코드를 수정합니다.

/** @type {Detox.DetoxConfig} */
module.exports = {
  // ...
  devices: {
    simulator: {
      type: 'ios.simulator',
      device: {
        type: 'iPhone 12', <- 이 부분을 수정
      },
    },
    attached: {
      type: 'android.attached',
      device: {
        adbName: '.*', // any attached device
      },
    },
    emulator: {
      type: 'android.emulator',
      device: {
        avdName: 'Pixel_3a_API_30_x86', <- 이 부분을 수정
      },
    },
  },
};
  1. 안드로이드

android 폴더 아래 build.gradle 파일을 아래와 같이 수정해줍니다.

 buildscript {
   ext {
     buildToolsVersion = "31.0.0"
     minSdkVersion = 21 // (1) 반드시 18 이상이어야 합니다.
     compileSdkVersion = 30
     targetSdkVersion = 30
+    kotlinVersion = 'X.Y.Z' // (2) 이 부분은 setting 등에서 확인할 수 있습니다.
   }
 …
   dependencies {
     classpath("com.android.tools.build:gradle:7.1.1")
     classpath("com.facebook.react:react-native-gradle-plugin")
     classpath("de.undercouch:gradle-download-task:5.0.1")
+    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") // (3) 이 부분
 …

 allprojects {
   repositories {
     …
     google()
+    maven { // (4) 이 부분
+      url("$rootDir/../node_modules/detox/Detox-android")
+    }
     maven { url 'https://www.jitpack.io' }
   }
 }

android/app/build.gradle 파일도 아래와 같이 수정합니다.

 …

 android {
   …
   defaultConfig {
     …
     versionCode 1
     versionName "1.0"
+    testBuildType System.getProperty('testBuildType', 'debug')
+    testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
   …
   buildTypes {
     release {
       minifyEnabled true
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+      proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"

       signingConfig signingConfigs.release
     }
   }
   …

 dependencies {
+  androidTestImplementation('com.wix:detox:+')
+  implementation 'androidx.appcompat:appcompat:1.1.0'
   implementation fileTree(dir: "libs", include: ["*.jar"])

그리고 android/app/src/androidTest/java/com/<your.package>/DetoxTest.java 이 경로에 실제 package이름을 넣어 파일을 만들어줍니다.

package com.<your.package>; // (1)

import com.wix.detox.Detox;
import com.wix.detox.config.DetoxConfig;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
    @Rule // (2)
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);

    @Test
    public void runDetoxTests() {
        DetoxConfig detoxConfig = new DetoxConfig();
        detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
        detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
        detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60);

        Detox.runTests(mActivityRule, detoxConfig);
    }
}

보통 android/app/src/main/AndroidManifest.xml 이 경로에서 아래 코드를 찾아보면 MainActivity로 되어 있을 건데요. 사용자가 다른 이름으로 정의했을 수도 있으니 체크해줍니다.

안드로이드 네트워크 보안을 위한 파일을 아래 경로에 다음과 같은 내용으로 만들어줍니다. android/app/src/main/res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">10.0.2.2</domain>
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>

만약 이전에 네트워크 보안 config가 없었다면, 생성 후에 이를 등록해야 합니다. android/app/src/main/AndroidManifest.xml

 <manifest>
   <application
   
+    android:networkSecurityConfig="@xml/network_security_config">
   </application>
 </manifest>
  1. 빌드
  • ios debug
detox build --configuration ios.sim.debug
  • ios release
detox build --configuration ios.sim.release
  • android debug
detox build --configuration android.emu.debug
  • android release
detox build --configuration android.emu.release

https://www.browserstack.com/guide/grey-box-testing
https://www.browserstack.com/guide/detox-testing-tutorial
https://wix.github.io/Detox/docs/articles/how-detox-works/
https://wix.github.io/Detox/docs/introduction/getting-started

0개의 댓글