테스트 하기 좋은 코드 작성하기 : 세미나 후기

Jayson·2024년 12월 20일
post-thumbnail

최근 자바로만 프로그래밍을 진행하면서 많은 고민을 하게 되었다.
좋은 코드란 무엇인지, 객체지향적인 코드는 무엇인지, 역할과 책임은 어떻게 나눠야 하는지에 대해 깊이 생각해보는 계기가 되었다. 그중에서도 가장 익숙하면서도 낯설게 느껴졌던 주제는 바로 테스트 코드였다.

테스트 코드에 대해 많이 들어왔고, 실제로도 AssertJ와 JUnit 5를 활용해본 적이 있다.
테스트 코드의 필요성을 느끼긴 했지만, 정작 프로젝트를 진행할 때는 주로 Postman이나 Swagger를 활용해 DB에 직접 요청을 보내고 결과를 눈으로 확인하는 방식에 의존했다.

그러던 중, 이동욱(인프랩 CTO)님께서 구름 커밋에서 테스트 코드를 주제로 세미나를 진행한다는 소식을 접하고 참여하게 되었다.
이 글은 해당 세미나를 통해 배운 내용 중 특히 인상 깊었던 테스트하기 좋은 코드란 무엇인가에 대한 내용을 정리하고, 배운 점을 공유하기 위해 작성되었다.


테스트 코드에 대한 오해

2024년 초, 어느 컨퍼런스에서 테스트 코드에 대한 부정적인 의견들이 많이 나왔던 기억이 있다.
특히, 한 스타트업에 재직 중인 2년 차 개발자가 마이크를 잡고 했던 말이 떠오른다.

“저희 팀은 테스트 코드를 절대 작성할 계획이 없고, 그럴 시간도 없습니다.”

이번 테스트 코드 컨퍼런스를 들으면서 그때 들었던 말들이 다시 떠올랐다.
하지만 이제는 그것이 단순한 오해였다는 것을 깨닫게 되었다.

“시간이 없어서 테스트를 못 만들었다”는 말은 “나는 테스트 코드를 만드는데 시간이 많이 걸린다”는 말과 동일합니다.


기존의 방식

  1. 코드를 작성한다.
  2. 개발 DB에 데이터를 적재한다.
  3. 서버를 실행한다.
  4. Postman 항목을 작성한다.
  5. API를 호출한다.
  6. 결과를 눈으로 검증한다.
  7. 개발 DB 데이터를 롤백한다.

테스트 코드 적용 방식

  1. 코드를 작성한다.
  2. 테스트 코드를 작성한다.
  3. 테스트 코드를 실행한다.

테스트 코드 도입

테스트 코드를 작성하는 데 걸리는 시간이 기존 방식의 (2) ~ (7) 과정보다 더 오래 걸린다면, 기존 방식이 더 효과적이라고 판단할 수도 있다. 하지만 테스트 코드를 도입하면 작업 효율과 숙련도가 높아지는 데 크게 기여한다.


작업 효율

  • 테스트 코드는 기존 방식보다 효율적인 검증과 유지보수를 가능하게 한다.

숙련도

  • 테스트 코드 작성에 어려움을 겪는 경우, 이는 테스트하기 어려운 구조를 구현했거나 테스트 작성 방법에 익숙하지 않아서일 가능성이 높다.

테스트 코드를 작성하는 문화가 조직에 자리 잡지 않았다면,
먼저 팀 문화를 존중하고 신뢰를 쌓은 후 테스트 코드 도입을 고려하자.


테스트 코드로 얻는 이점

  1. 목표 달성 가속화: 테스트 코드는 목표한 작업을 더 빠르게 완성할 수 있도록 돕는다.
  2. 유지보수 생산성 향상: 기능 추가 시 다른 기능에 영향을 주지 않도록 보장하여 유지보수 효율을 높인다.
  3. 변경과 버그 발견 지원: 변경 사항이나 버그를 빠르게 발견하여 안정적인 개발 환경을 조성한다.

테스트 코드의 필요성

if 문의 증가와 테스트 요소

  • if 문이 늘어날수록 테스트해야 할 요소는 지수적으로 증가한다.
    예를 들어, if 문이 1개에서 2개로 늘어나면 테스트해야 할 조합은 4개로 증가한다.

테스트 코드를 통해 더 빠르고 안전한 제품을 출시하고,
핵심 기능과 연관된 작업에서도 다른 코드에 영향을 주지 않는 개발 방식을 구현하자. 🚀


테스트 코드 프레임워크

Q: 그럼 테스트 코드 프레임워크 숙련도를 키우는 것에 의미를 둬야 할까요?

A: NO

테스트 코드 프레임워크 숙련도가 높아도 테스트 코드 작성은 어렵다.


테스트하기 좋은 코드 VS 테스트하기 어려운 코드

테스트하기 좋은 코드

