단위 테스트 현황
- 지난 20년간 단위 테스트의 적용은 매우 일반적인 상황이었음
- 대부분의 프로그래머는 단위 테스트를 실천하고 중요성을 알고 있다
- (백엔드 개발에서)는 더 이상 단위 테스트 작성이 논쟁의 여지가 될 수 없음
테스트 코드의 비율
- 대부분의 경우 1:1 에서 1:3, 혹은 비율이 훨씬 높아서 1:10 가까이 되는 경우도 존재하곤 한다
논쟁의 지점
- 단위 테스트를 작성해야 하는가는 더 이상 논쟁의 지점이 될 수 없다. 작성해야 하기 때문
- 그렇다면 좋은 단위 테스트를 작성하는 것은 어떤 의미인가? 로 논점이 변경되었음
많은 프로젝트에는 자동화된 테스트도 존재하고, CI 를 통하여 주기적 빌드를 하게 된다. 그리고 많은 양의 테스트가 주기적으로 실행되곤 한다. 그러나, 테스트를 해도 개발자가 원하는 결과를 얻지 못하는 경우가 많다.
원하는 결과를 얻지 못한다는 것은?
- 구현된 기능에는 많은 버그가 존재하고, 도움이 될 것이라고 생각하는 단위 테스트는 도움이 되지 못함
- 좋지 않은 테스트는 오히려 상황을 악화시킨다
단위 테스트의 목표
- 단위 테스트 활동이 더 나은 설계로 이어지는가?
- 일반적으로 더 나은 설계로 이어지나, 이것이 단위 테스트의 주된 목적은 아니다
- 왜냐하면, 단위 테스트를 짜기 어려운 코드는 보통은 좋지 못한 코드이지만
- 반대로 단위 테스트를 짜기 쉬운 코드라고 해도 설계가 좋은 코드 라고 볼 수 없기 때문
단위 테스트의 목표는 무엇인가?
- 소프트웨어 프로그램의 지속 가능한 성장을 가능하게 하는 것
- 프로젝트는 초기에는 쉽게 성장할 수 있다. 그러나, 시간이 지나면서 이렇게 성장하기는 훨씬 힘들다
- 프로젝트가 점차 진척됨에 따라, 테스트가 없는 코드는 점점 더 많은 시간을 들여야 같은 기능을 개발할 수 있다
- 심지어, 진척도가 0이 되버릴 수도 있음
소프트웨어 엔트로피
- 소프트웨어 복잡도가 증가함에 따라 개발 속도가 감소하는 현상을 소프트웨어 엔트로피라고 함
- 소프트웨어에서는 품질을 떨어뜨리는 코드 형태로 나타난다
- 무언가를 바꾸면, 엔트로피가 증가한다
- 리펙토링과 지속적인 정리를 하지 않고 두면, 시스템이 점점 더 복잡해지고 무질서해진다
테스트는 안정망 역할을 하며, 대부분에 리그레이션(특정 사건 이후에 기능이 의도한 대로 동작하지 않는 경우. 소프트웨어에서는 버그와 리그레이션은 거의 동의어)에 대해 보험을 제공하는 도구라 할 수 있다
좋은 테스트와 좋지 않은 테스트
- 테스트가 잘못 작성된 프로젝트는 초반에는 테스트가 있는 프로젝트와 비슷하다
- 그러나, 결국에는 테스트가 없는 것과 비슷한 상황에 빠짐
- 일부 테스트는 아주 중요하고 소프트웨어 품질에 많은 기여를 한다
- 그러나, 그 밖에 다른 테스트는 그렇지 않다
- 버그 발생을 해결하는 데 도움이 되지 않고
- 잘못된 경고가 발생하며
- 유지보수가 어렵고 아주 느리다
테스트 유지 비용
- 테스트가 많다고 무언가 좋아지는 게 아님
- 테스트 유지 비용 또한 테스트에 큰 영향을 미친다
- 기반 코드를 리펙토링 할 때 테스트 코드 리펙토링
- 각 코드 변경 시에 테스트 실행
- 테스트가 잘못된 경고 발생
- 기반 코드가 어떻게 동작하는지 이해하기 위해 테스트를 읽는 시간
테스트가 많을수록 좋은가?
- 그렇지 않다
- 사람들은 종종 프로덕션 코드와 테스트 코드가 다르다고 생각한다
- 하지만 코드는 자산 이 아니라 책임 이기 때문에 코드가 많아지면 많아질수록 잠재적인 버그 요인은 상승한다
- 단위 테스트 또한 버그에 취약하고 유지보수가 필요하다
테스트 커버리지 지표
- 두 가지 지표를 넓게 사용함
- 코드 커버리지와 분기 커버리지
- 일반적으로는 커버리지 지표가 높을수록 좋다
- 단 이것 또한 단위 테스트와 비슷하다
- 커버리지 지표가 너무 낮은 경우에는 테스트가 충분하지 않다 는 좋은 증거
- 그러나 100% 커버리지 지표라고 해도 반드시 양질의 테스트를 보장하지 않는다
코드 커버리지
- 코드 커버리지 = 실행 코드 라인 수 / 전체 라인 수
public static bool IsStringLong(string input) {
if(input.Length > 5)
return true
return false
}이와 같은 코드가 있고 테스트가 있을 때
public void Test() {
bool result = IsStringLong("abc");
Assert.Equal(false, result);
}전체 라인 수는 5, 위의 테스트는 true 를 반환하는 구문을 제외한 라인을 모두 테스트한다.
따라서 4/5 = 80% 이다
이제 메서드를 리펙터링하면 어떻게 될까?
public static bool IsStringLong(string input) {
return input.Length > 5;
}이렇게 변경 시 커버리지는 100%가 된다
- 리펙터링으로 테스트 스위트를 개선했는가? No
- 커버리지가 증가하였는가? Yes
코드가 적으면 테스트 커버리지는 증가하는데, 이는 애플리케이션의 유지보수성을 변경하지 않는다
분기 커버리지
- 분기 커버리지 = 통과 분기 / 전체 분기 수
public static bool IsStringLong(string input) {
return input.Length > 5;
}
public void Test() {
bool result = IsStringLong("abc");
Assert.Equal(false, result);
}- 위 테스트 코드의 분기 커버리지는 1/2 로 50%이다
- 그러나 위의 지표 또한 높다고 반드시 올바른 테스트라고 말할 수 없다
테스트 대상 시스템의 모든 결과를 검증한다 볼 수 없음
public static bool WasLastStringLong {get; private set;}
public static bool IsStringLong(string input) {
bool result = input.Length > 5;
WasLastStringLong = result; // 첫 번째 결과
return result; // 두 번째 결과
}
public void Test() {
bool result = IsStringLong("abc");
Assert.Equal(false, result); // 두 번째 결과만 검증
}- IsStringLong 메서드는 값을 반환 + 속성에 새로운 값을 쓰는 결과가 있음
WasLastStringLong를 검증하지 않더라도 코드 커버리지와 브랜치 커버리지는 같음
public void Test() {
bool result1 = IsStringLong("abc");
bool result2 = IsStringLong("abcdef");
}이 테스트는 코드 커버리지와 분기 커버리지가 모두 100% 지만, 아무 의미가 없다
그렇다면, 테스트 코드 대상에 대한 결과를 철저히 검증한다면, 테스트 지표는 신뢰할 만한 지표가 될 수 있는가? 또 테스트 스위트 품질을 결정하는 데 사용할 수 있는가?
모든 커버리지 지표가 외부 라이브러리를 통과하는 경우
public static int Parse(string input) {
return int.Parse(input)
}
public void Test() {
int result = Parse("5");
Assert.Equal(5, result);
}위의 테스트는 int.Parse 메서드가 수행하는 코드 경로를 고려하지 않음. 입력으로 다음과 같은 값이 들어온다면
- null
- ""
- “정수가 아님”
- 너무 긴 문자열
라이브러리의 엣지 케이스에 대한 지표를 검증할 수 없다. 라이브러리의 코드 경로를 고려해야 한다는 이야기가 아님. 커버리지 지표로 테스트가 올바른지 검증할 수 없다는 의미
무엇이 성공적인 테스트 스위트를 만드는가
- 지표가 아니라면, 무엇으로 테스트 스위트가 성공적인지 검증할 수 있는가?
개발 주기에 통합되어 있다
- 모든 테스트가 개발 주기에 통합되어 있어야 함
- 코드가 변경될 때 마다 아무리 작은 것이라도 실행하여야 함
코드베이스에서 가장 중요한 부분을 대상으로 수행
- 시스템의 가장 중요한 부분에 단위 테스트를 추가한다
- 보통은 비즈니스 로직(도메인 모델) 이 있는 곳이 코드베이스에서 가장 중요한 곳임
- 다른 부분은 간략하게, 간접적으로 검증
코드 범주
- 인프라 코드
- 데이터베이스나 서드파티 시스템, API 호출과 같은 외부 서비스 종속성
- 모든 것을 하나로 묶는 코드
인프라 코드가 중요할 수도 있다. 통합 테스트 같이 도메인 모델을 넘어서 시스템이 어떻게 작동하는지 확인할 수도 있다. 그러나 대부분은 도메인 모델을 테스트로 검증하여야 함
최소 유지비로 최대 가치를 끌어냄
- 가치 있는 테스트를 식별하여야 한다
- 가치 있는 테스트를 식별하려면 나름의 기준이 필요함
- 가치 있는 테스트를 작성하여야 한다
- 나름의 기준과 더하여 코드 설계 기술도 알아야 함