테스트와 테스트 하는 방법에 대해 알아보자.
Jest 도구를 이용해서 여러 가지 테스트를 해보자. 기본적인 단위 테스트를 하는 방법과 TDD를 할 수 있도록 테스트를 먼저 써서 진행하는 방법을 알아보자.
구현보다 테스트 코드를 먼저 작성하는 방식이다.
즉, 인터페이스
와 스펙
을 먼저 정의하고 개발을 진행한다.
인터페이스와 스팩을 먼저 정의함으로써 막연하게 시작하지 않을 수 있다.
여기에서 말하는 인터페이스는 타입스크립트에서 쓰는 시그니처와 같은 의미이다.
예를 들어서,add(x, y) => number
이렇게 쓸 수 있다.그리고 스펙을
x, y 를 넣으면 둘이 더해진다.
로 해석할 수 있다.
정확하게는 시그니처를 모아놓은게 인터페이스이다.
테스트 코드를 작성한다고 해서 TDD 가 아니다.
TDD Cycle 과정을 엄격하게 지켜서 개발을 진행해야 TDD라고 할 수 있다.
TDD Cycle은 테스트 코드를 먼저 작성하고, 구현하고, 리팩터링 하는 과정이다.
목표까지 TDD Cycle을 계속해서 반복해야 한다.
실패하는 테스트 코드를 작성한다.
인터페이스와 스펙을 어떻게 하면 잘 쓸지에 (테스트 코드를 어떻게 하면 잘 쓸지에) 집중한다.
재빨리 테스트를 통과시킨다.
브루트포스를 써도 상관없으니 재빨리 통과시킨다. 올바른 방법이 아니어도 괜찮다.
TDD 창시자 캔트 백은 빨리 통과시키기 위해 죄악을 저릴러도 괜찮다고 할 정도이다.
리팩터링을 통해 코드를 올바르게 만든다. 재빨리 만들었던 코드를 고치는 것이다.
STEP3: Refactor는 TDD에서 가장 중요한 부분이지만, 간과될 때가 많다.
TDD가 목표로 하는 것은 제대로 동작하는 클린코드
이다.
이를 위해서 제대로 작동해야 하고(STEP2), 클린코드로 나아갈 수 있게 리펙터링(STEP3)을 해야 한다.
그러니 처음부터 완벽한 해법을 내려고 하지 말자. 굉장히 어렵다.
일단 통과하는 테스트를 만들고(STEP2) 리펙터링(STEP3)을 통해 조금씩 바꾸자.
리펙터링이 지향하는 바는 동작은 바뀌지 않고 설계만 바꾸는 것이다.
즉, 어떻게 작동하는지에 대한 스펙은 바뀌지 않고 내부에 구현되어 있는 코드만 바꾼다.
실제로 마틴 파울러의 책에서 나오는 단위 테스트를 리펙터링 하는 부분을 보면, 테스트가 터지지 않은 선에서 고친다.
하지만 설계를 바꾸다 보면 동작을 바꿀 때가 있다. 괜찮다. 테스트가 깨지니 STEP1으로 돌아간 것이다. 다시 잘 고치면 된다.
작은 단계는 1분 이하로 가져올 수 있으면 BEST이다. 적어도 10분 안에 한 사이클이 돌아야 한다.
그래야 작은 단계를 여러 번 진행할 수 있다.
작은 단계를 여러 번 진행하면 좋은 점이 있다. 각 단계 속에서 피드백을 얻기 때문이다.
그래서 큰 단위로 일을 하면 안되는 일이 생겼을 때 일주일 동안 붙잡고 낑낑되게 되는 것이다.
STEP2 를 어떻게 해도 통과시키지 못하고 있다면 STEP1 에서 잡은 단위가 큰 것이다.
STEP1 에서 더 작고 더 쉬운 문제로 정의해야 한다.
중복을 제거를 하는 연습을 해야 한다.
코드는 어떤 의도를 가지고 작성하게 된다. 그러면 의도가 이곳저곳에 있게 된다. 그 중에서 중복되는 의도를 찾아서 처리하는 것이다.
클린 코드를 만들기 위해서 필수이다.
작은 단위의 문제를 만드는 것, 중복을 찾아서 제거하는 것. 개발을 잘하려면 당연히 갖춰야 하는 조건이다.
TDD 만의 문제가 아니다.
Jest 라는 도구를 이용해 TDD를 연습해보자.
Jest는 페이스북에서 만든 테스팅 도구이다. 쓸 만한 것을 모두 모아놓았고 단순하다.
Jest
는모카
라는 테스팅 도구의 영향을 받았고,
모카
는 루비로 된알스펙
이라는 테스팅 도구의 영향을 받았다.그래서
Jest
로 테스트 코드를 작성할 때알스펙
으로 작성한 코드를 참고해도 도움이 된다.예를 들어
Describe - Context - It
는알스펙
에서 쓰는 패턴이다.
Jest
에서는Context
를 기본적으로 제공하지 않지만 이 패턴을 사용한다.
test('add', () => {
expect(add(1, 2)).toBe(3);
});
주체(어떤 것을 테스트할지) 그리고 행위(어떻게 될지) 에 대해 드러낼 수 있다.
describe('add', () => {
it('add는 ~ 한다', () => {
expect(add(1, 2)).toBe(3);
});
});
describe('add', () => {
it('returns sum of two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
1번은 단순해서 좋다. 그래서 간단한 테스트를 할 때 사용한다.
Describe - Context - It
패턴으로const context = describe;
describe('add', () => {
context('with no argument', () => {
it('returns zero', () => {
expect(add()).toBe(0);
});
});
context('with only one number', () => {
it('returns the same number', () => {
expect(add(1)).toBe(1);
});
});
context('with two numbers', () => {
it('returns sum of two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
context('with three numbers', () => {
it('returns sum of three numbers', () => {
expect(add(1, 2, 3)).toBe(6);
});
});
});
위에서도 언급했지만
Describe - Context - It
는알스펙
에서 쓰는 패턴이다. 그래서 context는 지원하지 않는다. describe를 이용해서 만들어줘야 한다.
Jest에서 타입스크립트를 사용하도록 jest.config.js 파일 생성하고 작성한다.
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', {
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
}],
},
};
참고로
^.+\\.(t|j)sx?$
에서x?
는 x는 없어도 된다는 뜻이다.
그래서 ts(x) 과 js(x) 파일에 대해서 swc/jest가 변환을 해준다.swc는 타입스크립트 코드를 자바스크립트로 변환하기 위해서 jest와 함께 쓰고 있다.
jest는 기본적으로 타입스크립트를 지원하지 않는다.
https://github.com/ahastudio/til/blob/main/blog/2016/12-03-tdd-faq.md
바닥부터 만들기
https://github.com/ahastudio/til/blob/main/jest/20201204-simple-tdd-example.md
중복을 계속 만든다
그 다음에 패턴(중복)을 찾고 정리한다.
알고리즘 문제를 풀 때도 테스트를 작성하면 리펙터링이 쉬울 것 같다.
동작을 바꾸다가 설계도 바꾸는 실수를 줄일 수 있으니까.
React Testing Library 에 대해 알아보자.
좋은 정보 얻어갑니다, 감사합니다.