테스트 코드 작성에 대한 고찰
개요
요즘 프로젝트를 하면서, 주변사람들에게 이런 말을 듣고는 했다. "그런데, 테스트 코드는 작성 안 하세요?" 혹은 "TDD는 해?" 와 같은 말들. 나는 그동안 프로젝트를 완성하는 것에 초점을 맞추다 보니, 테스트 코드를 크게 염두에 두진 않았다. 그래서 지금 부터라도 소프트웨어 테스트에 대해 알아보고 추후에 프로젝트를 진행할 때 작성해보려고 한다.
소프트웨어 테스트란?
소프트웨어 테스트(software testing)는 주요 이해관계자들에게 시험 대상 제품 또는 서비스의 품질에 관한 정보를 제공하는 조사 과정이다. - 위키백과
즉, 소프트웨어가 의도한대로 동작한대로 동작하는지 테스트하는 행위를 일컫는다.
소프트웨어 테스트의 장점
옛날에는 사람들이 직접 테스트를 하고 또 테스트를 하는 팀이 별도로 있었지만, 지금은 개발자가 자동으로 테스트를 할 수 있는 환경이 조성되어있다고한다. 그래서 컴퓨터를 통해 자동으로 테스트함으로써 빠르고, 일관성 있는 테스트를 할 수 있다.
무엇보다도, "피드백은 빠른 주기로 개발 중에 받을 수 있다."는 점이다.
개발자가 테스트 코드를 작성하고, 수시로 정상 동작하는지 확인이 가능하면서 거의 실시간으로 피드백을 받고 수정해나갈 수 있다. 이는 테스트 코드 안에서 개발자가 작성한 코드가 제대로 동작한다는 확신을 주기도 하고, 생산성을 높일 수 있다.
소프트웨어 테스트의 종류
본인이 작성한 코드를 테스트 하고 싶은 범위, 복잡성에 따라 3종류로 나눌 수 있다.
Unit Test(유닛 테스트)
가장 작은 범위의 테스트로 크게 복잡하지 않다. 개별 함수나 메서드, 클래스 혹은 컴포넌트의 동작을 테스트 한다. 가장 적은 비용이 들기도하고, 간단하다는 이유로 가장 빈번하게 수행할 수 있는 테스트이다.
Integration Test(통합 테스트)
통함테스트는 두 개 이상의 모듈이 결합해서 잘 동작하는지를 확인하고 검증하는 테스트 이다. 예를 들자면, 컴포넌트 안에서 렌더링이 정상적으로 되는지만을 확인한다면 Unit Test이지만, 이 컴포넌트가 Recoil 혹은 Zustand 등의 상태관리 라이브러리와 통합되었을 때 잘 어우러져서 우리가 원하는 결과를 내는지 테스트 하는 것. 이렇게 모듈을 통합하는 과정이 필요하기 때문에 유닛 테스트에 비해 비용이 많이 든다.
End-to-End Test :: E2E Test(E2E 테스트)
E2E 테스트는 실제 사용자가 웹/앱을 사용하는것과 굉장히 비슷한 환경을 구축한 후에 그 동작을 흉내 내어 테스트 하는 것이다. 사용자의 흐름을 비슷하게 테스트할 수 있지만, 환경을 구축하고 사용자 시나리오도 구축을 해야해서 비용이 많이들고 복잡성이 높다. 그렇기 때문에 빈번하게 수행하기 어렵고, 꼭 필요한 경우에만 실행하는 것이 일반적이다. 프론트엔드 같은 경우, 회원가입 전체 흐름을 테스트 한다고 했을 때, 실제 브라우저와 유사한 환경을 구축하고 그 안에서 여러 이벤트(값 입력, 인증 등)를 발생시켜 테스트를 한다.
프론트엔드 테스트 도구
프론트엔드에서 소프트웨어 테스트를 하기 위한 라이브러리들이 개발되어있다. 그 중 Jest, Mocha, Chai가 대표적이고, 특히 Jest는 한 주에 약 1800만회의 다운로드가 발생한다. 사실 Jest같은 경우는 CRA에서 기본적으로 포함해서 구성이 괴어있기 때문도 있고, 표준으로 사용되고 있다. 그래서 Jest를 사용하는 방법을 익히고 이를 활용한 몇 가지 예제를 보려고 한다.
Jest 사용법
우선 기본적으로 *.test.*의 형태 파일을 테스트 파일로 인식하고, 이 파일 속 코드를 실행한다. 일반적인 테스트 과정은
- 특정한 동작 수행
- 그 결과가 기대한 상황과 일치하는지 확인
이렇게 된다. 테스트 코드 작성도 마찬가지다.
Jest에서는 이렇게 판단하는 함수를 matchers라고 표현한다. 위 테스트 과정에 적용하면,
- 특정한 동작 수행
- matcher을 통해 기대한 값이 실제 값과 일치하는지 확인
이때, 특정 동작을 수행하기 위해 test() 혹은 it()함수를 활용한다. 아래 처럼 말이다.
// test(”테스트 이름", callback)
test('one plus three is four', () => {
// expect(실제 결과 값).matcher()
expect(1 + 3).toBe(4);
});
// it(”테스트 이름", callback)
it('one plus three is four', () => {
// expect(실제 결과 값).matcher()
expect(1 + 3).toBe(4);
});
만약 이 중 하나라도 기대값이 일치하지 않는다면, 이 테스트는 실패로 간주한다.
자주 사용하는 Jest의 matcher
- toBe
- expect의 인자가 toBe의 인자와 일치하는지를 검사한다.
- toEqaul
- Object의 경우 참조값이 다르기에, toBe를 활용할 경우 실제 각 객체의 내용이 같더라도, 일치하지 않다고 판단되게 된다. 따라서 객체를 상호 비교할 때는 toEqaul을 활용해야한다. toEqaul은 객체의 각 요소를 재귀적으로 검사하면서 두 객체가 동일한지 판단해준다.
- toBeNull, toBeUndefined
- toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeLessThanOrEqaul
- 숫자값을 검증할 때 유용하게 사용할 수 있는 matcher
- toContain
- Iterable한 객체들이 특정한 요소를 포함하고 있는지 검증할 때 사용할 수 있다.
- not
- matcher의 기대값을 반대로 변경해준다.
이렇게 작은 단위에서 다양한 방법으로 테스트가 가능하다.
TDD에 대하여
테스트를 중심으로 개발하는 것이 중요해지고 있다. 이를 표현하는 용어가 TDD인 것이고. TDD는 Test-Driven-Development의 약자로,소프트웨어를 개발하는 여러 방법론 중 하나이다.
일반적인 개발 흐름은 코드작성 → 테스트코드 작성의 흐름을 따르지만, TDD의 핵심은 기존에는 테스트 코드를 먼저 작성하고, 그 후에 실제 코드를 작성하는 것이다. 즉, TDD는 실제 코드를 작성하기도 전에 테스트 코드부터 작성을 시작한다.
TDD는 크게 Red-Green-Blue 3가지 단계를 거치게 된다.
- Red: 실제 구현을 하기 전에, 먼저 실패하는 테스트 코드를 작성한다. 그 후 테스트를 실행한다. 실제 코드가 작성되지 않았기에 테스트 코드는 당연히 실패한다.
- Green: 테스트를 통과하기 위해 가장 간단한 형태로 코드를 작성한다. 그 후 테스트를 실행한다. 테스트는 실제 구현이 되었기에 통과한다.
- Blue: Green 단계의 코드를 더 좋은 형태로 리팩터링한다. 이 과정에서 지속적으로 테스트를 실행해서 테스트가 통과하는지 확인한다.
이러한 형태로 개발하게 되면 여러 가지 장점이 있다.
- 코드의 동작이 명확해진다.
- 구현을 잘못했을 경우 바로 확인이 가능하다.
- 코드 작성 과정에서 확신을 얻을 수 있다.
위와 같은 장점 외에도 더 많은 이점이 있다.
결론
테스트 코드를 작성하는 것은 여러가지 측면으로 보았을때, 아주 좋은 점들이 많다. 다만, 초기에 테스트를 하기에는 그 비용이 들기 때문에 그 부분도 적절히 고려해가면서 테스트를 하는 것이 핵심인 것 같다. 그리고 리액트를 테스팅해주는 라이브러리가 있는 것도 알게되었는데, 이 부분은 추후에 테스트 코드를 작성하게 되었을 때 조금 더 자세히 이야기 해볼 생각이다.