안녕하세요. J4J입니다.
이번 포스팅은 jest query 우선순위 비교에 대해 적어보는 시간을 가져보려고 합니다.
관련 글
[React] Jest Query 사용 방법 비교, render vs screen
query 우선 순위 비교란?
jest를 이용하여 테스트 케이스 코드를 작성하다 보면 testing library를 활용하여 query를 사용해야 하는 경우가 빈번히 발생합니다.
그리고 query를 사용할 땐 getBy, findBy, queryBy 등과 같은 query type을 선택할 수 있고 이들을 활용하여 테스트할 때 필요한 element들을 호출해 볼 수 있습니다.
element들을 호출하는 방법에는 여러 가지가 있습니다.
예를 들면 class, id를 이용하거나 role, text 등의 값을 확인해보는 방법들이 있습니다.
이것들 중 특정 하나를 사용하는 것이 정답이라고는 말할 순 없지만 testing library 우선 순위와 같은 곳에서는 추천하는 우선순위가 존재합니다.
공식 문서에서도 나와있는 것처럼 단지 추천 하는 우선순위이기에 만약 지금 사용하고 계시는 방법이 우선순위가 높지 않다고 해서 잘못된 것은 아니라는 점 다시 한번 말씀드립니다.
공식 문서 우선 순위
testing library 가이드 원칙을 확인해 보면 testing library에서 추구하고자 하는 원칙에 대해 알 수 있습니다.
내용을 보시면 아시겠지만 testing library에서 말하고자 하는 것 중 하나는 사용자가 서비스를 실제 사용하는 것과 동일한 방식으로 테스트 코드를 구성해야 된다는 것으로 이해됩니다.
즉, user interaction에 대해 말하고 있으며 그에 따라 element들을 호출할 때도 user interaction과 관련되어 우선순위를 추천하고 있습니다.
공식 문서에서 추천하는 우선순위는 다음과 같습니다.
1. 모두가 접근할 수 있는 쿼리
- getByRole
- getByLabelText
- getByPlaceholderText
- getByText
- getByDisplayValue
2. 시맨틱 쿼리
- getByAltText
- getByTitle
3. 테스트 아이디
- getByTestId
각각의 호출 방식은 어떻게 활용될 수 있는지 하나씩 살펴보도록 하겠습니다.
추천하지 않는 방법
가장 먼저 추천하지 않는 방법에 대해 얘기해 보겠습니다.
해당 방법은 지극히 주관적인 생각이며 공식 문서에서도 따로 확인된 바는 아닙니다.
추천하지 않는 방법은 크게 3가지를 얘기해 볼 수 있습니다.
- class
- id
- tag
위의 방법들을 이용하여 element들을 조회하여 테스트 코드를 작성하는 것이 틀린 방법은 아니지만 개인적으로 추천드리는 것은 아닙니다.
먼저 class와 id 들은 테스트 코드 말고도 css 등과 같이 기능을 생성해 내는 부분과 밀접한 관련이 있습니다.
즉, 해당 값들은 빈번하게 값이 변경될 확률이 높으며 이런 작업들은 테스트와는 전혀 관련 없이 변경 사항이 발생되더라도 테스트 코드 쪽도 같이 수정해야 되는 문제를 발생시킬 수 있습니다.
테스트 코드가 1~2개가 작성될 것도 아니고 개발된 소스 코드만큼 많은 양의 테스트가 필요할 텐데, 단순 변경에 의해 많은 개수의 테스트 코드를 건드리게 하는 행동들은 최대한 발생시키지 않는 것이 좋다고 생각합니다.
그리고 tag 같은 경우는 하나의 페이지에서 사용되는 tag가 여러 개가 존재할 경우 특정하기가 어려워집니다.
특정 값을 억지로 찾아냈다고 하더라도 해당 방법은 단순히 코드 위치만 변경하더라도 테스트 코드를 다시 수정해야 되는 문제가 발생되기 때문에 좋지 않은 것으로 생각됩니다.
마지막으로 query를 사용하는 방법 중 하나인 screen 함수를 이용할 때 class, id, tag로 정의된 element들을 직접적으로 조회할 수 있는 기능이 없습니다.
render 함수도 반환해 주는 container 값만을 이용하여 테스트를 구성할 수 있기 때문에 이 정도면 testing library에서도 이 방법들을 사용하지 말라고 돌려 말하는 것이 아닌가 생각됩니다.
추천하지 않는 방법들로 테스트 코드를 구성하게 된다면 다음과 같이 사용될 수 있습니다.
import { render } from '@testing-library/react';
describe('not recommend test', () => {
test('use className', () => {
const { container } = render(
<div>
<h2 className="subject">이번 주제는 className의 사용을 추천하지 않는 것입니다.</h2>
</div>,
);
expect(container.querySelector('.subject')).toBeInTheDocument();
});
test('use id', () => {
const { container } = render(
<div>
<h2 id="subject">이번 주제는 id의 사용을 추천하지 않는 것입니다.</h2>
</div>,
);
expect(container.querySelector('#subject')).toBeInTheDocument();
});
test('use tag', () => {
const { container } = render(
<div>
<h2>이번 주제는 tag의 사용을 추천하지 않는 것입니다.</h2>
</div>,
);
expect(container.querySelector('h2')).toBeInTheDocument();
});
});
getByRole
getByRole은 role을 활용하여 element들을 조회하는 방식입니다.
role은 구조 및 동작에 대한 이해를 돕기 위해 사용되는 속성이며 여러 가지 html markup 들에 대해 기본적인 role이 정의되어 있습니다.
예를 들면 <button> 같은 경우는 button이라는 role이 기본적으로 정의되어 있고, <input type='checkbox'> 같은 경우는 checkbox라는 role이 기본적으로 정의되어 있습니다.
만약 role에 대해 더 궁금하신 분들은 MDN Web Docs를 확인해 보셔도 좋을 것 같습니다.
그리고 role은 기본적으로 정의되어 있는 값이 있기는 하지만 개발자가 임의로 role 값을 부여할 수도 있습니다.
또한 동일한 이름의 role이 존재할 경우 name 값을 추가로 활용하여 특정 값을 분류해 볼 수도 있습니다.
해당 내용들을 기반으로 하여 role을 활용한 테스트 코드는 다음과 같이 구성될 수 있습니다.
import { render, screen } from '@testing-library/react';
describe('getByRole test', () => {
test('default test', () => {
render(
<div>
<button>button</button>
</div>,
);
expect(screen.getByRole('button')).toBeInTheDocument();
});
test('name option use test', () => {
render(
<div>
<button name="submit">submit</button>
<button name="cancle">cancle</button>
</div>,
);
expect(
screen.getByRole('button', {
name: 'submit',
}),
).toBeInTheDocument();
expect(
screen.getByRole('button', {
name: 'cancle',
}),
).toBeInTheDocument();
});
test('custom role definition test', () => {
render(<div role="wrapper"></div>);
expect(screen.getByRole('wrapper')).toBeInTheDocument();
});
});
getByLabelText
getByLabelText는 <label>을 활용하여 element를 조회하는 방법입니다.
해당 방법에서 알아야 될 점은 특정 text 값을 가지고 있는 label과 연결된 element를 찾도록 도와줍니다.
즉, text 값을 가지고 있는 label을 리턴해주지 않기 때문에 처음 사용하실 때 헷갈리실 수 있습니다.
또한 반대로 label과 연결된 element가 존재하지 않다면 테스트가 올바르게 동작되지 않습니다.
getByLabelText를 활용하여 테스트 코드를 구성해 보면 다음과 같이 작성할 수 있습니다.
import { render, screen } from '@testing-library/react';
describe('getByLabelText test', () => {
test('default test', () => {
render(
<div>
<label htmlFor="my-checkbox">
<input type="checkbox" id="my-checkbox" />
my label
<button>cancle</button>
</label>
</div>,
);
expect(screen.getByLabelText('my label')).toBeInTheDocument();
expect(screen.getByLabelText('my label').getAttribute('type')).toEqual('checkbox');
});
test('only use label test', () => {
render(
<div>
<label>my label</label>
</div>,
);
expect(screen.getByLabelText('my label')).toBeInTheDocument(); // Fail !!! (Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly.)
});
});
getByPlaceholderText
getByPlaceholderText는 placeholder에 담겨있는 text 값을 활용하여 element를 조회합니다.
input, textarea와 같이 placeholder를 주로 활용하여 element들을 조회할 때 자주 사용될 수 있습니다.
getByPlaceholderText를 활용하여 테스트 코드를 구성하면 다음과 같이 작성할 수 있습니다.
import { render, screen } from '@testing-library/react';
describe('getByPlaceholderText test', () => {
test('default test', () => {
render(
<div>
<input type="text" placeholder="my-placeholder" />
</div>,
);
expect(screen.getByPlaceholderText('my-placeholder')).toBeInTheDocument();
expect(screen.getByPlaceholderText('my-placeholder').getAttribute('type')).toEqual('text');
});
});
getByText
getByText는 text content 값을 이용하여 element를 조회합니다.
heading, span, paragraph 등에서 활용될 수 있는 방식입니다.
getByText로 테스트 코드를 다음과 같이 구성해 볼 수 있습니다.
import { render, screen } from '@testing-library/react';
describe('getByText test', () => {
test('default test', () => {
render(
<div>
<h2>my text</h2>
</div>,
);
expect(screen.getByText('my text')).toBeInTheDocument();
});
});
getByDisplayValue
getByDisplayValue는 value값을 활용하여 element를 조회합니다.
input, textarea 등 value 값을 이용하는 곳에서 사용될 수 있습니다.
getByDisplayValue로 구성된 테스트 코드는 다음과 같습니다.
import { render, screen } from '@testing-library/react';
describe('getByDisplayValue test', () => {
test('default test', () => {
render(
<div>
<input type="text" value="test-value" />
</div>,
);
expect(screen.getByDisplayValue('test-value')).toBeInTheDocument();
});
});
getByAltText
getByAltText는 alt 값을 이용하여 element를 조회합니다.
alt 값은 주로 image를 불러올 때 많이 활용될 텐데 해당 부분에서 사용해 볼 수 있습니다.
getByAltText로 테스트 코드를 구성하면 다음과 같습니다.
import { render, screen } from '@testing-library/react';
describe('getByAltText test', () => {
test('default test', () => {
render(
<div>
<img src="/assets/react.svg" alt="react-image" />
</div>,
);
expect(screen.getByAltText('react-image')).toBeInTheDocument();
});
});
getByTitle
getByTitle은 title 값을 활용하여 element를 조회합니다.
여기서 말하는 title은 head에 작성되는 <title>이 아니라 element들의 속성 값 중 하나로 tooltip의 목적으로도 활용될 수 있습니다.
getByTitle의 테스트 코드는 다음과 같이 구성됩니다.
import { render, screen } from '@testing-library/react';
describe('getByTitle test', () => {
test('default test', () => {
render(
<div>
<h2 title="fruit">apple</h2>
</div>,
);
expect(screen.getByTitle('fruit')).toBeInTheDocument();
});
});
getByTestId
getByTestId는 user interaction과 전혀 관련 없는 새로운 속성 값으로 단순히 테스트를 위한 목적으로써만 활용된다고 말할 수 있습니다.
그러다 보니 testing library 원칙에 가장 멀리 있는 속성 값으로 그에 따라 공식 문서에서도 가장 낮은 순위를 정해놨습니다.
getByTestId를 활용하여 테스트 코드를 구성하면 다음과 같습니다.
import { render, screen } from '@testing-library/react';
describe('getByTestId test', () => {
test('default test', () => {
render(
<div>
<input data-testid="my-input" type="text" />
</div>,
);
expect(screen.getByTestId('my-input')).toBeInTheDocument();
expect(screen.getByTestId('my-input').getAttribute('type')).toEqual('text');
});
});
정리
공식 문서에서 가장 추천하는 방법은 user interaction을 가장 높일 수 있는 getByRole을 활용하는 것으로 보이고, 가장 추천하지 않는 방법은 getByTestId를 활용하는 것으로 보입니다.
사실 개인적으로 getByTestId를 가장 많이 활용하고 있기는 합니다.
왜냐하면 위에서도 한번 언급했지만 기능적인 부분을 개발하는 것과 테스트 코드를 완전하게 독립시키기 위함입니다.
getByRole을 사용한다고 해서 테스트 코드 작성이 크게 달라지진 않을 것으로 생각하지만 getByTestId와 같이 완전한 독립은 불가능하다고 판단됩니다.
그래서 테스트와 관련 없는 기능 변경이 발생될 때 테스트 쪽에도 영향을 줄 수 있다고 보입니다.
하지만 공식 문서에서는 테스트 코드가 user interaction과 독립된다는 저와 비슷한 생각으로 인해 우선순위를 가장 낮게 주고 있습니다.
그렇다고 해서 제가 틀린 방법으로 개발하고 있다고 생각하진 않고 서비스를 개발하고 있는 팀의 성향에 맞게 테스트 방식을 정한다면 이것이 정답이라고 생각됩니다.
마지막으로 이런 의견들을 종합하여 테스트 코드를 작성하기 위해 고민하고 계시는 분들에게 개인적으로 추천드리는 우선순위는 getByTestId, getByRole 이며 나머지 기능들은 컨벤션이 복잡해질 수 있기 때문에 최대한 사용하지 않았으면 하는 바람입니다.
이상으로 jest query 우선 순위 비교에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'SPA > React' 카테고리의 다른 글
[React] Jest로 테스트 커버리지 확인하기 (2) - 커버리지 비율 한계점 (4) | 2023.11.20 |
---|---|
[React] Jest로 테스트 커버리지 확인하기 (1) - 개념과 설정 방법 (2) | 2023.11.16 |
[React] Jest Query 사용 방법 비교, render vs screen (2) | 2023.11.06 |
[React] Jest 이벤트 처리 비교, fireEvent vs userEvent (2) | 2023.11.02 |
[React] Vite 타입 스크립트 프로젝트에 ESLint / Prettier 설정하기 (2) | 2023.10.31 |
댓글