React 구성 요소가 UI 테스트를 쉽게 만드는 방법
게시 됨: 2022-03-11백엔드 테스트는 쉽습니다. 원하는 언어를 선택하고 좋아하는 프레임워크와 페어링하고 몇 가지 테스트를 작성한 다음 "실행"을 누르십시오. 본체에 "예! 효과가있다!" 지속적 통합 서비스는 모든 푸시에 대해 테스트를 실행합니다. 인생은 훌륭합니다.
물론 테스트 주도 개발(TDD)은 처음에는 이상하지만 예측 가능한 환경, 여러 테스트 실행기, 프레임워크에 구운 테스트 도구, 지속적인 통합 지원은 삶을 쉽게 만듭니다. 5년 전 나는 테스트가 내가 겪었던 모든 문제에 대한 해결책이라고 생각했습니다.
그런 다음 Backbone이 커졌습니다.
우리는 모두 프론트엔드 MVC로 전환했습니다. 우리의 테스트 가능한 백엔드는 영광스러운 데이터베이스 서버가 되었습니다. 가장 복잡한 코드가 브라우저로 옮겨졌습니다. 그리고 우리 앱은 더 이상 실제로 테스트할 수 없었습니다.
프런트 엔드 코드와 UI 구성 요소를 테스트하는 것이 다소 어렵기 때문입니다.
우리가 원하는 것이 모델이 잘 동작하는지 확인하는 것이라면 그렇게 나쁘지 않습니다. 또는 함수를 호출하면 올바른 값이 변경됩니다. React 단위 테스트를 위해 해야 할 일은 다음과 같습니다.
- 잘 구성된 분리된 모듈을 작성하십시오.
- Jasmine이나 Mocha 테스트(또는 무엇이든)를 사용하여 기능을 실행하십시오.
- Karma 또는 Chutzpah와 같은 테스트 러너를 사용하십시오.
그게 다야 우리 코드는 단위 테스트를 거쳤습니다.
예전에는 프론트엔드 테스트를 실행하는 것이 어려운 부분이었습니다. 모든 프레임워크에는 고유한 아이디어가 있었고 대부분의 경우 테스트를 실행할 때마다 수동으로 새로 고쳐야 하는 브라우저 창을 갖게 되었습니다. 물론, 당신은 항상 잊을 것입니다. 적어도 나는 내가 그랬다는 것을 안다.
2012년 Vojta Jina는 Karma runner(당시 Testacular라고 불림)를 출시했습니다. Karma를 사용하면 프론트 엔드 테스트가 도구 체인의 완전한 시민이 됩니다. React 테스트는 터미널 또는 지속적 통합 서버에서 실행되며 파일을 변경할 때 자체적으로 다시 실행되며 동시에 여러 브라우저에서 코드를 테스트할 수도 있습니다.
우리가 무엇을 더 바랄 수 있겠습니까? 글쎄, 실제로 우리의 프론트 엔드 코드를 테스트하기 위해.
프론트 엔드 테스트에는 단순한 단위 테스트 이상이 필요합니다.
단위 테스트는 훌륭합니다. 알고리즘이 매번 올바른 작업을 수행하는지 확인하거나 입력 유효성 검사 논리, 데이터 변환 또는 기타 격리된 작업을 확인하는 가장 좋은 방법입니다. 단위 테스트는 기초에 완벽합니다.
그러나 프론트 엔드 코드는 데이터를 조작하는 것이 아닙니다. 사용자 이벤트와 적시에 적절한 보기를 렌더링하는 것입니다. 프론트엔드는 사용자에 관한 것입니다.
다음은 우리가 할 수 있기를 원하는 것입니다:
- React 사용자 이벤트 테스트
- 해당 이벤트에 대한 응답 테스트
- 적절한 시간에 적절한 작업이 렌더링되는지 확인
- 많은 브라우저에서 테스트 실행
- 파일 변경 사항에 대한 테스트 재실행
- Travis와 같은 지속적 통합 시스템으로 작업
이 일을 한 10년 동안 나는 React를 파고들기 전까지 사용자 상호작용을 테스트하고 렌더링을 볼 수 있는 적절한 방법을 찾지 못했습니다.
React 단위 테스트: UI 구성 요소
React는 이러한 목표를 달성하는 가장 쉬운 방법입니다. 부분적으로는 테스트 가능한 패턴을 사용하여 앱을 설계해야 하기 때문에 부분적으로는 환상적인 React 테스트 유틸리티가 있기 때문입니다.
이전에 React를 사용한 적이 없다면 제 책 React+d3.js 를 확인해야 합니다. 시각화를 위한 것이지만 React에 대한 "굉장한 경량 소개" 라고 들었습니다.
React는 모든 것을 "컴포넌트"로 구축하도록 강요합니다. React 구성 요소를 위젯이나 일부 논리가 있는 HTML 덩어리로 생각할 수 있습니다. 그것들은 객체라는 점을 제외하고는 함수형 프로그래밍의 많은 최고의 원칙을 따릅니다.
예를 들어, 동일한 매개변수 세트가 주어지면 React 구성 요소는 항상 동일한 출력을 렌더링합니다. 얼마나 많이 렌더링되든, 누가 렌더링하든 상관없이, 우리가 출력을 어디에 배치하든 상관없습니다. 항상 동일합니다. 결과적으로 React 구성 요소를 테스트하기 위해 복잡한 스캐폴딩을 수행할 필요가 없습니다. 그들은 속성에만 관심이 있으며 전역 변수 및 구성 개체를 추적할 필요가 없습니다.
우리는 대부분 상태를 피함으로써 이것을 달성합니다. 함수형 프로그래밍에서는 이것을 참조 투명성이라고 부릅니다. React에는 이에 대한 특별한 이름이 없다고 생각하지만 공식 문서에서는 가능한 한 상태를 사용하지 않는 것이 좋습니다.
사용자 상호 작용을 테스트할 때 React는 함수 콜백에 바인딩된 이벤트를 다루었습니다. 테스트 스파이를 설정하고 클릭 이벤트가 올바른 기능을 호출하는지 확인하는 것은 쉽습니다. 또한 React 구성 요소는 자체적으로 렌더링되기 때문에 클릭 이벤트를 트리거하고 HTML에서 변경 사항을 확인할 수 있습니다. 이것은 React 구성 요소가 자체에만 관심을 갖기 때문에 작동합니다. 여기를 클릭해도 그곳 은 바뀌지 않습니다. 우리는 이벤트 핸들러의 중첩, 잘 정의된 함수 호출을 다룰 필요가 없습니다.
아, 그리고 React는 마술이기 때문에 DOM에 대해 걱정할 필요가 없습니다. React는 소위 가상 DOM을 사용하여 구성 요소를 JavaScript 변수로 렌더링합니다. 그리고 가상 DOM에 대한 참조는 실제로 React 구성 요소를 테스트하는 데 필요한 전부입니다.
꽤 달콤합니다.
React의 TestUtils
React에는 TestUtils
가 내장되어 있습니다. Jest라는 추천 테스트 러너도 있지만 마음에 들지 않습니다. 그 이유는 잠시 후에 설명하겠습니다. 먼저 TestUtils
.
우리는 require('react/addons').addons.TestUtils
와 같은 것을 함으로써 그것들을 얻습니다. 이것은 사용자 상호 작용을 테스트하고 출력을 확인하기 위한 진입점입니다.
React TestUtils
를 사용하면 DOM을 페이지에 삽입하는 대신 변수에 넣어 React 구성 요소를 렌더링할 수 있습니다. 예를 들어, React 구성 요소를 렌더링하려면 다음과 같이 합니다.
var component = TestUtils.renderIntoDocument( <MyComponent /> );
그런 다음 TestUtils
를 사용하여 모든 자식이 렌더링되었는지 확인할 수 있습니다. 이 같은:
var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' );
findRenderedDOMComponentWithTag
는 자식을 살펴보고 찾고 있는 구성 요소를 찾은 다음 반환합니다. 반환된 값은 React 구성 요소처럼 작동합니다.
그런 다음 getDOMNode()
를 사용하여 원시 DOM 요소에 액세스하고 해당 값을 테스트할 수 있습니다. 구성 요소의 h1
태그가 "A title" 인지 확인하려면 다음과 같이 작성합니다.
expect(h1.getDOMNode().textContent) .toEqual("A title");
종합하면 전체 테스트는 다음과 같습니다.
it("renders an h1", function () { var component = TestUtils.renderIntoDocument( <MyComponent /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' ); expect(h1.getDOMNode().textContent) .toEqual("A title"); });
멋진 부분은 TestUtils를 사용하여 사용자 이벤트도 트리거할 수 있다는 것입니다. 클릭 이벤트의 경우 다음과 같이 작성합니다.
var node = component .findRenderedDOMComponentWithTag('button') .getDOMNode(); TestUtils.Simulate.click(node);
이것은 클릭을 시뮬레이션하고 출력, 상태 또는 둘 모두를 변경하는 구성 요소 메서드여야 하는 잠재적 리스너를 트리거합니다. 이러한 수신기는 필요한 경우 상위 구성 요소에서 함수를 호출할 수 있습니다.
모든 경우는 테스트하기 쉽습니다. 변경된 상태는 component.state
에 있으며 일반 DOM 함수로 출력에 액세스할 수 있고 스파이로 함수 호출에 액세스할 수 있습니다.
왜 장난이 아니야?
React의 공식 문서에서는 https://facebook.github.io/jest/를 테스트 러너 및 React 테스트 프레임워크로 사용할 것을 권장합니다. Jest는 Jasmine을 기반으로 하며 동일한 구문을 사용합니다. Jasmine에서 얻는 모든 것 외에도 Jest는 테스트 중인 구성 요소를 제외한 모든 것을 조롱합니다. 이것은 이론상 환상적이지만 나는 성가시다. 아직 구현하지 않았거나 코드베이스의 다른 부분에서 가져온 것은 모두 undefined
입니다. 이것은 많은 경우에 문제가 없지만 조용히 실패하는 버그로 이어질 수 있습니다.
예를 들어 클릭 이벤트를 테스트하는 데 문제가 있었습니다. 내가 무엇을 시도하든, 그것은 단지 리스너를 호출하지 않을 것입니다. 그 다음 나는 그 함수가 Jest에 의해 조롱당했다는 것을 깨달았고 그것은 나에게 이것을 말하지 않았다.
그러나 Jest의 가장 큰 문제는 새로운 변경 사항을 자동으로 테스트하는 감시 모드가 없다는 것이었습니다. 한 번 실행하고 테스트 결과를 얻으면 끝입니다. (저는 작업하는 동안 백그라운드에서 테스트를 실행하는 것을 좋아합니다. 그렇지 않으면 실행하는 것을 잊습니다.) 요즘에는 더 이상 문제가 되지 않습니다.
아, 그리고 Jest는 여러 브라우저에서 React 테스트 실행을 지원하지 않습니다. 이것은 예전보다 문제가 덜하지만 heisenbug가 특정 버전의 Chrome에서만 발생하는 드문 경우에 중요한 기능이라고 생각합니다…
편집자 주: 이 기사가 처음 작성된 이후로 Jest는 상당히 개선되었습니다. 최신 튜토리얼인 React Unit Testing using Enzyme and Jest를 읽고 Jest 테스팅이 현재 작업에 적합한지 스스로 결정할 수 있습니다.
반응 테스트: 통합 예제
어쨌든, 우리는 좋은 프론트엔드 React 테스트가 이론적으로 어떻게 작동해야 하는지 보았습니다. 간단한 예를 들어 실행해 보겠습니다.
React 및 d3.js로 만든 산점도 구성 요소를 사용하여 난수를 생성하는 다양한 방법을 시각화할 것입니다. 코드와 데모도 Github에 있습니다.
Karma를 테스트 러너로, Mocha를 테스트 프레임워크로, Webpack을 모듈 로더로 사용할 것입니다.
설정
소스 파일은 <root>/src
디렉토리에 있고 테스트는 <root>/src/__tests__
디렉토리에 넣을 것입니다. 아이디어는 src
내에 여러 디렉토리를 넣을 수 있다는 것입니다. 각 주요 구성 요소에 대해 하나씩, 각각 고유한 테스트 파일이 있습니다. 이와 같이 소스 코드와 테스트 파일을 번들링하면 다른 프로젝트에서 React 구성 요소를 더 쉽게 재사용할 수 있습니다.
디렉토리 구조가 있으면 다음과 같이 종속성을 설치할 수 있습니다.
$ npm install --save-dev react d3 webpack babel-loader karma karma-cli karma-mocha karma-webpack expect
설치에 실패한 경우 설치의 해당 부분을 다시 실행해 보십시오. NPM은 때때로 재실행 시 사라지는 방식으로 실패합니다.
package.json
파일은 완료되면 다음과 같아야 합니다.
// package.json { "name": "react-testing-example", "description": "A sample project to investigate testing options with ReactJS", "scripts": { "test": "karma start" }, // ... "homepage": "https://github.com/Swizec/react-testing-example", "devDependencies": { "babel-core": "^5.2.17", "babel-loader": "^5.0.0", "d3": "^3.5.5", "expect": "^1.6.0", "jsx-loader": "^0.13.2", "karma": "^0.12.31", "karma-chrome-launcher": "^0.1.10", "karma-cli": "0.0.4", "karma-mocha": "^0.1.10", "karma-sourcemap-loader": "^0.3.4", "karma-webpack": "^1.5.1", "mocha": "^2.2.4", "react": "^0.13.3", "react-hot-loader": "^1.2.7", "react-tools": "^0.13.3", "webpack": "^1.9.4", "webpack-dev-server": "^1.8.2" } }
일부 구성 후에 npm test
또는 karma start
를 사용하여 테스트를 실행할 수 있습니다.

구성
구성이 별로 없습니다. Webpack이 코드를 찾는 방법을 알고 Karma가 테스트를 실행하는 방법을 알고 있는지 확인해야 합니다.
Karma와 Webpack이 함께 플레이할 수 있도록 ./tests.webpack.js
파일에 두 줄의 JavaScript를 넣습니다.
// tests.webpack.js var context = require.context('./src', true, /-test\.jsx?$/); context.keys().forEach(context);
이것은 Webpack이 -test
접미사가 있는 모든 것을 테스트 스위트의 일부로 고려하도록 지시합니다.
Karma를 구성하려면 약간의 작업이 더 필요합니다.
// karma.conf.js var webpack = require('webpack'); module.exports = function (config) { config.set({ browsers: ['Chrome'], singleRun: true, frameworks: ['mocha'], files: [ 'tests.webpack.js' ], preprocessors: { 'tests.webpack.js': ['webpack'] }, reporters: ['dots'], webpack: { module: { loaders: [ {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'} ] }, watch: true }, webpackServer: { noInfo: true } }); };
이 라인의 대부분은 기본 Karma 구성에서 가져온 것입니다. browsers
를 사용하여 테스트가 Chrome에서 실행되어야 한다고 말하고 프레임워크를 사용하여 사용 중인 테스트 프레임 frameworks
를 지정하며 singleRun
을 사용하여 기본적으로 테스트가 한 번만 실행되도록 했습니다. karma start --no-single-run
을 사용하여 백그라운드에서 karma를 계속 실행할 수 있습니다.
그 세 가지는 분명합니다. Webpack 물건은 더 흥미 롭습니다.
Webpack은 코드의 종속성 트리를 처리하기 때문에 files
배열에 모든 파일을 지정할 필요가 없습니다. 우리는 필요한 모든 파일이 필요한 tests.webpack.js
만 있으면 됩니다.
webpack
설정을 사용하여 Webpack에 수행할 작업을 알려줍니다. 일반적인 환경에서 이 부분은 webpack.config.js
파일에 들어갈 것입니다.
또한 Webpack에 JavaScript에 babel-loader
를 사용하도록 지시합니다. 이것은 ECMAScript2015 와 React의 JSX의 멋진 새 기능을 모두 제공합니다.
webpackServer
구성을 사용하여 디버그 정보를 인쇄하지 않도록 Webpack에 지시합니다. 테스트 결과를 망칠 뿐입니다.
React 컴포넌트와 테스트
실행 중인 테스트 스위트를 사용하면 나머지는 간단합니다. 임의의 좌표 배열을 받아들이고 많은 점을 포함하는 <svg>
요소를 생성하는 구성 요소를 만들어야 합니다.
React 테스트 모범 사례(예: 표준 TDD 사례)에 따라 먼저 테스트를 작성한 다음 실제 React 구성 요소를 작성합니다. src/__tests__/
에 있는 바닐라 테스트 파일로 시작합시다.
// ScatterPlot-test.jsx var React = require('react/addons'), TestUtils = React.addons.TestUtils, expect = require('expect'), ScatterPlot = require('../ScatterPlot.jsx'); var d3 = require('d3'); describe('ScatterPlot', function () { var normal = d3.random.normal(1, 1), mockData = d3.range(5).map(function () { return {x: normal(), y: normal()}; }); });
먼저 React, TestUtils, d3.js, expect
라이브러리 및 테스트 중인 코드가 필요합니다. 그런 다음 describe
를 사용하여 새 테스트 모음을 만들고 임의의 데이터를 만듭니다.
첫 번째 테스트를 위해 ScatterPlot
이 제목을 렌더링하는지 확인하겠습니다. 우리의 테스트는 describe
블록 안에 들어갑니다:
// ScatterPlot-test.jsx it("renders an h1", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( scatterplot, 'h1' ); expect(h1.getDOMNode().textContent).toEqual("This is a random scatterplot"); });
대부분의 테스트는 동일한 패턴을 따릅니다.
- 세우다.
- 특정 노드를 찾습니다.
- 내용을 확인하세요.
앞에서 본 것처럼 renderIntoDocument
는 구성 요소를 렌더링하고 findRenderedDOMComponentWithTag
는 테스트 중인 특정 부분을 찾고 getDOMNode
는 원시 DOM 액세스를 제공합니다.
처음에는 테스트가 실패합니다. 통과시키려면 제목 태그를 렌더링하는 구성 요소를 작성해야 합니다.
var React = require('react/addons'); var d3 = require('d3'); var ScatterPlot = React.createClass({ render: function () { return ( <div> <h1>This is a random scatterplot</h1> </div> ); } }); module.exports = ScatterPlot;
그게 다야 ScatterPlot
구성 요소는 예상 텍스트가 포함된 <h1>
태그로 <div>
를 렌더링하고 테스트를 통과합니다. 예, HTML보다 길지만 양해해 주십시오.
나머지 올빼미 그리기
위에서 언급한 것처럼 GitHub에서 나머지 예제를 볼 수 있습니다. 이 문서에서는 단계별로 설명하는 것을 건너뛰지만 일반적인 프로세스는 위와 동일합니다. 하지만 더 재미있는 테스트를 보여드리고 싶습니다. 모든 데이터 포인트가 차트에 표시되는지 확인하는 테스트:
// ScatterPlot-test.jsx it("renders a circle for each datapoint", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot data={mockData} /> ); var circles = TestUtils.scryRenderedDOMComponentsWithTag( scatterplot, 'circle' ); expect(circles.length).toEqual(5); });
이전과 동일합니다. 렌더링, 노드 찾기, 결과 확인 여기서 흥미로운 부분은 DOM 노드를 그리는 것입니다. 다음과 같이 ScatterPlot
구성 요소에 d3.js 마법을 추가합니다.
// ScatterPlot.jsx componentWillMount: function () { this.yScale = d3.scale.linear(); this.xScale = d3.scale.linear(); this.update_d3(this.props); }, componentWillReceiveProps: function (newProps) { this.update_d3(newProps); }, update_d3: function (props) { this.yScale .domain([d3.min(props.data, function (d) { return dy; }), d3.max(props.data, function (d) { return dy; })]) .range([props.point_r, Number(props.height-props.point_r)]); this.xScale .domain([d3.min(props.data, function (d) { return dx; }), d3.max(props.data, function (d) { return dx; })]) .range([props.point_r, Number(props.width-props.point_r)]); }, ...
componentWillMount
를 사용하여 X 및 Y 도메인에 대한 빈 d3 스케일을 설정하고 componentWillReceiveProps
를 사용하여 변경 사항이 있을 때 업데이트되도록 합니다. 그런 다음 update_d3
은 두 척도 모두에 대한 domain
과 range
를 설정했는지 확인합니다.
두 개의 척도를 사용하여 데이터 세트의 임의 값과 그림의 위치 사이를 변환합니다. 대부분의 임의 생성기는 [0,1] 범위의 숫자를 반환하며, 이는 픽셀로 보기에는 너무 작습니다.
그런 다음 구성 요소의 렌더링 메서드에 포인트를 추가합니다.
// ScatterPlot.jsx render: function () { return ( <div> <h1>This is a random scatterplot</h1> <svg width={this.props.width} height={this.props.height}> {this.props.data.map(function (pos, i) { var key = "circle-"+i; return ( <circle key={key} cx={this.xScale(pos.x)} cy={this.yScale(pos.y)} r={this.props.point_r} /> ); }.bind(this))}; </svg> </div> ); }
이 코드는 this.props.data
배열을 통해 각 데이터 포인트에 대한 <circle>
요소를 추가합니다. 단순한.
React와 d3.js를 결합하여 데이터 시각화 구성 요소를 만드는 방법에 대해 더 알고 싶다면 내 책 React+d3.js 를 확인해야 하는 또 다른 좋은 이유입니다.
자동화된 React 구성 요소 테스트: 생각보다 쉬움
이것이 React로 테스트 가능한 프론트 엔드 구성 요소를 작성하는 방법에 대해 알아야 할 전부입니다. 더 많은 코드 테스트 React 구성 요소를 보려면 위에서 언급한 Github의 React 테스트 예제 코드베이스를 확인하세요.
우리는 다음을 배웠습니다.
- React는 우리를 모듈화하고 캡슐화하도록 합니다.
- 이를 통해 React UI 테스트를 쉽게 자동화할 수 있습니다.
- 단위 테스트는 프런트 엔드에 충분하지 않습니다.
- Karma는 훌륭한 테스트 러너입니다.
- Jest는 잠재력이 있지만 아직 충분하지 않습니다. (아니면 지금은 그럴 수도 있습니다.)
이 기사가 마음에 들면 Twitter에서 저를 팔로우하고 아래에 댓글을 남겨주세요. 읽어 주셔서 감사합니다. 행복한 React 테스트입니다!