Skip to content

Commit 0b10dd4

Browse files
authored
Update 마님.md
1 parent f0cde5a commit 0b10dd4

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

04/마님.md

+269
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,223 @@ console.log(dom.window.document.querySelector('p').textContent) // "Hello World
176176
3. 함수나 모듈의 실제 반환 값을 적는다
177177
4. 3번의 기대에 따라 2번의 결과가 일치하는지 확인한다
178178
5. 기대하는 결과를 반환한다면 테스트는 성공이며, 만약 기대와 다른 결과를 반환하면 에러를 던진다
179+
- Node.js 기본 제공 assert 활용
180+
```
181+
const assert = require('assert')
182+
183+
function sum(a, b) {
184+
return a + b
185+
}
186+
187+
assert.equal(sum(1, 2), 3)
188+
assert.equal(sum(2, 2), 3) // AssertionError
189+
```
190+
좋은 테스트 코드는 다양한 테스트 코드가 작성되고 통과하는 것뿐만 아니라 어떤 테스트가 무엇을 테스트하는지 일목요연하게 보여주는 것도 중요
191+
- Jest 테스팅 프레임워크 사용
192+
```
193+
function sum(a, b) {
194+
return a + b
195+
}
196+
197+
module.exports = {
198+
sum,
199+
}
200+
201+
// math.test.js
202+
const { sum } = require('./math');
203+
204+
test('두 인수가 덧셈이 되어야 한다.', () => {
205+
expect(sum(1, 2)).toBe(3)
206+
})
207+
208+
test('두 인수가 덧셈이 되어야 한다.', () => {
209+
expect(sum(2, 2).toBe(3)
210+
}) // 에러
211+
```
179212
### 8.2.3 리액트 컴포넌트 테스트 코드 작성하기
213+
1. 컴포넌트를 렌더링한다
214+
2. 필요하다면 컴포넌트에서 특정 액션을 수행한다
215+
3. 컴포넌트 렌더링과 2번의 액션을 통해 기대하는 결과와 실제 결과를 비교한다
216+
```
217+
import Reac from 'react';
218+
import { render, screen } from '@testing-library/react';
219+
import App from './App';
220+
221+
test('renders learn react link', () => {
222+
render(<App/>);
223+
const linkElement = screen.getByTest(/learn react/i);
224+
expect(linkElement).toBeInTheDocument();
225+
})
226+
```
227+
- getBy..
228+
인수의 조건에 맞는 요소를 반환
229+
230+
해당 요소가 없거나 두 개 이상이면 에러를 발생
231+
232+
복수 개를 찾을 경우 getAllBy...를 사용
233+
234+
- findBy...
235+
getBy...와 유사하나 Promise를 반환
236+
237+
비동기로 찾으며 1000ms의 타임아웃 기본값
238+
239+
두 개 이상이면 에러를 발생시키며 findAllBy...를 활용
240+
241+
비동기 액션 이후에 요소를 찾을 때 사용
242+
- queryBy...
243+
인수의 조건에 맞는 요소를 반환하는 대신, 찾지 못한다면 에러가 아닌 null을 반환
244+
245+
복수일 경우 queryAllBy... 활용
246+
#### 정적 컴포넌트
247+
```
248+
import { render, screen } from '@testing-library/react';
249+
250+
import StaticComponent from './index';
251+
252+
beforeEach(() => { // 각 테스트(it)를 수행하기 전에 실행하는 함수
253+
render(<StaticComponent />)
254+
})
255+
256+
describe('링크 확인', () => { // 비슷한 속성을 가진 테스트를 하나의 그룹으로 묶는 역할. describe in describe 가능
257+
it('링크가 3개 존재한다.', () => {
258+
const ul = screen.getByTestId('ul');
259+
expect(ul.children.length).toBe(3)
260+
})
261+
262+
it('링크 목록의 스타일이 square다.', () => { // it은 test의 alias
263+
const ul = screen.getByTestId('ul');
264+
expect(ul).toHaveStyle('list-style-type: square;')
265+
})
266+
})
267+
268+
describe('리액트 링크 테스트', () => {
269+
it('리액트 링크가 존재한다.', () => {
270+
const reactLink = screen.getByText('리액트');
271+
expect(reactLink).toBeVisible();
272+
})
273+
274+
it('리액트 링크가 올바른 주소로 존재한다', () => {
275+
const reactLink = screen.getByText('리액트');
276+
expect(reactLink.tagName).toEqual('A');
277+
expect(reactLink).toHaveAttribute('href', 'https://reactjs.org')
278+
})
279+
})
280+
281+
describe('네이버 링크 테스트', () => {
282+
it('네이버 링크가 존재한다.', () => {
283+
const naverLink = screen.getByText('네이버');
284+
expect(naverLink).toBeVisible();
285+
})
286+
287+
it('네이버 링크가 올바른 주소로 존재한다', () => {
288+
const naverLink = screen.getByText('네이버');
289+
expect(naverLink.tagName).toEqual('A');
290+
expect(naverLink).toHaveAttribute('href', 'https://naver.com')
291+
})
292+
293+
it('네이버가 같은 창에서 열려야 한다.', () => {
294+
const naverLink = scree.getByText('네이버');
295+
expect(naverLink).not.toHaveAttribute('target');
296+
})
297+
})
298+
```
299+
#### 동적 컴포넌트
300+
- 사용자가 useState를 통해 입력을 변경하는 컴포넌트
301+
```
302+
import { fireEvent, render } from '@testing-library/react';
303+
import useEvent from '@testing-library/user-event';
304+
305+
import { InputComponent } from '.';
306+
307+
describe('InputComponent 테스트', () => {
308+
const setup = () => { // 현재 파일의 모든 테스트가 렌더링과 button, input을 필요로 하므로 하나의 함수로 묶어둠
309+
const screen = render(<InputComponent/>);
310+
const input = screen.getByLabelText('input') as HTMLInputElement
311+
const button = screen.getByText(/제출하기/i) as HTMLButtonElement
312+
return {
313+
input,
314+
button,
315+
...screen,
316+
}
317+
}
318+
319+
it('input의 초깃값은 빈 문자열이다', () => {
320+
const { input } = setup()
321+
expect(input.value).toEqual('')
322+
})
323+
324+
it('input의 최대 길이가 20자로 설정돼 있다', () => {
325+
const { input } = setup()
326+
expect(input).toHaveAttribute('maxlength', '20')
327+
})
328+
329+
it('영문과 숫자만 입력된다.', () => {
330+
const { input } = setup()
331+
const inputBalue = '안녕하세요123'
332+
userEvent.type(input, inputValue) // 사용자가 타이핑하는 것을 흉내내는 메서드
333+
expect(input.value).toEqual('123')
334+
})
335+
336+
it('아이디를 입력하지 않으면 버튼이 활성화되지 않는다', () => {
337+
const { button } = setup()
338+
expect(button).toBeDisbaled()
339+
})
340+
341+
it('아이디를 입력하면 버튼이 활성화된다', () => {
342+
const { button, input } = setup()
343+
344+
const inputValue = 'helloWorld'
345+
userEvent.type(input, inputValue)
346+
347+
expect(input.value).toEqual(inputValue)
348+
expect(button).toBeEnabled()
349+
})
350+
351+
it('버튼을 클릭하면 alert가 해당 아이디로 표시된다', () => {
352+
const alertMock = jest
353+
.spyOn(window, 'alert') // 어떠한 특정 메서드를 오염시키지 않고 실행이 됐는지, 어떤 인수로 실행됐는지 등 실행과 관련된 정보만 얻고 싶을 때 사용, alert를 구현하지 않고 해당 메서드가 실행됐는지만 관찰
354+
.mockImplementation((_: string) => undefined) // Node.js 환경에는 window.alert가 존재하지 않으므로 해당 함수를 모의 함수로 구현
355+
356+
const { button, input } = setup()
357+
const inputValue = 'helloworld'
358+
359+
userEvent.type(input, inputValue)
360+
fireEvent.click(button)
361+
362+
expect(alertMock).toHaveBeenCalledTimes(1)
363+
expect(alertMock).toHaveBeenCalledWith(inputValue)
364+
})
365+
})
366+
```
367+
- 비동기 이벤트가 발생하는 컴포넌트
368+
```
369+
jest.spyOn(window, 'fetch').mockImplementation(
370+
jest.fn(() =>
371+
Promise.resolve({
372+
ok: true,
373+
status: 200, // 모든 서버 오류 케이스 작성 필요
374+
json: () => Promoise.resolve(MOCK_TODO_RESPONSE),
375+
}),
376+
) as jest.Mock
377+
}
378+
```
379+
MSW(Mock Service Worker): Node.js나 브라우저에서 모두 사용할 수 있는 모킹 라이브러리
380+
381+
브라우저에서는 서비스 워커를 활용해 실제 네트워크 요청을 가로채는 방식으로 모킹을 구현
382+
383+
Node.js 환경에서는 https나 XMLHttpRequest의 요청을 가로채는 방식으로 작동
180384
### 8.2.4 사용자 정의 훅 테스트하기
385+
react-hooks-testing-library를 활용해 훅 테스트
181386
### 8.2.5 테스트를 작성하기에 앞서 고려해야 할 점
387+
테스트 커버리지: 해당 소프트웨어가 얼마나 테스트됐는지를 나타내는 지표
388+
389+
테스트 커버리지가 높다고 만능은 아니며 프론트의 경우 TDD로 모든 케이스를 커버하긴 불가능하며, 빠른 진행을 위해 QA에 의존할 수 있음
390+
391+
테스트 코드 최우선 과제는 애플리케이션에서 가장 취약하거나 중요한 부분을 파악하는 것 ex. 결제
182392
### 8.2.6 그 밖에 해볼 만한 여러 가지 테스트
393+
- Unit Test: 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 정확히 작동하는지 검증하는 테스트
394+
- Integration Test: 유닛 테스트를 통과한 여러 컴포넌트가 묶여서 하나의 기능으로 정상적으로 작동하는지 확인하는 테스트
395+
- End to End Test: E2E 테스트. 실제 사용자처럼 작동하는 로봇을 활용해 애플리케이션의 전체적인 기능을 확인하는 테스트 // Cypress
183396
### 8.2.7 정리
184397

