소프트웨어는 복잡하다.
회사에서 개발자로 일하고 있다면 공감할 것이다.
새로운 기능이 추가될 때마다 깔끔한 코드는 더러워지고,
더러운 코드에 기능을 추가하는 순간이면 휴...
정말 코드 한 줄 추가하기도 어렵다.
소프트웨어는 코드가 추가될수록 점점 복잡해진다.
이를 물리학 용어 엔트로피를 사용해 소프트웨어 엔트로피라고 말한다.
열역학 2 법칙
엔트로피는 증가하는 현상만 일어나며 감소하지 않는다.
코드에 엔트로피가 높아짐에 따라 새로운 코드를 추가하는 것이 어려워진다.
코드를 작성할 때 고려해야 하는 다른 코드가 많기 때문이다.

가만히 있으면 소프트웨어 엔트로피는 절대 줄어들지 않고 증가만 한다.
그래서 우리는 주기적으로 이 엔트로피를 줄여야 한다. 즉 코드 리팩토링을 해야 한다.
하지만 리팩토링은 너무 어렵다. 왜?
괜히 잘 동작하는 코드를 건드렸다가 버그나 장애를 만들지 않을까? 라는 두려움 때문이다.
이 두려움 때문에 우리는 리팩토링을 여러 변명으로 미룬다.
좋다.
그럼 어떻게 하면 코드 변경에 대한 두려움을 줄일 수 있을까?
결국 테스트 코드다.
코드 수정에 대한 두려움이 있는가? 테스트 코드를 작성하자.
실수하지 않는 컴퓨터가 내 코드를 검증하게 만들자.
테스트 코드는 두려움 없이 코드를 리팩토링할 수 있게 만든다.
이러한 환경은 소프트웨어의 복잡성을 해소하게 만들며, 소프트웨어 개발 속도를 높인다.
이제 제목에 대한 답을 하겠다.
왜 테스트 코드가 필요한가? 소프트웨어 개발을 지속 가능하게 만들기 위해서다.
사실 테스트 코드가 중요하다는 사실을 누구나 안다.
하지만 테스트 코드를 작성하기는 너무 어렵다.
테스트 코드를 작성하기 위해 내가 직접 만들어줘야 하는 의존성도 많다.
특히 프론트 개발자들에게 테스트 코드 작성은 정말 어렵다.
우리가 작성하는 코드의 반환 값은 화면이고, 사용자 상호작용이기 때문이다.
사용자에게 보이는 화면을 어떻게 테스트하는가? ㅠㅠ (UI 테스트가 있지만 이 역시 쉽지 않다)
위와 같은 이유로 테스트 코드를 작성하는 것은 나에게 너무 어려웠다.
효율 대비 공수도 많이 드는 것 같았다.
그래서 알고 싶었다.
좋은 테스트 코드는 무엇인지? 테스트 코드를 어떻게 작성해야 하는지?
위 내용에 공감한다면 앞으로의 내용을 기대해도 좋다.
지금부터 유닛테스트라는 책을 읽고 내가 알게 된 좋은 테스트 코드에 대해 소개하겠다.
좋은 테스트 코드는 리팩토링 내성이 있어야 한다.
쉽게 말해 코드의 구현 방식이 바뀌어도 결과만 같다면 테스트가 통과해야 한다.
우리는 간혹 함수 구현 부에 집중하는 테스트 코드를 작성한다.
함수를 호출했을 때 이 API를 호출해야 해. 함수를 호출했을 때 내부의 이 상태 값이 바뀌어야 해 등
하지만 이는 좋은 테스트 코드가 아니다.
리팩토링으로 다른 API를 호출하도록 코드를 바꾼다면? 기존에 사용하던 내부 상태 값을 지웠다면?
리팩토링한 함수가 올바른 결과를 내더라도 테스트 코드는 실패한다.
이러한 상황을 거짓 양성이라고 한다.
실제 기능이 의도한 대로 동작했지만 실패하는 것을 말한다.
테스트 코드는 기존 기능이 고장 났을 때 조기 경보를 제공한다.
하지만 코드가 고장 나지 않더라도 테스트 코드에서 경보가 발생한다면? 우리는 테스트 코드를 신뢰하지 못하게 된다.
거짓 양성이 반복된다면 어떻게 될까?
우리는 테스트 코드 실패에 익숙해지고 결국 우리는 테스트 코드를 신뢰하지 못한다.
이는 테스트 코드를 믿을 만한 안전망을 인식하지 않게 한다.
결국 우리는 테스트 코드를 믿지 못하기 때문에 리팩토링을 주저하게 된다. ㅠㅠ
좋다. 그럼 내성이 있는 테스트 코드는 어떻게 작성할 수 있을까?
바로 출력 기반의 테스트 코드를 작성해야 한다.
뭔가 있는 것처럼 설명했지만, 출력 기반 테스트 코드 별거 없다.
함수의 결과만 검증하는 테스트 코드를 작성하라는 것이다.
함수가 어떤 API를 호출하는지, 어떤 상태 값을 변경하는지를 검증하는 등
위와 같은 테스트 코드는 좋지 않다. 리팩토링 내성이 떨어지기 때문이다.
그럼 코드는 어떻게 바꿀 수 있을까?
만약 내부 상태를 변경하는 함수라면
함수가 상태를 변경하라는 명령을 반환하도록 함수를 리팩토링하자.
함수 내부에서 어떤 API를 호출하는 함수라면
함수 외부에서 API를 호출하고 해당 결과를 함수에 넘겨주도록 하자.
결국 출력 기반의 테스트 코드를 작성하기 위해서라도
우리는 함수형 프로그래밍으로 코드를 작성해야 한다.
출력 기반의 테스트 코드에 대한 내용을 읽다 보면 이런 고민이 든다.
(사실 내가 책을 보면서 든 고민이었다)
함수의 동작만 검증하면 어떻게 하냐? 실제 그 결과가 반영되었는지 검증해야 하지 않는가?
실제로 서버에 API 호출을 했는지, 파일을 썼는지, 사용자에게 이런 화면이 보였는지 등 말이다.
하지만 내가 예시로 든 코드까지 검증하기 위해서는
테스트 코드 작성에 큰 공수가 든다. 심지어 리팩토링 내성도 떨어진다.
어떻게 해야 하나? 고민하던 나에게 험블 객체가 찾아왔다.
나는 코드가 테스트하기 어려운 의존성에 결함이 되어 있기 때문에 테스트하기 어렵다는 것을 깨달았다.
로직을 테스트하려면, 테스트가 가능한 부분을 추출해야 한다.
결과적으로 코드는 테스트할 수 있는 부분 / 테스트하기 어려운 의존성 / 그 사이를 연결하는 험블 객체로 이루어지게 된다.
이 험블 객체는 자체적인 로직이 거의 없기 때문에 테스트할 필요가 없다.
책 [유닛 테스트] 중
우리는 테스트 코드를 두려움 없이 리팩토링하기 위해 작성한다.
우리는 자동화 테스트를 하는 것이 아니다.
이 책은 나에게 이렇게 말했다.
도메인 모델이나 비즈니스 로직만 테스트하라고.
나머지는 다 험블 객체라고.
마치기 전 글을 짧게 정리 하겠다.
1. 테스트 코드는 리팩토링을 두려움 없이 할 수 있게 만드는 것에 목적이 있다.
2. 그렇기에 좋은 테스트 코드는 리팩토링 내성이 있어야 한다.
3. 리팩토링 내성이 있으려면 출력 기반의 테스트 코드를 작성해야 한다.
4. 출력 기반으로 테스트가 어려운 코드는 다 험블 객체다. 그러니 비즈니스 로직만 테스트해라.
이 책은 막연했던 테스트 코드에 대한 개념을 확립시켜 주었다.
또 다양한 코드 예시가 있어서 이해하기 몹시 어렵지 않았다.
만약 이 글을 읽고 테스트 코드에 관심이 생겼다면, 책 유닛 테스트를 한번 읽어보는 것을 추천한다.