•
테스트를 작성하는 이유는 크게 회귀 버그 방지와 품질 개선이다.
•
품질 개선의 경우, 크게 안 와닿을 수 있는데, 이는 테스트가 기존의 코드에 부가적으로 작성되는 것으로 받아들여지기 때문이다.
•
하지만 시점을 조금 옮겨서 구현 전에 작성, 구현 중에 작성하는 것으로 보기 시작하면 이야기가 달라진다. 이 과정들에서 개발자는 테스트를 어떻게 하면 쉽게 작성할 수 있을지 고민함으로써 코드의 품질을 높일 수 있다.
◦
즉 설계가 테스트 가능성을 높이기 위한 방향으로 개선되는 것이다. 이는 보통 변경에 유연한 설계가 될 가능성이 높다.
•
이번 장에서는 테스트 가능성이라는 개념을 학습하는데, 이는 테스트를 이용해 좋은 설계를 얻고자 하는 개발자라면 반드시 알아두어야 하는 개념이다.
1. 테스트를 어렵게 만드는 요소
•
테스트는 테스트하려는 대상의 입력을 쉽게 바꿀 수 있고 출력을 쉽게 검증할 수 있을 때 작성하기가 쉽다.
•
반면 테스트를 하려는 대상에 숨겨진 입력이 존재하거나 숨겨진 출력이 있을 때, 테스트를 검증하기가 어려워진다.
•
그렇다면 테스트를 어렵게 만드는 숨겨진 입출력은 뭘까?
1. 숨겨진 입력
•
사용자가 로그인하면 현재 시각을 사용자의 마지막 로그인 시각으로 기록해야 하는 요구사항이 있다고 해보자.
•
해당 요구사항에 대한 테스트를 작성할 때, 어떻게 해야할까? 현재 시각으로 변경되었는지 확인하는 것은 성능이 매우 좋지 않은 이상 어려울 것이다. 아니면 변경되었는지만 체크하는 정도로 확인해야할까?
•
물론 Mockito 같은 프레임워크를 이용해 강제로 Stub으로 만드는 방법도 고려해볼 수 있다.
•
하지만 이들은 근본적인 해결책이 되지 못한다. 근본적인 문제는 테스트 가능성이 낮은 코드이기 때문이다.
•
login 함수의 매개변수에 현재 시각을 넘겨주는 식으로 변경해보자. 이는 테스트하기가 매우 쉬운 코드가 될 것이다.
•
하지만 문제가 완전히 제거된 것은 아니다. login 함수의 외부로 넘어갔을 뿐이다. 아마 UserService에는 여전히 Clock의 전역 메소드를 사용하는 부분이 남아있을 것이다.
•
이를 개선하기 위해 의존성을 주입받아 사용하게끔 변경할 수 있다.
2. 숨겨진 출력
•
숨겨진 출력은 메소드 호출 결과가 반환값이 아닌 경우를 가리킨다. 즉, 반환값 이외의 존재하는 모든 부수적인 출력을 숨겨진 출력이라고 한다.
•
시스템에 이 같은 숨겨진 출력이 너무 많다면 개발자는 메소드를 호출할 때마다 주춤할 수 밖에 없다. 메소드를 호출한 결과로 어떤 결과가 나올지 모르기 때문이다.
2. 테스트가 보내는 신호
•
테스트를 작성하기 위해 고민하다 보면 테스트가 보내는 신호들이 몇몇 있다.
◦
테스트의 입출력을 확인할 수 없는데? 이런 경우에는 어떻게 하지?
◦
private 메소드는 어떻게 테스트해야 하지?
◦
서비스 컴포넌트의 간단한 메소드를 테스트하고 싶을 뿐인데, 이를 위해 필요도 없는 객체를 너무 많이 주입해야 하네?
◦
메소드의 코드 커버리지를 100% 달성하려면 테스트해야 할 케이스가 너무 많아지는데?
•
이는 모두 하나의 의견으로 귀결된다. 설계가 잘못됐을 확률이 높으니 좋은 설계로 변경해보라는 의견이다. 각각 다음과 같이 해석할 수 있다.\
◦
테스트의 입출력을 확인할 수 있는 코드로 구조를 변경해야 한다.
◦
private 메소드는 테스트할 필요가 없다. 이는 내부 구현을 테스트하는 것과 같은 말이다. 이를 테스트하고 싶다면 책임을 잘못 할당한 경우일 가능성이 높다.
◦
서비스 컴포넌트를 더 작은 단위로 나누라는 의미일 수 있다. 더 세분화해보자.
◦
커버리지 100%는 그렇게 중요한 목표가 아니다. 테스트로 책임을 수행하고 있는지를 제대로 검증하는 것이 더 중요하다.
•
테스트를 작성하면서 우리는 사용자의 입장에서 코드를 바라보기 때문에 이러한 신호들을 포착할 수 있다.
•
끝으로 Mockito, H2, JUnit 없이도 테스트 코드를 작성할 줄 알아야 한다. 어떤 라이브러리를 사용할지보다 어떤 것을 테스트할 지가 더 중요한 항목이다.