이제 테스트코드의 필요성은 굳이 여기서 말을 하지 않아도 될 정도로 다들 잘 알고있고 그만큼 널리알려져 있다고 생각한다. 그래도 개인적인 경험을 말해보자면 난 처음부터 테스트코드를 작성하진 않았다. 사실 그땐 테스트코드에 대한 존재 자체도 잘 몰랐다.
첫 회사에선 코드를 작성하고 서비스를 운영 했던 방식은 다음과 같았다. (프론트, 백엔드 모두 작업하던 환경)
1. 신나게 어플리케이션 코드 작업
2. 손수 한땀한땀 클릭 클릭, 오고가는 데이터까지 확인하며 기능 작동 확인
3. 버그 발견시 1. 로 회귀. Or 스테이지 서버에 배포후 다시 한번 기능 테스트 (QA)
4. 배포
이렇게 일을 하며 느꼈던 점은 테스트 과정이 지루하기도 하고 기존 기능에 무언가 추가 되었을때 똑같은 테스트를 또 해야했기에 효율성에 대한 의문이 직감적으로 들었고 자동화에 대한 필요성을 느꼈다.
그리고 코드를 직접 작성한 개발자가 테스트 할 경우 기능 작동이 성공하는 케이스만 테스트 할 확률이 높아 테스트시 충분한 테스트 케이스 검증이 됐는지 불확실 하다.
결국 사람이 직접 수동으로 테스트 하기엔 날이 갈수록 서비스는 커지고 커버해야 될 코드의 양은 늘어난다. 이는 서비스의 안전성과 개발자가 작성한 코드에 대한 믿음 하락 이라는 결과를 야기한다.
결국 테스트의 궁극적인 목적은
- 서비스의 안정성 향상
- 개발자가 작성한 코드에 대한 믿음
더 나아가 테스트코드를 작성하다보면 어플리케이션 코드에 대한 피드백을 받을수 있고 이는 어플리케이션 코드 설계에 대한 수정이나 리팩토링으로 이어지는 선순환이 이루어질수도 있다.
- 테스트코드 또한 어플리케이션 코드 처럼 다른 동료들, 미래의 나를 위해 잘 작성해야 한다. (코드 파악과 유지보수가 용이하게끔~)
자 그럼 무엇을 어떻게 테스트 해야 할까?
테스트 종류
보편적으로 다음과 같이 3가지로 분류된다.
Unit test
유닛 테스트에 대해 위키백과에선 위와같이 설명하고 있다. 그리고 가장 작은 테스트 단위이기도 하다. 하나의 모듈에서 기능(메소드) 이 제대로 동작하는지 테스트 하는 것 이다.
보통 모듈화된 비즈니스 로직을 테스트할때 작성하는 코드 단위이다. 그리고 주의할 것이 테스트 커버리지가 무조건 높다고 좋은것은 아니다. 모든 코드에 대하여 테스트를 한다는건 그만큼 개발 생산성이 낮아진다는 의미이기도 하고 이것이 애플리케이션의 버그를 사전에 예방하고 줄여준다는 지표가 아니기 때문이다.
그리고 애플리케이션 또한 서비스 운영을 하며 계속해서 변화한다. 어플리케이션 코드의 변경은 곧 테스트코드의 변경과도 이어지기에 잘못 작성된 테스트코드나 너무 많은 테스트코드는 리팩토링시 걸림돌이 될수도 있다.
결국 중요한 코드를 테스트해야 하는데 유닛테스트에선 무엇을 테스트 해야 하는 걸까? 아래의 기준을 참고하자.
- “애플리케이션이 해결하려는 문제를 이 코드가 직접적으로 다루고 있는가?”
- “애플리케이션이 해결하려는 문제와 이 코드는 관련성이 높은가?”
- “애플리케이션이 정상적인 동작을 하기 위해서 필요한 정책, 규칙, 설정을 이 코드가 조작, 제어, 생성, 수정, 삭제하고 있는가?”
- “기획자 혹은 이해관계자의 대화에서 나온 개념 혹은 용어가 이 코드에 들어가 있는가?”
- “이 코드가 실제 서비스에 적용된다면 매출 상승, 보안 강화, 사용자 증가 등 실질적인 비즈니스 가치에 도움이 되는가?”
- “이 코드가 다른 곳에 없는 이 애플리케이션만의 차별화된 부분을 다루고 있는가?”
“그렇다”라고 대답한 개수가 많을수록 중요한 코드일 가능성이 높다.
Integration test (Functional test)
Integration test 와 Functional test 에 대한 차이가 무엇인지 궁금했는데 정의는 다음과 같다.
Integration test
Functional test
내가 이해한 바론 Integration test 의 경우 여러 모듈을 호출하여 기능이 작성된 집합체를 테스트하는것으로 이해했다.
ex) Controller 에서 여러 서비스 비즈니스 모듈을 호출하는 메소드가 정상 작동하는지 테스트.
그리고 Functional test 의 경우 API 를 호출했을때 예상대로 정상 동작하였는지, 응답값을 제대로 내려주었는지 등에 대한 테스트로 이해했다. 내부에서 어떻게 동작하는지(블랙박스)에 대한 관심보단 입력과 출력에 대한 값을 테스트 하는데 초점을 둔 것으로 보인다.
여러 글을 읽어보며 느낀 것은 Integration test 를 Functional test 로 바라보는 시각도 많이 존재 하여 Integration test 를 Functional test 로 동일하게 바라보겠다. 그리고 라라벨의 경우 Unit Test, Feature Test (Functional test) 이렇게 둘만 존재하는걸로 보아 라라벨에선 Integration test 는 Functional test 로 대체하는것으로 보인다.
E2E test
글 시작에서 말했듯이 첫 회사에선 직접 클릭하고 확인하며 수동으로 하던 테스트가 대부분이었고 기능이 추가되거나 변경될때마다 똑같은 테스트를 반복하던 내가 필요로 했던건 E2E 테스트였다.
E2E 테스트는 보통 자동화(브라우저, 앱)를 통해 테스트 하는데 브라우저 자동화 테스트 툴들 중에서 가장 많이 쓰이고 유명한건 cypress, playwright 인것으로 보인다.
툴 사용시 크로스 브라우징, 브라우저 버전별 테스트 등 폭넓은 테스트의 자동화가 가능하며 병렬처리 또한 지원하여 상대적으로 다른 테스트보다 속도가 느린것을 보완할수 있기 때문에 적극적으로 사용하자.
이상으로 테스트코드의 종류에 대해 알아보았다.
Unit Test 의 경우 개발자의 관점에서 해당 모듈의 기능이 정상 작동하는지 중요기능들에 대해 초점이 맞춰져있다. 테스트 코드를 작성하며 모듈의 책임 분리와 적절한 Mock 을 통해 좋은 코드를 작성하도록 하자.
Integration Test 의 경우 사용자의 관점에서 내가 입력한 값에 대해 예상한 출력 값이 정상적으로 나왔는지에 대한 관심이 크다. 사용자 입장에선 내부에서 어떻게 작동하는지 관심이 없고 알수도 없다. 이때 말하는 사용자란 어플리케이션을 사용하는 일반 사용자가 아닌 해당 기능(API, etc..) 을 사용하는 사용자이다.
E2E 테스트는 실제 어플리케이션을 사용하는 일반 사용자의 관점에서 테스트 하는것으로 어플리케이션의 동작 테스트에 관심이 있고 브라우저, 앱 자동화를 통해 사용자 시나리오를 검증하고 크로스 브라우징 이슈, 버전 이슈등을 사전에 감지하고 대응할수 있다.
마지막으로 테스트코드를 작성하며 느낀점은 테스트코드를 짜더라도 버그를 완전히 예방하긴 힘들고 QA 나 운영중 나온 버그는 테스트코드로 추가해 좀더 안정적인 서비스를 구축하는데 활용하자.
참고자료
https://tech.inflab.com/20230404-test-code/
https://jojoldu.tistory.com/697
https://fe-developers.kakaoent.com/2023/230209-e2e/
https://stackoverflow.com/questions/55636697/unit-integration-or-feature-test
https://hyperconnect.github.io/2022/01/28/e2e-test-with-playwright.html