프로덕션 코드와 테스트 코드의 비율은 1:1에서 1:3 많게는 1:10 수준에 이르기도 한다.
주요 논쟁은 "좋은 단위 테스트를 작성하는 것은 어떤 의미인가?" 이다.
단위 테스트 활동이 더 나은 설계로 이어진다.
코드 베이스에 대해 단위 테스트 작성이 필요하면 일반적으로 더 나은 설계로 이어 진다.
더 나은 설계는 단지 좋은 부수 효과일 뿐이다.
- 코드 조각을 단위 테스트 하는 것은 좋은 부정 지표다. 비교적 높은 정확도로 저품질 코드를 가려낸다.
- 코드를 단위 테스트하기 어렵다면 코드 개선이 필요하다는 것을 의미한다.
- 보통 강결합에서 저품질이 나타난다.
- 반대로 코드 조각을 단위 테스트할 수 있다는 것은 좋지 않은 긍정 지표다.
- 코드를 쉽게 단위 테스트할 수 있다고 해도 반드시 코드 품질의 좋은 것을 의미하지는 않는다.
단위 테스트의 목표는 소프트웨어 프로젝트의 지속 가능한 성장을 가능 하게 하는 것이다.
테스트가 없는 프로젝트는 처음에는 잘못된 아키텍처 결정이 없고, 걱정할 만한 코드가 없어서 빠르게 시작할 수 있지만, 시간이 지나면서 점점 더 많은 시간을 들여야 처음과 같은 진척을 낼 수 있다. 결국 개발 속도가 느려지고, 전혀 진행하지 못할 정도로 느려질 수도 있다.
지속적인 정리와 리팩토링 등과 같은 관리를 하지 않고 방치하면 시스템이 점점 더 복잡해지고 무질서해진다.
테스트는 새로운 요구사항에 더 잘 맞게 리팩토링한 후에도 기존 기능이 잘 작동하는지 확인하는데 도움이 된다.
테스트는 초반에는 노력이 필요하다. 하지만 시간이 지날수록 비용이 메워진다.
지속성과 확장성이 핵심이며, 이를 통해 장기적으로 개발 속도를 유지할 수 있다.
잘못된 경고가 발생하는 테스트
버그를 알아내는 데 도움이 되지 않는 테스트
유지보수가 어렵고 느린 테스트
테스트의 가치와 유지 비용을 결정하는 활동들
- 기반 코드를 리팩토링할 때 테스트도 리팩토링하라.
- 각 코드 변경 시 테스트를 실행하라.
- 테스트가 잘못된 경고를 발생시킬 경우 처리하라.
- 기반 코드가 어떻게 동작하는지 이해하려고 할 때는 테스트를 읽는 데 시간을 투자 하라.
테스트는 프로덕션 코드에 추가된 것으로 간주된다.
코드가 많아질수록, 소프트웨어 내의 잠재적인 보그에 노출되는 표면적이 넓어지고 프로젝트 유지비가 증가한다. 따라서 가능한 적은 코드로 문제를 해결하는 것이 좋다.
테스트 코드도 애플리케이션의 정확성을 보장하는 것을 목표로 하는 코드베이스의 일부로 봐야한다.
다른 코드와 마찬가지로 단위 테스트도 버그에 취약하고 유지보수가 필요하다.
커버리지 지표는 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타낸다.
일반적으로 커버리지 숫자가 높을수록 좋지만, 좋은 부정 지표지만 좋지 않은 긍정 지표다.
코드 커버리지가 너무 적을 경우엔 테스트가 충분치 않다는 좋은 증거지만, 코드 커버리지가 100%라고 하더라도 반드시
양질의 테스트 스위트라고 보장하지 않기 때문이다.
테스트 커버리지 백분율
@Test
void coverage80() {
assertThat(isStringLong("abc")).isFalse();
}
private boolean isStringLong(String input) {
if (input.length() > 5) {
return true;
}
return false;
}
위 테스트 코드는 true를 반환하는 구문은 테스트 라인을 통과하지 않기 때문에 테스트 커버리지가 80%가 된다.
@Test
void coverage100() {
assertThat(isStringLongRefactoring("abc")).isFalse();
}
private boolean isStringLongRefactoring(String input) {
return input.length() > 5;
}
위 테스트 코드는 모든 구문을 통과하기 때문에 테스트 커버리지가 100%가 된다.
위의 경우처럼 테스트 케이스를 개선한게 아닌(테스트가 검증하는 결과 개수가 같은) 단지 메서드 내 코드를 바꾼걸로도
테스트 커버리지의 지표를 높일 수 있다.
코드가 작을수록 테스트 커버리지 지표가 더 좋아진다.
코드가 작아져도 테스트 스위트의 가치나 기반 코드베이스의 유지 보수성이 변경되지 않는다.
테스트 커버리지의 단점을 극복, 더 정확한 결과를 제공한다.
원시 코드 라인 수를 사용하는 대신 if문과 switch문과 같은 제어 구조에 중점을 둔다.
분기 커버리지 = 통과 분기 / 전체 분기 수
private boolean isStringLength(String input) {
return input.length() > 5;
}
@Test
void coverage50() {
assertThat(isStringLength("abc")).isFalse();
} // isStringLong메서드는 true, false를 반환하는 경우가 있는데, 해당 테스트는 false를 반환하는 경우만 테스트하고 있으므로 분기 커버리지 지표는 50%가 된다.
@Test
void coverage100() {
assertThat(isStringLong("abcde")).isFalse();
assertThat(isStringLong("abcdef")).isTrue();
} // 2가지 경우 모두를 테스트하고 있으므로 분기 커버리지 지표는 100%가 된다.
private boolean wasLastStringLong;
@Test
void test() {
assertThat(isStringLong("abc")).isFalse();
}
private boolean isStringLong(String input) {
boolean result = input.length() > 5;
wasLastStringLong = result;
return result;
}
isStringLong 메서드에는 값을 반환하는 명시적 결과와 속성에 새로운 값을 쓰는 암묵적 결과가 있다.
암묵적 결과를 검증하지 않더라도 테스트 커버리지는 100%, 분기 커버리지는 50%의 결과를 보여준다.
일부 실행된 것만 보장된다.
@Test
void test() {
int result = parseInt("5");
assertThat(result).isEqualTo(5);
}
private int parseInt(String input) {
return Integer.parseInt(input);
}
해당 테스트는 정수만 테스트 하고 있다.
null, 정수가 아닌 값, 공백에 대한 테스트가 되지 않아도 분기 커버리지는 100%로 표시된다.
테스트에서 이러한 예외 상황을 다루는지 확인할 방법이 없다.
테스트를 작성할 때 커버리지 지표를 목표로 삼기 시작하면 위험 영역으로 이어질 수 있다.
커버리지 지표를 보는 가장 좋은 방법은 지표 그 자체로 보는 것이며, 목표로 여겨서는 안된다.
테스트 스위트의 품질을 측정하는 방법은 각 테스트를 하나씩 따로 평가하는 것 뿐이다.
요점은 테스트 스위트가 얼마나 좋은지 자동으로 확인할 수 없다. 개인 판단에 맡겨야 한다.
성공적인 테스트 스위트의 특징
- 개발 주기에 통합돼 있다.
- 코드베이스에서 가장 중요한 부분만을 대상으로 한다.
- 최소한의 유지비로 최대의 가치를 끌어낸다.
자동화된 테스트를 할 수 있는 방법은 끊임없이 하는 것 뿐이다.
코드가 변경될 때마다 아무리 작은 것이라도 실행해야 한다.
시스템의 가장 중요한 부분에 단위 테스트 노력을 기울이고, 다른 부분은 간략하게 또는 간접적으로 검증하는것이 좋다.
대부분의 애플리케이션에서 가장 중요한 부분은 비즈니스 로직(도메인 모델)이 있는 부분이다.
다른 부분은 세 가지 범주로 나눌 수 있다.
- 인프라 코드
- 데이터베이스나 서드파티 시스템과 같은 외부 서비스 및 종속성
- 모든 것을 하나로 묶는 코드
ex) 인프라 코드에 중요한 알고리즘이 있을수도 있다.
이 지침을 따르려면 도메인 모델을 코드베이스 중 중요하지 않은 부분과 분리해야 한다.
도메인 모델을 다른 애플리케이션 문제와 분리해야 단위 테스트에 대한 노력을 도메인 모델에만 집중할 수 있다.
단위 테스트에서 가장 어려운 부분은 최소 유지비로 최대 가치를 달성하는 것이다.
가치가 유지비를 상회하는 테스트만 스위트에 유지하는 것이 중요하다.
- 가치 있는 테스트(가치가 낮은 테스트) 식별하기
- 가치 있는 테스트 작성하기
가치 있는 테스트를 식별하려면 기준틀이 필요하다.
가치 있는 테스틀 작성하려면 코드 설계 기술도 알아야 한다.
단위 테스트와 기반 코드는 서로 얽혀 있으므로 코드베이스에 노력을 많이 기울이지 않으면 가치 있는 테스트를 만들 수 없다.