이 글은 아래 원문을 번역한 글로 의역이 있을 수 있습니다. 정확한 의미를 파악하고 싶으신 분은 원문을 참고해주시기 바랍니다.
원문: https://css-tricks.com/front-end-testing-is-for-everyone/
저작권 정보: https://css-tricks.com/translate-an-article/
테스트는 당신을 매우 신나게 하거나 혹은 눈을 감고 외면하게 하는 것들 중 하나입니다. 당신이 어느 캠프에 들어갔든, 저는 당신에게 프런트엔드 테스트는 모두를 위한 것이라고 말하기 위해 왔습니다. 많은 종류의 테스트들이 있고, 아마도 이것이 진입장벽 중 하나일 것입니다.
저는 이 글에서 가장 유명하고 널리 사용되는 테스트들을 다루려고 합니다. 몇몇 분들에게는 새로운 것이 아닐 수도 있지만, 적어도 상기시키는 역할은 할 수 있을 것입니다. 어느 쪽이든, 제 목표는 당신이 여러 테스트 종류에 대해 좋은 아이디어를 얻어 가는 것입니다. 유닛, 통합, 접근성, Visual regression. 우리가 알아볼 테스트들입니다.
이 뿐만이 아닙니다. 우리는 Mocha와 같이 각 테스트 유형에 사용되는 라이브러리와 프레임워크들도 알아볼 겁니다. Jext, Puppeteer, 그리고 Cypress 등 말이에요. 걱정하진 마세요. 전문 용어는 너무 많이 쓰지 않도록 할 거예요. 하지만, 우리가 다룰 예제를 이해하기 위해서는 약간의 프런트엔드 개발 경험이 있어야 합니다.
시작해봅시다!
목차
- 테스트란?
- 서로 다른 테스트는 프로젝트의 서로 다른 면을 살핀다
- 유닛 테스트(Unit testing)
- 통합 테스트(Integration testing)
- End-to-end (E2E) testing
- 접근성 테스트(Accessibility testing)
- Visual regression testing
- 성능 테스트(Performance testing)
- 마무리
테스트란?
소프트웨어 테스트는 이해 당사자에게 소프트웨어 제품이나 서비스의 품질에 대한 정보를 제공하게 위해 수행되는 조사이다.
- CAM KANER, "Exploratory Testing" - (2006.11.17)
기본적으로, 테스트는 가능한 한 빠르게 개발 오류를 찾는 자동화된 도구입니다. 이를 통해, 당신은 이것이 제품이 되기 전에 오류들을 수정할 수 있습니다. 테스트는 또한 당신이 특정 영역, 예를 들면 접근성 같은 영역의 작업을 하는 것을 잊어버렸다는 것을 상기시켜 주기도 합니다.
간단히 말하면, 프런트엔드 테스트는 사람들이 보는 사이트와 그들이 사용하는 기능들이 의도대로 동작하는지 검증합니다.
프런트엔드 테스트는 애플리케이션의 클라이언트 사이드를 위한 것입니다. 예를 들면, 프런트 엔드 테스트는 '삭제' 버튼을 눌렀을 때 화면에서 항목이 제대로 지워지는지를 검증합니다. 그러나 항목이 데이터베이스에서 실제로 지워졌는지를 확인할 필요는 없습니다. 이런 부분은 백엔드 테스트에서 커버합니다.
요약하면, 테스트는 코드를 배포하기 전에 클라이언트 사이드의 에러를 발견하고 고치기 위한 것입니다.
서로 다른 테스트는 프로젝트의 서로 다른 면을 살핀다
다른 종류의 테스트는 프로젝트의 다른 측면을 다룹니다. 그럼에도 불구하고, 각 테스트의 역할을 구분하고 이해하는 것은 중요합니다. 각 테스트가 무엇을 하는지 헷갈리면 지저분하고 신뢰할 수 없는 테스트를 만들게 됩니다.
여러 종류의 테스트를 사용하여 발생할 수 있는 여러 유형의 이슈를 발견하는 것이 이상적입니다. 일부 테스트들은 특정 테스트에서 얼마나 많은 코드(백분율)가 검토되는지 보여주는 테스트 범위 분석 기능이 있습니다. 굉장히 좋은 기능으로, 개발자들이 100% 커버리지를 목적으로 하는 것을 보아왔지만, 저는 이 기준에만 의존하지는 않습니다. 가장 중요한 것은 가능한 모든 에지 케이스를 다루고 고려하는 것입니다.
자, 이제 다른 종류의 테스트에 관심을 돌려볼 차례입니다. 기억하세요. 이것들을 모두 사용해야 하는 경우는 그리 많지 않습니다. 어떤 상황에서 어떤 테스트를 사용해야 하는지 알 수 있도록 테스트를 구분할 수 있어야 합니다.
유닛 테스트(Unit testing)
- 난이도: 낮음
- 범위: 애플리케이션의 기능과 함수 검증
- 사용 가능 툴: AVA, Jasmine, Jest, Karma, Mocha
유닛 테스트는 테스트의 가장 기본적인 구성 요소입니다. 개별 컴포넌트가 기대한 대로 동작하는지 살펴보고 확인합니다. 유닛 테스트는 모든 프런트엔드 애플리케이션에 중요합니다. 테스트를 통해, 당신의 컴포넌트들은 그들이 예상대로 동작하는지 테스트되고, 더 신뢰할 수 있는 코드 베이스와 앱이 됩니다. 에지 케이스도 고려되고 다뤄질 수 있습니다.
유닛 테스트는 API 테스트에 특히 유용합니다. 그러나 실제 API를 호출하는 것보다 하드코딩(혹은 모킹)된 데이터를 사용하는 것이 테스트가 항상 일관성을 유지하게 하는데 좋습니다.
아주 간단한(기초적인) 함수를 예로 들어보겠습니다.
const sayHello = (name) => {
if (!name) {
return "Hello human!";
}
return `Hello ${name}!`;
};
기본적인 사례지만, 사용자가 이름을 전달하지 않을 수 있는 에지 케이스를 다루고 있는 것을 알 수 있습니다. 이름이 있으면, `Hello ${name}!`을 받을 수 있습니다. 여기서 ${name}은 사용자가 제공한 것입니다.
"음, 왜 우리가 그런 작은 것을 테스트해야 하지?" 당신은 의아해할지도 모릅니다. 여기에는 몇 가지 매우 중요한 이유가 있습니다.
- 테스트는 함수에서 나올 수 있는 결과에 대해 깊이 생각하게 합니다. 당신은 당신의 코드를 커버하는데 도움이 되는 에지 케이스를 발견하게 됩니다.
- 코드의 일부는 이 에지 케이스에 의해 테스트될 수 있으며, 누군가 와서 중요한 부분을 지우면, 테스트가 이 코드는 중요하고 지우면 안 된다는 것을 경고할 것입니다.
유닛 테스트는 종종 작고 간단합니다. 예제로 확인해봅니다.
describe("sayHello function", () => {
it("should return the proper greeting when a user doesn't pass a name", () => {
expect(sayHello()).toEqual("Hello human!")
})
it("should return the proper greeting with the name passed", () => {
expect(sayHello("Evgeny")).toEqual("Hello Evgeny!")
})
})
'describe'와 'it'는 문법적 설탕일 뿐입니다. 가장 중요한 줄은 'expect'와 'toEqual'입니다. 'describe'와 'it'는 테스트를 터미널에 출력되는 논리적인 단위로 분리합니다. 'expect' 함수는 우리가 검증하고자 하는 입력값을 받고, 'toEqual' 함수는 기대되는 결괏값을 받습니다. 당신의 애플리케이션을 테스트하는 데 사용할 수 있는 수많은 기능과 함수가 있습니다.
통합 테스트(Integration testing)
- 난이도: 보통
- 범위: 유닛 간의 통합 검증
- 사용 가능 툴: AVA, Jest, Testing Library
만약 유닛 테스트가 블록의 동작을 검사한다면, 통합 테스트는 블록들이 함께 결점 없이 잘 동작하는지 검사합니다. 통합 테스트는 컴포넌트 사이의 상호 작용을 검증하기 때문에 굉장히 중요합니다. 애플리케이션이 각자 동작하는 고립된 조각들로 구성되는 경우는 매우 드뭅니다. 이것이 우리가 통합 테스트에 의존하는 이유입니다.
유닛 테스트로 검증한 함수이지만, 이번엔 간단한 React 애플리케이션으로 만들어보았습니다. 버튼을 클릭하면 화면에 인사말이 나타난다고 가정해봅시다. 이 말은 테스트가 함수뿐만 아니라 HTML DOM과 버튼의 기능까지 검증한다는 것을 말합니다. 우리는 이 모든 부분들이 함께 동작하는 것을 테스트하고자 합니다.
테스트할 <Greeting /> 컴포넌트:
export const Greeting = () => {
const [showGreeting, setShowGreeting] = useState(false);
return (
<div>
<p data-testid="greeting">{showGreeting && sayHello()}</p>
<button data-testid="show-greeting-button" onClick={() => setShowGreeting(true)}>Show Greeting</button>
</div>
);
};
통합 테스트 코드:
describe('<Greeting />', () => {
it('shows correct greeting', () => {
const screen = render(<Greeting />);
const greeting = screen.getByTestId('greeting');
const button = screen.getByTestId('show-greeting-button');
expect(greeting.textContent).toBe('');
fireEvent.click(button);
expect(greeting.textContent).toBe('Hello human!');
});
});
우리는 이미 유닛 테스트를 통해 'describe'와' it'를 알고 있습니다. 이것들은 테스트를 논리적으로 분리합니다. 특수 에뮬레이트 된 DOM에 <Greeting /> 컴포넌트를 표시하는 render 함수가 있습니다. 실제 DOM을 건드리지 않고 컴포넌트 간의 상호 작용은 테스트할 수 있고, 비용을 절약할 수 있습니다.
다음으로 테스트 ID(#greeting, #show-greeting-button)을 통해 <p>와 <button> 요소를 가져옵니다. 에뮬레이트 된 DOM에서 원하는 구성 요소를 쉽게 얻을 수 있기 때문에 테스트 ID를 사용합니다. 다른 방법도 많지만 이것이 가장 많이 사용하는 방법입니다.
7번째 줄이 되어서야 진짜 통합 테스트가 시작됩니다! 먼저 <p> 태그가 비어있는지 확인합니다. 그리고 클릭 이벤트를 시뮬레이션하여 버튼을 클릭합니다. 마지막으로 <p> 태그 안에 'Hello human!'이 있는지 확인합니다. 끝입니다! 비어있는 단락이 버튼이 클릭된 후 텍스트를 포함한는지 확인했습니다. 컴포넌트는 검증되었습니다.
물론, 우리는 누군가가 그들의 이름을 입력할 수 있는 input 태그를 추가할 수 있고 입력값을 greeting 함수에 사용할 수 있습니다. 그러나, 저는 좀 더 간단하게 만들기로 했습니다. 입력값은 다른 유형의 테스트에서 다뤄볼 수 있습니다.
End-to-end (E2E) testing
- 난이도: 높음
- 범위: 수행할 작업 및 예상 결과를 바탕으로 실제 브라우저에서의 사용자 상호 작용 검증
- 사용 가능 툴: Cypress, Puppeteer
E2E 테스트는 이 중에서 가장 높은 수준의 테스트입니다. E2E 테스트는 사람들이 어떻게 당신의 애플리케이션을 보고 어떻게 상호작용 하는지에만 관여합니다. 코드나 구현에 대해서는 전혀 다루지 않습니다.
E2E 테스트는 브라우저에 무엇을 할 건지, 무엇을 클릭할 건지, 무엇을 입력할 건지 알려줍니다. 우리는 최종 사용자가 경험할 수 있는 다양한 기능과 흐름을 테스트할 수 있는 모든 종류의 상호작용을 만들 수 있습니다. 말 그대로 모든 것이 작동하는 것을 검증하기 위해 상호작용하는 로봇입니다.
E2E 테스트는 통합 테스트와 유사하다고 볼 수 있습니다. 그러나, E2E 테스트는 mock이 아닌 실제 브라우저에서 실제 DOM을 사용하여 실행됩니다. 일반적으로 실제 데이터와 실제 API를 사용하여 테스트합니다.
유닛 테스트와 통합 테스트를 통해 모두 커버하는 것이 좋습니다. 그러나, 사용자는 브라우저에서 애플리케이션을 실행할 때 예상치 못한 동작을 마주칠 수도 있습니다. E2E 테스트는 이에 대한 완벽한 해결책입니다.
매우 유명한 테스트 라이브러리 Cypress를 사용하여 예제를 살펴봅시다. 좀 전의 컴포넌트를 E2E 테스트에서 사용할 예정입니다. 이번에는 브라우저에서 추가 기능과 함께 합니다.
다시 말하지만, 우리는 애플리케이션의 코드를 볼 필요가 없습니다. 우리는 어떤 애플리케이션을 갖고 있고, 이것을 사용자로서 테스트해보고자 하는 것입니다. 클릭해야 하는 버튼과 버튼의 ID를 알고 있습니다. 이것만 알면 됩니다.
describe('Greetings functionality', () => {
it('should navigate to greetings page and confirm it works', () => {
cy.visit('http://localhost:3000')
cy.get('#greeting-nav-button').click()
cy.get('#greetings-input').type('Evgeny', { delay: 400 })
cy.get('#greetings-show-button').click()
cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')
})
})
이 E2E 테스트는 이전의 통합 테스트와 매우 유사해 보입니다. 명령문은 굉장히 비슷하고, 다른 점은 실제 브라우저에서 실행된다는 것입니다.
먼저, 'cy.visit'를 사용하여 애플리케이션이 있는 특정 URL로 이동합니다.
cy.visit('http://localhost:3000')
두 번째로, 'cy.get'을 이용하여 ID로 navigation 버튼을 받은 다음, click 하도록 지시합니다. 그러면 <Greeting /> 컴포넌트가 있는 페이지로 이동합니다. 사실, 저는 컴포넌트를 제 개인 웹 사이트에 추가했고 그것의 URL 경로를 제공했습니다.
cy.get('#greeting-nav-button').click()
그다음, 순차적으로, 텍스트 입력을 받고, 'Evgeny'를 입력하고, #greeting-show-button 버튼을 클릭하고, 마지막으로 기대하던 인사말을 받았는지 확인합니다.
cy.get('#greetings-input').type('Evgeny', { delay: 400 })
cy.get('#greetings-show-button').click()
cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')
실제 라이브 브라우저에서 테스트가 어떻게 버튼을 클릭하는지 지켜보는 것은 멋집니다. 무슨 일이 일어나고 있는지 볼 수 있도록 테스트 속도를 조금 늦췄습니다. 이 모든 것은 보통 매우 빠르게 일어납니다.
터미널 결과:
접근성 테스트(Accessibility testing)
- 난이도: 높음
- 범위: 접근성 표준에 따라 애플리케이션 인터페이스 검증
- 사용 가능 툴: AccessLint, axe-core, Lighthouse, pa11y
접근성 테스트는 장애인들이 웹사이트에 효과적으로 접근하고 사용할 수 있는지 검증합니다. 이 테스트들은 당신이 접근성 면에서 표준을 준수하여 웹사이트를 구성했는지 확인합니다.
예를 들어, 많은 시각장애인들은 스크린 리더를 사용합니다. 스크린 리더는 당신의 웹사이트를 스캔하여 사용자가 이해할 수 있는 형식(주로 말하는 형식)으로 표현하려고 시도합니다. 개발자로서, 당신은 스크린 리더의 작업을 쉽게 만다면 접근성 테스트는 어디서부터 시작해야 하는지 이해하도록 도울 것입니다.
여러 가지 툴이 있는데, 일부는 자동화되어 있고 일부는 접근성을 검증하기 위해 수동으로 실행합니다. 예를 들어, 크롬은 이미 개발자 도구에 하나(Lighthouse) 내장하고 있습니다.
Lighthouse를 이용해서 E2E 테스트 섹션에서 만든 애플리케이션을 검증해보겠습니다. 크롬 개발자 도구를 열고, 'Accessibility(접근성)' 옵션을 클릭하고, 'Generate report(보고서 생성)'을 클릭합니다.
이게 끝입니다! Lighthouse는 제 역할을 다한 다음, 멋진 보고서를 생성하고, 점수를 매기고, 결과를 요약하고, 점수를 개선할 수 있는 대략적인 방법을 알려줍니다.
그러나 이것은 접근성을 테스트하는 도구들 중 하나일 뿐입니다. 우리는 접근성 테스트를 위한 모든 종류의 툴이 있으며, 무엇을 테스트할 건지 그 툴이 그런 목적에 적합한지 생각해볼 가치가 있습니다.
Visual regression testing
- 난이도: 높음
- 범위: 코드 변경으로 인한 시각적 차이를 포함한 애플리케이션의 시각적 구조 검증
- 사용 가능 툴: Cypress, Percy, Applitools
때때로 마지막 변경 사항이 애플리케이션의 인터페이스를 시각적으로 손상시키지 않았는지 검증하기에 E2E 테스트가 충분하지 않을 때가 있습니다. 애플리케이션이 깨지지 않았는지 확인하기 위해 코드를 추가한 적이 있나요? 대부분의 경우 코드 베이스를 변경하면 앱의 시각적 구조나 레이아웃이 변경됩니다.
해결책은 Visual regression 테스트입니다. 작동 방식은 꽤 간단합니다. 시각적 테스트는 단지 페이지나 컴포넌트를 스크린샷으로 캡처하여 이전에 성공한 테스트에서 캡처한 스크린샷과 비교하는 것입니다. 스크린샷 간에 다른 점을 발견하면 우리에게 일종의 알림을 줄 것입니다.
visual regression 테스트가 어떻게 동작하는지 Percy라는 visual regression 툴을 사용하여 알아봅시다. visual regression 테스트를 할 수 있는 많은 방법들이 있지만, 저는 Percy가 간단한 것 같습니다. 여기서 당신은 CSS-Tricks에 있는 Paul Ryan의 Percy 톺아보기로 넘어갈 수 있습니다. 우리는 그 개념을 이해하기 위해 훨씬 간단한 것을 해볼 것입니다.
우리의 Greegting 애플리케이션에서 버튼을 input 하단으로 옮겨서 의도적으로 레이아웃을 깼습니다. 이 오류를 Percy로 잡아봅시다.
Percy는 Cypress와 잘 연동되기 때문에 설치 안내에 따라 기존의 E2E 테스트와 함께 Percy regression 테스트를 실행할 수 있습니다.
describe('Greetings functionality', () => {
it('should navigate to greetings page and confirm everything is there', () => {
cy.visit('http://localhost:3000')
cy.get('#greeting-nav-button').click()
cy.get('#greetings-input').type('Evgeny', { delay: 400 })
cy.get('#greetings-show-button').click()
cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')
// Percy test
cy.percySnapshot() // HIGHLIGHT
})
})
E2E 테스트 마지막에 추가한 것은 단 한 줄입니다. 'cy.perchSnapshot()'. 이것은 스크린샷을 찍어 Perch에 보내 비교하게 합니다. 테스트가 끝나면, 우리는 regression을 확인할 수 있는 링크를 받게 됩니다.
터미널 결과창:
Percy 결과 링크:
성능 테스트(Performance testing)
- 난이도: 높음
- 범위: 애플리케이션의 성능 및 안정성 검증
- 사용 가능 툴: Lighthouse, PageSpeen Insights, WebPageTest, YSlow
성능 테스트는 애플리케이션의 속도를 측정하는데 유용합니다. 당신의 사업에서 속도가 중요하다면, 특히 Core Web Vitals와 SEO면에서, 코드 베이스의 변경이 애플리케이션의 속도에 나쁜 영향을 미치는지 알고 싶을 것입니다.
우리는 이것을 나머지 테스트 흐름에 추가하거나 수동으로 실행할 수 있습니다. 테스트를 어떻게 실행하고 얼마나 자주 실행할지는 전적으로 당신에게 달려 있습니다. 일부 개발자들은 "성능 예산"이라고 불리는 것을 만들어 앱의 크기를 계산하는 테스트를 실행합니다. 크기가 특정 임계값을 넘어 테스트가 실패하면 배포되지 않습니다. 또는 Lighthouse를 사용하여 수동으로 자주 테스트합니다. 성능 측정 매트릭들을 측정하기도 합니다. 아니면 이 둘을 결합하여 테스트에 Lighthouse를 넣어 구축합니다.
성능 테스트는 성능과 관련한 것은 무엇이든 측정할 수 있습니다. 애플리케이션이 로드되는 속도, 초기 번들 크기, 심지어 특정 함수의 속도까지 측정할 수 있습니다. 성능 테스트는 다소 광범위하고 방대합니다.
여기 Lighthouse를 이용한 간단한 테스트가 있습니다. 어떠한 설치나 구성 없이 크롬 개발자 도구로 쉽게 접근할 수 있을 뿐만 아니라 Core Web Vitals에 초점을 맞추고 있기 때문에 아주 좋다고 생각합니다.
마무리
종류 | 난이도 | 범위 | 라이브러리/프레임워크 |
유닛 | 낮음 | 애플리케이션의 기능과 함수 검증 | AVA, Jasmine, Jest, Karma, Mocha |
통합 | 보통 | 유닛 간의 통합 검증 | AVA, Jest, Testing Library |
End-to-end | 높음 | 수행할 작업 및 예상 결과를 바탕으로 실제 브라우저에서의 사용자 상호 작용 검증 | Cypress, Puppeteer |
접근성 | 높음 | 접근성 표준에 따라 애플리케이션 인터페이스 검증 | AccessLint, axe-core, Lighthouse, pa11y |
Visual regression | 높음 | 코드 변경으로 인한 시각적 차이를 포함한 애플리케이션의 시각적 구조 검증 | Applitools, Cypress, Percy |
성능 | 높음 | 애플리케이션의 성능 및 안정성 검증 | Lighthouse, PageSpeed Insights, WebPageTesst, YSlow |
그래서 테스트는 모두를 위한 것일까요? 그렇습니다! 우리가 테스트해야 하는 다양한 측면에서, 사용 가능한 모든 라이브러리, 서비스, 툴들을 고려하면, 적어도 우리가 표준과 기대 결과를 기반으로 코드를 테스트하고 측정할 수 있게 하는 무언가가 있습니다. 일부는 심지어 코드나 구성도 필요하지 않습니다.
제 경험으로 볼 때, 많은 개발자들이 테스트를 소홀히 하고 간단한 클릭이나 개발 이후 확인하는 것이 코드 수정으로 발생할 수 있는 버그를 잡는 것에 도움이 될 것이라고 생각한다. 만약 당신의 애플리케이션이 당신이 기대한 대로 잘 동작하고, 가능한 많은 사용자를 포괄하며, 효율적으로 실행되는 잘 설계된 애플리케이션이길 원한다면, 테스트는 자동이든 수동이든 워크플로우의 핵심 파트가 되어야 합니다.
이제 어떤 유형의 테스트가 있고 어떻게 동작하는지 알게 되었습니다. 업무에 어떻게 적용해 볼 건가요?