testing-library-philosophy
Q&A 정리: testing-library-philosophy
컴포넌트 테스트에서 implementation details를 테스트하면 어떤 문제가 생기는가? Testing Library는 이 문제를 어떻게 해결하는가?
내부 구현 방식을 테스트하면, 기능은 그대로인데 코드만 정리해도 테스트가 깨져서 개발 속도가 느려진다. Testing Library는 실제 사용자가 화면을 조작하는 방식(텍스트로 버튼 찾기 등)으로만 테스트하도록 유도해서, 기능이 정상이면 테스트도 통과하게 만든다.
You want to write maintainable tests that give you high confidence that your components are working for your users. As a part of this goal, you want your tests to avoid including implementation details so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.
The core library, DOM Testing Library, is a light-weight solution for testing web pages by querying and interacting with DOM nodes (whether simulated with JSDOM/Jest or in the browser). The main utilities it provides involve querying the DOM for nodes in a way that's similar to how the user finds elements on the page. In this way, the library helps ensure your tests give you confidence that your application will work when a real user uses it.
The more your tests resemble the way your software is used, the more confidence they can give you.
Testing Library는 implementation details 테스트를 기술적으로 차단하는가?
기술적으로 완전히 막지는 않지만, 내부 구현을 테스트하기 어렵게 API를 설계하여 자연스럽게 좋은 테스트 습관을 유도한다.
Testing Library encourages you to avoid testing implementation details like the internals of a component you're testing (though it's still possible).
Testing Library의 Guiding Principles는 "사용자처럼 테스트하라"는 원칙을 API 설계에 어떻게 반영하는가?
컴포넌트 내부 인스턴스가 아닌 DOM 노드를 다루고, 사용자가 실제로 화면을 쓰는 방식에 가깝도록 유틸리티를 설계한다. API는 단순하고 유연하게 유지하되, 사용자 관점 테스트를 자연스럽게 유도하는 방향으로만 기능을 제공한다.
We try to only expose methods and utilities that encourage you to write tests that closely resemble how your web pages are used.
Utilities are included in this project based on the following guiding principles:
If it relates to rendering components, then it should deal with DOM nodes rather than component instances, and it should not encourage dealing with component instances. It should be generally useful for testing the application components in the way the user would use it. We are making some trade-offs here because we're using a computer and often a simulated browser environment, but in general, utilities should encourage tests that use the components the way they're intended to be used. Utility implementations and APIs should be simple and flexible.
React Testing Library로 테스트를 작성하면 접근성(a11y)이 자연스럽게 개선된다고 하는데, 어떤 원리인가?
Testing Library는 라벨 텍스트로 폼 요소를 찾고, 버튼 텍스트로 버튼을 찾는 방식을 권장한다. 이렇게 테스트를 작성하려면 자연스럽게 적절한 라벨과 의미 있는 텍스트를 코드에 넣게 되어, 화면 읽기 프로그램(스크린 리더) 사용자에게도 좋은 화면이 만들어진다.
The utilities this library provides facilitate querying the DOM in the same way the user would. Finding form elements by their label text (just like a user would), finding links and buttons from their text (like a user would). It also exposes a recommended way to find elements by a data-testid as an "escape hatch" for elements where the text content and label do not make sense or is not practical.
This library encourages your applications to be more accessible and allows you to get your tests closer to using your components the way a user will, which allows your tests to give you more confidence that your application will work when a real user uses it.
Enzyme으로도 사용자 관점 테스트를 작성할 수 있는데, Testing Library가 Enzyme을 대체한 이유는 무엇인가?
Enzyme도 사용자 관점 테스트를 작성할 수는 있지만, 내부 구현을 손쉽게 들여다보는 도구들이 너무 많아서 나쁜 습관에 빠지기 쉽다. Testing Library는 그런 도구 자체를 제공하지 않아 올바른 테스트 방식을 강제하기 수월하다.
This library is a replacement for Enzyme. While you can follow these guidelines using Enzyme itself, enforcing this is harder because of all the extra utilities that Enzyme provides (utilities which facilitate testing implementation details).
React Testing Library에서 컴포넌트 트리의 어느 레벨을 테스트해야 하나?
개별 작은 컴포넌트보다는, 사용자가 실제로 상호작용하는 단위(페이지나 큰 기능 블록)에서 테스트하는 것이 권장된다. 컴포넌트 트리 구조는 언제든 바뀔 수 있는 내부 구현이므로, 너무 낮은 레벨에서 테스트하면 리팩토링할 때마다 테스트를 고쳐야 한다.
Following the guiding principle of this library, it is useful to break down how tests are organized around how the user experiences and interacts with application functionality rather than around specific components themselves. In some cases, for example for reusable component libraries, it might be useful to include developers in the list of users to test for and test each of the reusable components individually. Other times, the specific break down of a component tree is just an implementation detail and testing every component within that tree individually can cause issues (see https://kentcdodds.com/blog/avoid-the-test-user).
In practice this means that it is often preferable to test high enough up the component tree to simulate realistic user interactions. The question of whether it is worth additionally testing at a higher or lower level on top of this comes down to a question of tradeoffs and what will provide enough value for the cost (see https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests on more info on different levels of testing).
Kent C. Dodds가 말하는 "test user"란 무엇이고, 왜 피해야 하나?
컴포넌트의 진짜 사용자는 화면을 쓰는 최종 사용자와 컴포넌트를 조합하는 개발자, 이 둘뿐이다. 내부 구현을 테스트하면 "테스트만을 위한 세 번째 사용자"가 생기는데, 이 사용자는 비용을 지불하지도, 시스템에 영향을 주지도 않으면서 유지보수 부담만 늘린다.
The two users your UI code has are 1) The end user that's interacting with your component and 2) the developer rendering your component.
These are the only two users that your component should be concerned with. This component can experience a lot of changes over time. If it makes changes that alter the developer's API or the end user's expectations, then additional changes need to be made. If it changes the API, (like maybe it accepts a user prop instead of accessing it from context) then the developer user will have to alter its usage to account for that. If it changes the user experience, then maybe there will need to be release notes explaining the updates, or some training material updated for example.
However, it can change in other ways too. Internal refactorings which change how things are implemented (for example, to make the code easier to follow), but don't change the experience of the developer using the component or the end user using it. With these kinds of changes, no additional work outside the component is needed.
So what does this have to do with testing? One thing that I talk about a lot is "The more your tests resemble the way your software is used, the more confidence they can give you." So knowing how your software is used is really valuable. It gives you a guide for knowing how to test the component.
But far too often, I see tests which are testing implementation details. When you do this, you introduce a third user. The developer user and the end user are really all that matters for this component. So long as it serves those two, then it has a reason to exist. And when you're maintaining the component you need to keep those two users in mind to make sure that if you break the contract with them, you do something to handle that change.
But as soon as you start testing things which your developer user and end user don't know or care about (implementation details), you add a third testing user, you're now having to keep that third user in your head and make sure you account for changes that affect the testing user as well.
And for what? To get "confidence?" But what are you getting confidence in when you test things this way? You're getting confidence that things work for the testing user. But nobody cares about the testing user. The testing user doesn't pay the bills like the end user. It doesn't affect the rest of the system like the developer user.
Writing tests that include implementation details is all downside and no upside. Focus on the developer user and the end user and your tests will actually give you confidence that things will continue to work for them. When your tests break it becomes a cue for you to know that you have other changes to make elsewhere to account for the changes you've made. Avoid testing implementation details and you'll be much better off.