단위 테스트의 목표

theonde·2022년 9월 8일
0

단위 테스트의 목표

  • 테스트에 드는 노력을 가능한 한 줄이고 그에 따르는 이득을 최대화 해야 한다.

단위 테스트 현황

  • 프로덕션 코드와 테스트 코드의 비율은 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%가 된다.

커버리지 지표에 관한 문제점

  • 2가지 문제점이 있다.
  1. 테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.
    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%의 결과를 보여준다.

  • 일부 실행된 것만 보장된다.

  1. 외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.
    @Test
    void test() {
        int result = parseInt("5");
        assertThat(result).isEqualTo(5);
    }

    private int parseInt(String input) {
        return Integer.parseInt(input);
    }
  • 해당 테스트는 정수만 테스트 하고 있다.

  • null, 정수가 아닌 값, 공백에 대한 테스트가 되지 않아도 분기 커버리지는 100%로 표시된다.

  • 테스트에서 이러한 예외 상황을 다루는지 확인할 방법이 없다.

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

  • 테스트를 작성할 때 커버리지 지표를 목표로 삼기 시작하면 위험 영역으로 이어질 수 있다.

  • 커버리지 지표를 보는 가장 좋은 방법은 지표 그 자체로 보는 것이며, 목표로 여겨서는 안된다.

성공적인 테스트 스위트를 만드는 요인

  • 테스트 스위트의 품질을 측정하는 방법은 각 테스트를 하나씩 따로 평가하는 것 뿐이다.

  • 요점은 테스트 스위트가 얼마나 좋은지 자동으로 확인할 수 없다. 개인 판단에 맡겨야 한다.

  • 성공적인 테스트 스위트의 특징

- 개발 주기에 통합돼 있다.

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

- 최소한의 유지비로 최대의 가치를 끌어낸다.

개발 주기에 통합돼 있음

  • 자동화된 테스트를 할 수 있는 방법은 끊임없이 하는 것 뿐이다.

  • 코드가 변경될 때마다 아무리 작은 것이라도 실행해야 한다.

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

  • 시스템의 가장 중요한 부분에 단위 테스트 노력을 기울이고, 다른 부분은 간략하게 또는 간접적으로 검증하는것이 좋다.

  • 대부분의 애플리케이션에서 가장 중요한 부분은 비즈니스 로직(도메인 모델)이 있는 부분이다.

  • 다른 부분은 세 가지 범주로 나눌 수 있다.

- 인프라 코드

- 데이터베이스나 서드파티 시스템과 같은 외부 서비스 및 종속성

- 모든 것을 하나로 묶는 코드
  • 이 중 일부는 단위 테스트를 철저히 해야 한다.
ex) 인프라 코드에 중요한 알고리즘이 있을수도 있다.
  • 이 지침을 따르려면 도메인 모델을 코드베이스 중 중요하지 않은 부분과 분리해야 한다.

  • 도메인 모델을 다른 애플리케이션 문제와 분리해야 단위 테스트에 대한 노력을 도메인 모델에만 집중할 수 있다.

최소 유지비로 최대 가치를 끌어냄

  • 단위 테스트에서 가장 어려운 부분은 최소 유지비로 최대 가치를 달성하는 것이다.

  • 가치가 유지비를 상회하는 테스트만 스위트에 유지하는 것이 중요하다.

- 가치 있는 테스트(가치가 낮은 테스트) 식별하기

- 가치 있는 테스트 작성하기
  • 가치 있는 테스트를 식별하려면 기준틀이 필요하다.

  • 가치 있는 테스틀 작성하려면 코드 설계 기술도 알아야 한다.

  • 단위 테스트와 기반 코드는 서로 얽혀 있으므로 코드베이스에 노력을 많이 기울이지 않으면 가치 있는 테스트를 만들 수 없다.

profile
개발자ㅋ.ㅋ

0개의 댓글