[React Native] TDD

2021. 1. 4. 03:09React/React Native

1. TDD

1) TDD 단계

  • ReadMe Driven Development : 어떤 요구사항 어떤 결과가 예측되어야하는지 기술하는 단계
  • Make it fail : 테스트 코드만 작성하고 개발을 진행하지 않음
  • Make it green : 작성한 테스트 코드가 통과하도록 실제 코드를 작업하는 단계
  • Refactoring

2) React Native

해외에서 널리사용되고 개발자들이 선호하는 플랫폼이다. 너무 잦은 업데이트로 인한 어려움이 있다. 아직까지는 국내에서는 많이 사용하고 있지 않다.

3) 개발 환경 설정

  • JEST 라이브러리 : React-Native-Cli 로 하면 기본적으로 JEST가 포함되어있다. (1)
  • Enzyme 라이브러리 : 에어비엔비에서 제공되며, 리액트 컴포넌트 테스트 라이브러리로 컴포넌트 렌더링 혹은 탐색에 사용된다. (2)
  • Detox : 사용자 관점의 테스트를 수행하며, iOS에만 지원이 되고 있다. (3)  (미해결)

 

(1)

JEST 설치

(2)

Enzyme 설치

주의해야할 점은, Enzyme를 사용하기 위해서는 별도의 설정이 필요하다.

npm i --save-dev enzyme
npm i --save react@16 react-dom@16
npm i --save-dev enzyme enzyme-adapter-react-16

루트 폴더에 setup.js 파일을 생성하고, package.json 파일에서 setupFile을 추가해주어야 한다.

// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

Enzyme

(3)

Detox 설치

// 없다면 설치
brew tap wix/brew
brew install applesimutils

// detox 설치
npm install -g detox-cli
npm install detox --save-dev
npm install jest-circus --save-dev

설치가 완료되었을 경우, packages.json 파일에서 설정을 추가해준다.

"detox": {
    "test-runner": "jest",
    "configurations": {
      "ios.sim.debug": {
        "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/TDDRN.app",
        "build": "xcodebuild -project ios/TDDRN.xcodeproj -scheme TDDRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
        "type": "ios.simulator",
        "name": "iPhone 7"
      }
    }
  }

 

detox init -r jest
detox build

detox test --configuration ios
detox test --configuration android

※ Detox를 설치하는 과정에서 문제가 많이 발생하는 것을 확인할 수 있다. packages.json 파일에서 추가한 설정파일을 .detoxrc.json에도 binaryPath와 build를 똑같이 추가해주었음에도 문제가 발생하는 것을 확인할 수 있었다. (미해결)

4) 테스트 라이브러리 기본 동작 확인

'__test__' 폴더에서 App.spec.js를 수정하고 'npm test' 명령어로 동작을 확인해본다.

JEST (1)

Enzyme (2)

 

(1)

import 'react-native';
import React from 'react';
import App from '../App';


describe('Jest', () => {
  it('is it working?', () => {
    const a = 1;
    expect(a + 1).toBe(2)
  })
})

(2)

단, Enzyme는 컴포넌트를 테스트하는 용도라는 점에서 주의해야한다.

import 'react-native';
import React from 'react';
import App from '../App';

import { shallow } from 'enzyme';

describe('Enzyme', () => {
  it('is it working?', () => {
    // 컴포넌트 확인
    const text = 'some text'
    const wrapper = shallow(<Text>{text}</Text>);
    expect(wrapper.text()).toBe(text)
  })
})

(3)

Detox는 async - await를 지원한다는 점에서 사용자 관점으로 테스트를 할 수 있다는 장점을 가지고 있다. 기본적으로 생성된 'firstTest.spec.js' 테스트 케이스는 앱과는 맞지 않기 때문에 당연히 실패할 것이다.

5) 컴포넌트 테스트 예시

물론, 해당 테스트에 대한 컴포넌트 클래스가 생성되어 있어야 올바르게 동작한다.

import 'react-native';
import React from 'react';
import App from '../App';

import { shallow } from 'enzyme';

describe('App', () => {
  const wrapper = shallow(<App></App>)

  it('is Text visible?', () => {
    expect(wrapper.find('Text').contains('ToDo TDD')).toBe(true); // 'ToDo TDD' 텍스트의 존재 유무를 확인
  })

  it('is AddToDo visible?', () => {
    expect(wrapper.find('AddToDo')).toHaveLength(1); // 'AddToDo'의 존재가 1개가 있는지 확인
  })

  it('is ToDoList visible?', () => {
    expect(wrapper.find('ToDoList')).toHaveLength(1); // 'ToDoList'의 존재가 1개가 있는지 확인
  })
})
import 'react-native';
import React from 'react';
import AddToDo from '../src/AddToDo';

import { shallow } from 'enzyme';

describe('Rendering', () => {
  let wrapper;

  // 깨끗한 상태에서 동작하도록 새로 랜더링 설정
  beforeEach(() => {
    wrapper = shallow(<AddToDo/>)
  })

  it('is TextInput visible?', () => {
    expect(wrapper.find('TextInput')).toHaveLength(1); // 'TextInput'의 존재가 1개가 있는지 확인
  })

  it('is Button visible?', () => {
    expect(wrapper.find('Button')).toHaveLength(1); // 'Button'의 존재가 1개가 있는지 확인
  })
})

describe('Interaction', () => {
  let wrapper;
  let props;
  const text = "some toDo";

  beforeEach(() => {
    props = {
        onAdded: jest.fn() // jest에서 제공하는 함수로 함수가 몇번이 호출되었는지에 대한 정보를 가짐
    }
    
    wrapper = shallow(<AddToDo {...props}/>)

    wrapper.find('TextInput').simulate('changeText', text)
    wrapper.find('Button').prop('onPress')();
  })

  it('should call the onAdded callback with input text', () => {
    expect(props.onAdded).toHaveBeenCalledTimes(1);
    expect(props.onAdded).toHaveBeenCalledWith(text); // 인자 전달 확인
  })
})
import 'react-native';
import React from 'react';
import ToDoList from '../src/ToDoList';

import { shallow } from 'enzyme';

describe('rendering', () => {

    let wrapper;
    let props;

    beforeEach(()=> {
        props = {
            items: [
                {
                    text: 'some Todo 1',
                    completed: false
                },
                {
                    text: 'some Todo 2',
                    completed: true
                }
            ]
        }
        wrapper = shallow(<ToDoList {...props}/>)
    })
    
    it('should render a flat list', () => {
        expect(wrapper.find('FlatList')).toHaveLength(1);
    })

    it('should pass props to FlatList', () => {
        expect(wrapper.find('FlatList').prop('data')).toBe(props.items);
    })
})

※ describe 내부에 또 다른 describe가 존재할 수 있다.

6) 마무리

버전 업데이트에 따라 신경을 써야 하는 부분도 많고 제공하는 API들의 동작에 문제가 많은 것을 확인할 수 있다. 또한 Detox에 대한 부분들에 대해 학습이 더 필요할 것 같다.

728x90

'React > React Native' 카테고리의 다른 글

[React Native] Expo  (0) 2021.01.05
[React Native] Dimensions, Device 정보  (0) 2021.01.04
[React Native] Platform  (0) 2021.01.04
[React Native] 재사용 가능한 Component  (0) 2021.01.04