저번 포스팅에 테스트 코드란 무엇인가? 을 포스팅하였습니다. 그렇다면 이런 질문이 나올 수 있는데요. “테스트 코드는 뭔지 알겠는데 어떻게 하면 잘 작성할 수 있어?” 이 질문에 답이 될 수 있는 좋은 테스트 코드란 무엇인지에 대하여 포스팅해 보도록 하겠습니다.

서론 🕵🏻‍♂️

사실 개발을 하는 모든 사람이라면 테스트 코드가 중요하다는 말은 많이 들어 봤을 거다. 그런데 정작 좋은 테스트 코드가 어떤것인지 어떻게 작성해야하는지는 모른다.(물론 나도 그랬다…😢) 그래서 이제부터라도 좋은 테스트 코드는 어떤것인지 같이 한번 알아보도록 하자.

좋은 테스트 코드란?🧐

  • 테스트 코드의 가독성과 유지 보수성이 좋아야 한다.
  • 프로젝트 안에서, 그리고 소스 파일 안에서 코드는 적절히 구조화 되어 있어야 한다.
  • 테스트가 무엇을 검사하는지 확실히 명시해야 한다.
  • 테스트는 안정적이고 반복 가능해야 한다.

좋은 테스트의 구조 🏠

AAA패턴

  • Arange(준비): 테스트를 실행하기 전에 필요한 것들을 준비한다. 예를 들어 객체를 생성하거나, Mock 객체를 만드는 것을 의미한다.
  • Act(실행): 테스트 코드를 실행한다.
  • Assert(단언): 실행한 코드가 예상한 대로 동작했는지 확인한다.

이렇게 AAA패턴은 단어 앞의 A를 붙인 것을 의미한다. 다른 개발자분들이 내가 작성한 테스트 코드를 볼 때 명확하게 이 AAA패턴을 알 수 있게 작성한다면 더 빨리 이해 할 수 있을것이다.

AAA패턴(Arange,Act,Assert)을 GWT(Given,When,Then)으로 부르기도 합니다.

좋은 테스트의 원칙 FIRST 🥇

FIRSTFast, Isolated, Repeatable, Self-validating, Timely 의 문자를 따서 지어진 이름이다.

Fast , 빠르다

테스트 코드가 빠르게 수행될 수 있도록 만드는것! 느린 것에 대한 의존성을 낮춰야 한다ex)network. 테스트 코드가 빠르게 수행되어야지 많은 테스트가 있더라도 빈번히 테스트를 수행해서 검증할 수 있기 때문이다.

Isolated, 고립시키다

테스트 코드를 최소한의 유닛으로 테스트할 수 있도록 만들어야 한다. 하나의 테스트에서 너무 많은 것들을 테스트 해버린다면 어디에서 문제가 생겼는지 알기 힘들다. 최소한의 단위로 고립적으로, 독립적으로 만드는 것이 중요하다.

Repleatable, 반복 가능해야 한다

테스트 코드를 반복이 가능하도록 만들어야 한다. 실행할 때마다 동일한 결과를 유지해야 한다는 말이다. 너무 당연한 말이다. 네트워크나 직접 통제할 수 없는 외부 환경에 의존하는 코드라면 실행할 때마다 결과값이 달라질 수도 있을것이다.❌

Self-validating, 스스로 검증 가능해야한다.

테스트 코드의 결과를 일일이 봐야 한다면 너무 번거롭다. Jest나 다른 라이브러리를 통해 내가 실행한 값과 원하는 결과값이 같은지 스스로 검증할 수 있도록 만들어야 한다.

Timely, 적시에 사용한다.

사실 공부하면서 제가 했던 가장 큰 오류중 하나이기도 합니다 😂

코드를 작성하고 수정한 다음 사용자에게 배포한 다음에 테스트 코드를 작성한다면 사실 아무런 의미가 없다. 사용자에게 배포하기 전!!에 적시에 테스트 코드를 작성해야 한다.

좋은 테스트의 범위 Right-BICEP💡

Right-BICEP이란 테스트 코드에서 무엇을 테스트할지에 대해 쉽게 선별하게 해준다.

Right, 결과가 올바른가?

테스트 코드는 무엇보다도 먼저 기대한 결과가 제대로 나오는지 검증할 수 있어야 한다. 모든 요구 사항이 정상 동작하는지 확인해야 한다.

Boundary conditions, 경계 조건은 맞는가?

테스트 코드는 모든 코너 케이스에 대해 테스트를 해야 한다. 예를 들어 값이 0이거나 이상적인 기댓값을 벗어나는 값이 들어올 때, 잘못된 양식의 데이터가 들어왔을 때의 케이스를 테스트해야 한다.

Inverse relationship, 역 관계를 검사할 수 있는가?

말 그대로 역관계를 적용해서 결과값을 확인할 수 있어야 한다. 곱셈으로 나눗셈을 검증하고 뺄셈으로 덧셈을 검증하는 것 을 의미한다.

Cross-check, 다른 수단을 활용하여 교차 검사할 수 있는가?

기존에 작성했던 테스트 코드가 같은 역할을 하는 라이브러리나 다른 테스트 코드를 사용해도 똑같은 결과값이 나오는지 확인해야 함을 의미한다.

Error conditions, 오류 조건을 잘 처리했는가?

네트워크 에러가 발생했을 때 메모리가 가득 찼을 때 우리가 예상하는 오류 상황에서 우리의 코드가 오류를 잘 처리하는지 확인해야 한다.

Performance characteristics,성능 조건은 기준에 부합하는가?

성능 확인은 테스트를 통해 정확한 수치로 확인해야 한다. 성능이 좋다 하던지 기준에 부합한다는 것은 데이터를 통해 정확히 확인해야 한다.

경계 조건에서는 CORRECT 🧭

Right-BICEP의 Boundary conditions에서 CORRECT약어가 잠재적인 경계 조건을 기억하는 데 도움을 줄 수 있습니다.👍🏻

  • Conformance(준수) : 특정한 포맷을 준수하는지 확인해야 한다. 전화번호, 이메일 등 특정한 포맷을 따라야 한다면 인풋이 포맷에 맞을 때 아닐 때 우리의 코드가 어떻게 동작하는지 예상하는 테스트 코드를 작성해야 한다.

  • Ordering(순서) : 우리의 코드에서 값의 집합이 적절하게 정렬되어서 들어와야 하는 경우라면 정렬되어 있지 않을 때 순서가 잘못되었을 때 우리의 코드가 어떻게 동작하는지 예상하는 테스트 코드를 작성해야 한다.

  • Range(범위) : 인풋의 범위가 우리가 예상하는 범위를 넘어섰을 때 어떻게 동작하는지도 테스트 코드를 작성해야 한다.

  • Reference(참조) : 우리가 가정하고 있는 상황이 아닐 때 어떻게 동작하는지도 테스트 코드를 작성해야한다.

  • Existence(존재) : 값이 존재하지 않을 때 어떻게 동작하는지 확인하고 테스트 코드를 작성해야 한다.

  • Cardinality(기수) : 목록이 하나도 없을 때, 하나만 있을 때, 여러 개가 있을 때 등의 경우에 어떻게 동작하는지 확인하고 테스트 코드를 작성해야 한다.

  • Time(시간): 특정한 시간을 너무 많이 소비했을 때 와 나라마다 시간이 다른 특정한 경우 어떻게 동작하는지 확인하고 테스트 코드를 작성해야 한다.


궁금하신 점이 있다면 아래 댓글로 남겨주세요!👇