////
Search
Duplicate
🚛

Chapter 15. 테스트 가능성

테스트를 작성하는 이유는 크게 회귀 버그 방지와 품질 개선이다.
품질 개선의 경우, 크게 안 와닿을 수 있는데, 이는 테스트가 기존의 코드에 부가적으로 작성되는 것으로 받아들여지기 때문이다.
하지만 시점을 조금 옮겨서 구현 전에 작성, 구현 중에 작성하는 것으로 보기 시작하면 이야기가 달라진다. 이 과정들에서 개발자는 테스트를 어떻게 하면 쉽게 작성할 수 있을지 고민함으로써 코드의 품질을 높일 수 있다.
즉 설계가 테스트 가능성을 높이기 위한 방향으로 개선되는 것이다. 이는 보통 변경에 유연한 설계가 될 가능성이 높다.
이번 장에서는 테스트 가능성이라는 개념을 학습하는데, 이는 테스트를 이용해 좋은 설계를 얻고자 하는 개발자라면 반드시 알아두어야 하는 개념이다.

1. 테스트를 어렵게 만드는 요소

테스트는 테스트하려는 대상의 입력을 쉽게 바꿀 수 있고 출력을 쉽게 검증할 수 있을 때 작성하기가 쉽다.
반면 테스트를 하려는 대상에 숨겨진 입력이 존재하거나 숨겨진 출력이 있을 때, 테스트를 검증하기가 어려워진다.
그렇다면 테스트를 어렵게 만드는 숨겨진 입출력은 뭘까?

1. 숨겨진 입력

사용자가 로그인하면 현재 시각을 사용자의 마지막 로그인 시각으로 기록해야 하는 요구사항이 있다고 해보자.
해당 요구사항에 대한 테스트를 작성할 때, 어떻게 해야할까? 현재 시각으로 변경되었는지 확인하는 것은 성능이 매우 좋지 않은 이상 어려울 것이다. 아니면 변경되었는지만 체크하는 정도로 확인해야할까?
물론 Mockito 같은 프레임워크를 이용해 강제로 Stub으로 만드는 방법도 고려해볼 수 있다.
하지만 이들은 근본적인 해결책이 되지 못한다. 근본적인 문제는 테스트 가능성이 낮은 코드이기 때문이다.
login 함수의 매개변수에 현재 시각을 넘겨주는 식으로 변경해보자. 이는 테스트하기가 매우 쉬운 코드가 될 것이다.
하지만 문제가 완전히 제거된 것은 아니다. login 함수의 외부로 넘어갔을 뿐이다. 아마 UserService에는 여전히 Clock의 전역 메소드를 사용하는 부분이 남아있을 것이다.
이를 개선하기 위해 의존성을 주입받아 사용하게끔 변경할 수 있다.

2. 숨겨진 출력

숨겨진 출력은 메소드 호출 결과가 반환값이 아닌 경우를 가리킨다. 즉, 반환값 이외의 존재하는 모든 부수적인 출력을 숨겨진 출력이라고 한다.
시스템에 이 같은 숨겨진 출력이 너무 많다면 개발자는 메소드를 호출할 때마다 주춤할 수 밖에 없다. 메소드를 호출한 결과로 어떤 결과가 나올지 모르기 때문이다.

2. 테스트가 보내는 신호

테스트를 작성하기 위해 고민하다 보면 테스트가 보내는 신호들이 몇몇 있다.
테스트의 입출력을 확인할 수 없는데? 이런 경우에는 어떻게 하지?
private 메소드는 어떻게 테스트해야 하지?
서비스 컴포넌트의 간단한 메소드를 테스트하고 싶을 뿐인데, 이를 위해 필요도 없는 객체를 너무 많이 주입해야 하네?
메소드의 코드 커버리지를 100% 달성하려면 테스트해야 할 케이스가 너무 많아지는데?
이는 모두 하나의 의견으로 귀결된다. 설계가 잘못됐을 확률이 높으니 좋은 설계로 변경해보라는 의견이다. 각각 다음과 같이 해석할 수 있다.\
테스트의 입출력을 확인할 수 있는 코드로 구조를 변경해야 한다.
private 메소드는 테스트할 필요가 없다. 이는 내부 구현을 테스트하는 것과 같은 말이다. 이를 테스트하고 싶다면 책임을 잘못 할당한 경우일 가능성이 높다.
서비스 컴포넌트를 더 작은 단위로 나누라는 의미일 수 있다. 더 세분화해보자.
커버리지 100%는 그렇게 중요한 목표가 아니다. 테스트로 책임을 수행하고 있는지를 제대로 검증하는 것이 더 중요하다.
테스트를 작성하면서 우리는 사용자의 입장에서 코드를 바라보기 때문에 이러한 신호들을 포착할 수 있다.
끝으로 Mockito, H2, JUnit 없이도 테스트 코드를 작성할 줄 알아야 한다. 어떤 라이브러리를 사용할지보다 어떤 것을 테스트할 지가 더 중요한 항목이다.