[책 리뷰 - 단위 테스트] 1. 단위 테스트의 목표

Jeongyeon Kim·2024년 6월 22일
0
post-thumbnail

단위 테스트는 최소한의 노력으로 최대한의 이득을 내야 한다.

1. 단위 테스트 현황

단위 테스트가 중요하고, 필수적이라는 인식은 자리 잡았으나,
좋은 단위 테스트를 작성하는 것은 어떤 것인지에 대한 의미는 혼란스러운 상태

2. 단위 테스트의 목표

단위테스트의 목표는 소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것.


테스트가 부재하면 처음에는 빠른 시작이 가능하나 이후 시간이 지날 수록 개발 속도가 느려진다.
소프트웨어 엔트로피(software entropy): 개발 속도가 빠르게 감소하는 현상
단위 테스트는 지속성확장성이 핵심

2.1 좋은 테스트와 좋지 않은 테스트를 가르는 요인

테스트의 가치와 유지 비용을 모두 고려해야 한다.

  • 기반 코드를 리팩토링 할 때 테스트 코드도 리팩토링
  • 각 코드 변경 시 테스트 실행
  • 테스트가 잘못된 경고를 발생시킬 경우 처리
  • 기반 코드가 어떻게 동작하는지 이해하려고 할 때는 테스트를 읽는 데 시간 투자
제품 코드 vs. 테스트 코드
  • 코드는 자산이 아니라 책임
  • 코드가 많아 진다 = 잠재적인 버그에 노출될 확률 높다 = 프로젝트 유지비가 증가한다
  • 테스트 코드도 코드이기에 다른 코드와 마찬가지로 지속적인 리팩토링 필요

3. 테스트 스위트 품질 측정을 위한 커버리지 지표

커버리지는 좋은 부정 지표이지만 나쁜 긍정 지표

3.1 코드 커버리지 지표에 대한 이해

코드 커버리지(테스트 커버리지) = 실행 코드 라인 수 / 전체 라인 수

  • 하나 이상의 테스트로 실행된 코드 라인 수와 제품 코드 베이스의 전체 라인 수의 비율
public boolean isStringLong(String input) {
	if (input.length > 5)
    	return true;
    return false;
}

public void Test() {
	boolean result = isStringLong("abc");
    Assertions.assertThat(result).isFalse();
}

이 경우 코드 커버리지는 4/5 = 0.8 = 80%

public boolean isStringLong(String input) {
	return input.length > 5;
}

public void test() {
	boolean result = isStringLong("abc");
    Assertions.assertThat(result).isFalse();
}

위 코드를 리팩토링하여 코드 커버리지를 100%로 만들었다.
하지만 이는 메서드 내의 코드만 바꿨을 뿐 검증하는 결과의 개수는 동일하다.
코드 커버리지는 라인 수만 처리하기 때문에 코드가 작을 수록 유리하다.

3.2 분기 커버리지 지표에 대한 이해

분기 커버리지 = 통과 분기 / 전체 분기 수

  • 테스트 스위트 내 하나 이상의 테스트가 통과하는 제어 구조의 수
public boolean isStringLong(String input) {
	return input.length > 5;
}

public void test() {
	boolean result = isStringLong("abc");
    Assertions.assertThat(result).isFalse();
}

이 경우 분기 커버리지는 1/2 = 50%이다.
코드를 길게 작성하던 짧게 작성하던 분기의 개수는 동일하기에 분기 커버리지는 동일하다.

3.3 커버리지 지표에 대한 문제점

테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.

public class Example {
	private boolean wasLastStringLong;
    
    public boolean isStringLong(String input) {
    	boolean result = input.length > 5;
        wasLastStringLong = result;
        return result;
    }
    
    public void test() {
        boolean result = isStringLong("abc");
        Assertions.assertThat(result).isFalse();
    }
}

wasLastStringLong에 result 값을 쓰는 암묵적인 결과를 검증하지 않아도
코드 커버리지 100%와 분기 커버리지 50%의 결과를 보인다.

public void test() {
    boolean result1 = isStringLong("abc");
    boolean result2 = isStringLong("abcdef");
}

test() 메서드를 위와 같이 바꾸면,
코드 커버리지 100%, 분기 커버리지 100%를 나타낸다.
그러나 이 메서드는 아무것도 검증하지 않기 때문에 쓸모가 없는 테스트 코드이다.

외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.

public int parse(String input) {
	return Integer.parseInt(input);
}

public void test() {
	int result = parse("5");
    Assertions.assertThat(result).isEqualTo(5);
}

분기 커버리지는 100%
그러나 Integer.parseInt()가 수행하는 코드 경로는 고려되지 않는다.

3.4 특정 커버리지 숫자를 목표로 하기

커버리지 지표는 지표 그 자체로 보는 것이지, 목표로 여겨서는 안된다.

4. 무엇이 성공적인 테스트 스위트를 만드는가?

4.1 개발 주기에 통합되어 있다.

모든 테스트는 개발 주기에 통합 되어야 한다.
이상적으로는 코드가 변경될 때마다 작은 테스트 코드라도 실행해야 한다.

4.2 코드베이스에서 가장 중요한 부분만을 대상으로 한다.

비즈니스 로직(도메인 모델)을 테스트 하는 것에 집중하여 노력을 기울여야 한다.

4.3 최소 유지비로 최대 가치를 끌어낸다.

가치 있는 테스트(+ 가치가 낮은 테스트)를 식별한다.
가치 있는 테스트를 작성한다.


📚 내 생각

단순히 단위 테스트 코드를 작성해야 하고, 단위 테스트를 작성하는 것이 좋다는 것만 알고 있었지
그것이 왜 필요한지, 단위 테스트는 무엇을 검증하는 것인지, 좋은 단위 테스트란 무엇인지 등
단위 테스트의 목표에 대해 생각해보지는 못했었다.

1장의 내용을 통해 단위 테스트의 목표가 무엇인지 알 수 있었고,
지금까지 내가 작성해왔던 단위 테스트, 혹은 통합 테스트가 좋은 테스트 코드였는가에 대한 생각을 해보는 계기가 되었다.

이 책을 읽고 단위 테스트에 대한 이해를 통해
단위 테스트를 작성할 때 애플리케이션의 지속 가능한 성장을 염두에 두고,
최소한의 유지비로 최대한의 가치를 끌어내는 좋은 테스트 코드를 작성하며
나와 애플리케이션이 모두 성장하는 테스트 코드를 작성해야겠다는 생각을 했다.

profile
Backend Developer👩🏻‍💻

0개의 댓글