테스트란?
테스트 시작하기
테스트란?
- 프로그래밍에서 테스트는 소프트웨어 개발 과정의 중요한 부분으로, 작성된 코드가 의도한 대로 정확하게 작동하는지 확인하는 과정을 말한다.
테스트를 사용하는 이유
- 테스트를 통한 버그 찾기
- 소트프웨어의 품질을 향상
- 사용자에게 안정적인 제품을 제공
테스트의 장점
1) 사려깊은 설계가 가능해진다.
- 새로 작성한 코드의 테스트를 작성하는 일은 해당 코드의 API가 잘 설계 되었는지를 시험하는 행위이다.
- 구현보다 인터페이스에 집중하게 된다는 장점을 가지고 있다.
- 코딩이 완료되는 시점을 정확하게 판단할 수 있어 좋은 설계를 이끌어낼 수 있다.
- 코드를 작성하기 전에 테스트를 작성하는것은 테스트를 작성하다보면 원하는 기능을 추가하기 위해 무엇이 필요한지 고민하게 된다는 장점을 가진다고 마틴 파울러는 언급하였다.
2) 자신 있게 변경할 수 있으며 디버깅 시간이 줄어들고 빠르게 배포할 수 있게 된다.
마틴파울러의 '리팩토링'
아무리 간단한 수정이라도 리팩터링 후에는 항상 테스트하는 습관을 들이는 것이 바람직하다. 사람은 실수하기 마련이다. 적어도 내가 겪은 바로는 그렇다. 한 가지를 수정할 때마다 테스트 하면, 오류가 생기더라도 변경 폭이 작기 떄문에 살펴볼 범위도 좁아서 문제를 찾고 해결하기가 훨씬 쉽다. 이처럼 조금씩 변경하고 매번 테스트하는 것은 리팩터링 절차에 핵심이다. 한 번에 너무 많이 수정하려다 실수를 저지르면 디버깅하기 어려워서 결과적으로 작업 시간이 늘어난다. 조금씩 수정하여 피드백 주기를 짧게 가져가는 습관이 이러한 재앙을 피하는 길이다.
'구글 엔지니어는 이렇게 일한다'
테스트를 거친 후 서브밋 되는 코드는 통상적으로 결함이 적다.여기서 중요한 사실은 결함 대부분이 서브밋 전에 고쳐지기 때문에 그 코드의 존속 기간 전체로 봤을 때 결함이 줄어든다.
3) 좋은 문서자료가 된다.
- 한 번에 하나의 행위만 집중해서 검증하는 명확한 테스트는 문서와 동일한 역할을 한다.
- 해당 테스트를 읽음으로써 문서를 읽는 것처럼 해당 기능의 동작을 명확하게 이해할 수 있게 된다.
4) 작동 하는지 검증하는 시간을 줄여준다.
- 정확성, 극단 상황, 오류 상황 등 다양한 측면에서 코드를 검사해주는 테스트가 준비되어 있다면 리뷰 단계에서 해당 시간을 아낄 수 있다.
올바른 테스트 작성을 위한 규칙
1) 인터페이스를 기준으로 테스트를 작성하자.
- 여기서 말하는 인터페이스는 주로 서로 다른 클래스 또는 모듈이 상호작용 하는 시스템을 의미한다. 즉, 모든 테스트는 세부 구현을 테스트하는 것 대신 외부에 노출되는 public 메서드를 기준으로 작성해야한다.
- 캡슐화된 내부 구현에 대한 테스트 코드는 수많은 테스트 코드를 양산할 뿐만 아니라 내부 구현과의 강한 의존성 때문에 구현이 조금만 변경되어도 깨지기 쉽고, 유지 보수하기도 굉장히 어렵다.
- 컴포넌트에 대한 단위/통합 테스트의 경우 구현 세부 명세를 테스트하기 보다는, UI 구성 요소를 사용자가 사용하는 방식으로 테스트하는 게 좋다. 사용자가 앱을 사용하는 방식과 테스트가 유사할수록 테스트이 신뢰성을 향상된다.
2) 커버리지보다는 의미있는 테스트인지 고민하자.
- 간단한 유틸 함수들은 변경 가능성도 거의 없으며, 내부 로직도 너무 간단하기 때문에 억지로 버그 상황을 만들지 않으면 문제가 될 가능성이 제로에 가깝다.
- 아주 간단한 함수는 과감하게 테스트를 생략하는 것이 좋다. 오히려 이런 함수들이 다른 모듈이나 컴포넌트의 로직에 포함되어있을때 한번에 검증하는 것이 더 효율적이다.
3) 테스트 코드도 가독성을 높여야 한다.
- 잘 작성된 테스트 코드는 문서와도 같다. 명확한 테스트 디스크립션이 있다면 테스트 파일만보고 앱이 어떻게 동작하는지 파악하기 좋다.
4) 하나의 테스트에서는 가급적 하나의 동작만 검증하는 것이 좋다.
- 다양한 컴포넌트들이 조합되었을 때의 시나리오를 검증해야 한다면 하나의 테스트에서 한번에 검증하는 것이 아니라 여러 개로 나누어 검증하는 것이 가독성과 유지 보수에 좋다.
테스트 작성하기
테스트를 작성하는 방법으로는 일반적으로 AAA(Arrange-Act-Assert) 패턴 또는 GWT(Given-When-Then) 패턴을 따르곤 한다. 각 테스트 패턴의 큰 패턴은 아래와 같다.
1. 테스트를 위한 환경 만들기
2. 테스트 동작 재현
3. 올바른 동작이 실행되었는지 또는 변경사항을 검증하기
예시
TextField 컴포넌트의 className prop 에 설정된 css class가 텍스트 필드에 적용되었는가를 검증하는 테스트 작성
it('className prop으로 설정한 css class가 적용된다.', () => {
// 테스트를 위한 환경 만들기
// -> className을 지닌 컴포넌트 렌더링
await render(<TextField className="my-class" />);
// Act - 테스트할 동작 발생
// -> 렌더링에 대한 검증이기 때문에 이 단계는 생략
// -> 클릭이나 메서드 호출, prop 변경 등등에 대한 작업이 여기에 해당
// Assert - 올바른 동작이 실행되었는지 검증
// -> 렌더링 후 DOM에 해당 class가 존재하는지 검증
expect(screen.getByPlaceholderText('텍스트를 입력해 주세요.')).toHaveClass('my-class');
});