테스트 하기 좋은 코드는 결과가 인수에만 의존하는 부작용이 없는 코드를 말한다. 다시 말해서 멱등성이 보장되는 순수함수를 말한다. 예를 들어 a를 넣으면 b가 return 되는 것이 보장되는 것 그렇지 않은 경우는 어떤 날짜, 상황이 변하면 b의 값이 달라지는 것. 구체적으로는, 일요일에 할인 정책이 적용되는 함수가 다른 요일에 테스트를 하면 날짜가 달라져 통과를 못하는 경우.

예시:

  • 일요일에 할인 정책이 적용되는 함수는 다른 요일에 테스트하면 통과하지 못할 가능성이 있다.

테스트하기 어려운 코드

제어할 수 없는 값에 의존하는 코드

new Date() 등 실행할 때마다 결과가 다른 함수에 의존하는 경우 테스트가 어렵다. 브라우저 함수, 전역 변수 등에 함수 밖의 영역에 의존하는 경우 테스트가 어렵다. PG사 라이브러리 등 외부 상황에 의존하는 경우 테스트가 어렵다. 예를 들어, 주문일이 일요일이면 주문 금액의 10%를 할인하는 함수. 이런 경우에는 테스트 실행 일자에 따라 테스트 결과가 달라짐. 테스트 하기 어려운 부분과 테스트 하기 쉬운 부분을 나누자. 테스트가 어려운 부분은 LocalDate.now() 이부분이다. 위 함수 부분을 변수로 받아서 테스트로 만든다. 즉, 테스트 하기 어려운 부분과 쉬운 부분을 나누는 것.

해결 방법

  • 외부 의존성을 분리하여 테스트하기 쉬운 구조로 리팩터링한다.

외부에 영향을 주는 코드

  • 외부 출력(log), 이메일 발송, DB 연동 등이 포함된 코드

해결 방법

  • 핵심 로직과 외부 연동 코드를 분리하여 테스트를 용이하게 한다.

테스트하기 어려운 코드를 개선하기 위한 전략

제어할 수 없는 걸 개선해보자. 예를 들어, 주문하는 상황에서 discount() 를 호출하는 경우, 모든 부분이 테스트가 어려워지는 상황을 생각해보자. 테스트의 어려움은 전파가 되는데 특히 Core에 있을 떄 어려워진다.

Controller - Service - Domian - Repository

제어할 수 없는 값을 외부에서 주입받도록 하면 원하는 상황(제어할 수 있음) 테스트 가능하게 된다. 이 상황이 조삼모사처럼 보일 수 있다. JS/TS, Kotlin의 기본 인자를 활용할 수 있다면 기본인자를 활용한다.

java에는 기본 인자 기능이 없는데 어떻게할까. 구조를 해치지 않는 범위 내에선 의존하는 코드가 가장 적은 영역까지 밀어내는 것이 좋다. 제어하기 어려운 값을 계층의 바깥으로 밀어낸다. (Domain에 있는 어려운 걸 Controller로 밀어내는 것) 이렇게 되면 Controller만 테스트가 어렵고 Service 등 나머지는 테스트가 쉬움. Domain도 안전해지는 상황을 만들 수 있다.

그러나 Controller의 테스트는 여전히 어렵다. Service, Repository, Component 등 많은 함수가 제어할 수 없는 값을 항상 인자로 받아야만 한다.

이럴 때는 의존성 주입을 사용한다.

Controller - Service - Time(Interface) - Jodal Time, Sub Time

실제는 Jodal Time을 사용하지만 테스트는 Sub Time을 사용. 중간에 Interface를 사용해서 의존성을 주입 받을 수 있게 한다. static을 사용하지 말고 의존성으로 주입 받을 수 있게 한다.

  1. 의존성 주입 활용

    • 테스트에서 특정 상황을 재현할 수 있도록 의존성을 주입한다.
  2. 로직 분리

    • 데이터베이스 의존성을 제거하여 핵심 로직만 테스트한다.
  3. private 메서드 검증

    • 역할을 명확히 분리하여 테스트 가능한 구조를 만든다.
    • private 메서드가 늘어난다면 클래스의 책임에 대해서 생각해 봐야 한다는 시그널로 생각해 보자.
  1. 좋은 함수 작성 원칙
    • 부작용 없이 입력값에만 의존하는 순수함수로 작성한다.

마무리

좋은 테스트 코드는 자연스럽게 좋은 함수와 구조를 만들게 한다. 비즈니스 로직은 순수함수로 구현하고, 부수효과는 외부로 밀어내어 격리하는 것이 중요하다. 이를 통해 프레임워크나 라이브러리에 의존하지 않고도 테스트하기 쉬운 코드를 작성할 수 있다.

결국 객체지향, 좋은 코드를 작성하려고 노력하다 보면 테스트 코드 역시 작성하기 쉽고, 이렇게 되면 개발의 생산성도 높아진다는 걸 느꼈다. 이전까지 눈으로 검증을 해왔지만 눈으로 한다는 생각을 별로 하지 않았는데 앞으론 가능하면 Mocking, Postman, Swagger 등 사용을 최소화하면서 개발의 효율, 안정성을 챙겨보려고 한다.

profile
Small Big Cycle

0개의 댓글