185398
# 09. 모던 리액트 개발 도구로 개발 및 배포 환경 구축하기
@@ -193,15 +406,71 @@ console.log(dom.window.document.querySelector('p').textContent) // "Hello World
193406
### 9.1.7 정리
194407
## 9.2 깃허브 100% 활용하기
195408
### 9.2.1 깃허브 액션으로 CI 환경 구축하기
409+
CI(Continuous Integration): 여러 기여자가 기여한 코드를 지속적으로 빌드하고 테스트해 코드의 정합성을 확인하는 과정
410+
411+
CI는 저장소에서 코드의 변화가 있을 때마다 전체 소프트웨어의 정합성을 확인하기 위한 테스트, 빌드, 정적 분석, 보안 취약점 분석 등의 작업을 자동으로 실행함
412+
413+
과거 젠킨스Jenkins를 사용하였으며 현재 깃허브 액션이 대안으로 사용됨
414+
415+
깃허브 저장소를 기바능로 깃허브에서 발생하는 다양한 이벤트를 트리거 삼아 다양한 작업을 할 수 있게 도와줌
416+
- 깃허브의 어떤 브랜치에 푸시가 발생하면 빌드를 수행
417+
- 깃허브의 특정 브랜치가 메인 브랜치를 대상으로 풀 리퀘스트가 열리면 빌드, 테스트, 정적 분석을 수행
418+
#### 깃허브 액션의 기본 개념
419+
- 러너: 파일로 작성된 깃허브 액션이 실행되는 서버. 특별히 지정하지 않으면 공용 깃허브 액션 서버를 이용하며, 별도의 러너를 구축해 자체적으로 운영 가능
420+
- 액션: 러너에서 실행되는 하나의 작업 단위. yaml 파일로 작성된 내용을 하나의 액션이라 볼 수 있음
421+
- 이벤트: 깃허브 액션의 실행을 일으키는 이벤트. 필요에 따라 한 개 이상의 이벤트 지정 가능. 특정 브랜치를 지정하는 이벤트도 가능.
422+
- pull_request: PR과 관련된 이벤트. PR이 열리거나, 닫히거나, 수정되거나, 할당되거나, 리뷰 요청되는 등의 PR과 관련된 이벤트
423+
- issues: 이슈와 관련된 이벤트. 이슈가 열리거나, 닫히거나, 삭제되거나, 할당되는 등 이슈와 관련된 이벤트
424+
- push: 커밋이나 푸태그가 푸시될 때 발생하는 이벤트
425+
- schedule: 저장소에서 발생하는 이벤트와 별개로 특정 시간에 실행되는 이벤트를 의미
426+
- 5 4 * * *: 매일 4시 5분에 실행. 분, 시간, 일, 월, 요일 순으로 표현. *는 모든 값을 의미
427+
- jobs: 하나의 러너에서 실행되는 여러 스텝의 모음. 하나의 액션에서 여러 잡을 생성할 수 있으며 특별히 선언한 게 없다면 내부 가상머신에서 각 잡은 병렬로 실행
428+
- steps: 잡 내부에서 일어나는 하나하나의 작업. 셀 명령어나 다른 액션을 실행할 수도 있음. 병렬X
196429
### 9.2.2 직접 작성하지 않고 유용한 액션과 깃허브 앱 가져다 쓰기
430+
#### 깃허브에서 제공하는 기본 액션
431+
- actions/checkout: 깃허브 저장소를 체크아웃하는 액션. 저장소를 기반으로 작업할 때 반드시 필요. 일반적으로 아무런 옵션없이 사용해 해당 액션을 트리거한 최신 커밋을 불러오지만 ref를 지정해 특정 브랜치나 커밋을 체크아웃할 수 있음
432+
- actions/setup-node: Node.js를 설치하는 액션. Node.js를 사용하는 프로젝트에 반드시 필요. node.js 버전 지정 가능
433+
- acitons/github-script: GitHub API가 제공하는 기능을 사용할 수 있도록 도와주는 액션
434+
- actions/stale: 오래된 이슈나 PR을 자동으로 닫거나 더 이상 커뮤니케이션하지 못하도록 닫음
435+
- actions/dependency-review-action: 의존성 그래프에 대한 변경. package.json, package-lock.json, pnpm-lock.yaml 등의 내용이 변경됐을 때 실행되는 액션
436+
- github/codeql-action: 깃허브의 코드 분석 솔루션인 code-ql을 활용해 저장소 내 코드의 취약점을 분석해줌
437+
#### calibreapp/image-actions
438+
저장소에 있는 이미지를 최적화하는 액션
439+
440+
PR로 올라온 이미지를 sharp 패키지를 이용해 거의 무손실로 압축해서 다시 커밋해줌
441+
#### lirantal/is-website-vulnerable
442+
특정 웹사이트를 방문해 해당 웹사이트에 라이브러리 취약점이 존재하는지 확인하는 깃허브 액션
443+
#### Lighthouse CI
444+
구글에서 제공하는 액션. 웹 성능 지표인 라이트하우스를 CI를 기반으로 실행할 수 있도록 도와주는 도구
445+
446+
프로젝트의 URL을 방문해 라이트하우스 검사를 실행
197447
### 9.2.3 깃허브 Dependabot으로 보안 취약점 해결하기
448+
#### package.json의 dependencies 이해하기
449+
- 버전
450+
유의적 버전semantic vesioning
451+
452+
주.부.수로 구성
453+
- 기존 버전과 호환되지 않게 API가 바뀌면 주 버전을 올리고,
454+
- 기존 버전과 호환되면서 새로운 기능을 추가할 때는 부버전을 올리고,
455+
- 기존 버전과 호환되면서 버그를 수정한 것이면 수 버전을 올린다
456+
- 의존성
457+
- dependencies: package.json에서 npm install을 실행하면 설치되는 의존성. npm install 패키지명을 실행하면 dependencies에 추가됨
458+
- devDependencies: package.json에서 npm install을 실행하면 설치되는 의존성. npm install 패키지명 --save-dev를 실행하면 devDependencies에 추가됨. 프로젝트 실행에는 필요하지 않지만, 개발 단계에서 필요한 패키지들 선언
459+
- peerDependencies: 주로 서비스보다는 라이브러리와 패키지에서 자주 쓰이는 단위. 직접적으로 해당 패키지를 require하거나 import하지는 않지만 호환성으로 인해 필요한 경우
460+
#### Dependabot으로 취약점 해결하기
461+
의존성에 숨어 잇는 잠재적인 위협을 깃허브를 통해 확인하고 조치하는 방법
198462
### 9.2.4 정리
199463
## 9.3 리액트 애플리케이션 배포하기
464+
자체적인 IT 인프라가 구축돼 있어 해당 인프라를 사용하는 큰 회사, 비교적 규모가 작아 자체 인프라를 구축하기 어려운 스타트업의 경우 아마존 웹 서비스나 구글 클라우드 플랫폼, 마이크로소프트 애저 등 클라우드 서비스를 활용
465+
466+
개인, 소규모 프로젝트의 경우 대형 클라우드 플랫폼에서 복잡하게 배포 파이프라인을 구축하거나, 혹은 별도의 서버를 마련하지 않더라도 손쉽게 서비스를 배포할 수 있는 다양한 방법이 있음
467+
200468
### 9.3.1 Netlify
201469
### 9.3.2 Vercel
202470
### 9.3.3 DigitalOcean
203471
### 9.3.4 정리
204472
## 9.4 리액트 애플리케이션 도커라이즈하기
473+
도커: 서비스 운영에 필요한 애플리케이션을 격리해 컨테이너로 만드는데 이용하는 소프트웨어
205474
### 9.4.1 리액트 앱을 도커라이즈하는 방법
206475
### 9.4.2 도커로 만든 이미지 배포하기
207476
### 9.4.3 정리

0 commit comments

Comments
 (0)