Unit Testing 단위테스트 정리

Crow·2022년 8월 5일
0

Unit Testing

목록 보기
1/5

이 책의 목표

  • 제품코드와 관련 테스트 스위트를 리팩터링하는 방법
  • 단위 테스트를 다양한 스타일로 적용하는 방법
  • 통합 테스트로 시스템 전체 동작 검증하기
  • 단위 테스트 안티 패턴을 식별하고 예방하기

단위테스트란 ?

가장 중요한 3가지로 정리하면

  • 작은 코드 조각(단위)을 검증하고,
  • 빠르게 수행하고,
  • 격리된 방식으로 처리하는 자동화된 테스트

이다 하지만 3번째 속성인 격리문제는 단위테스트의 고전파와 런던파를 구분할 수 있게 해주는 근원적 차이가 존재함.

런던파와 고전파의 차이

대상격리 주체단위의 크기테스트 대역 사용대상
런던파단위단일 클래스불변 의존성 외 모든 의존성
고전파단위 테스트단일 클래스 또는 클래스 세트공유 의존성

단위 테스트의 목표

소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것

단위테스트를 배우는 것은 테스트 프레임워크나 목 라이브러리(mocking library)등과 같은 기술적인 부분을 익히는 것에 그치지 않음.

단위 테스트에 시간을 투자할 때는 항상 테스트에 드는 노력을 줄이고 이득을 최대화 해야함.

따라서 테스트에 대한 비용 편익 분석(cost-benefit analysis)을 사용해서 특정 상황에 적절한 테스트 기술을 적용할 수 있음
또한 공통적인 안티패턴(anti-pattern)을 피해서 작성해야한다.

단위 테스트 현황

기업용 애플리케이션(enterprise application)개발에는 거의 모두 단위 테스트가 적용돼 있고 상당수는 수많은 단위 테스트와 통합 테스트를 통해 좋은 코드 커버리지(code coverage)를 달성하고 있음

일반적으로 제품코드(production)와 테스트 코드 비율은 1:1에서 1:3(제품 코드 한줄 : 테스트 코드 세 줄)정도지만 때론 1:10 수준에 이르기도 함

다른 기술과 마찬가지로 단위 테스트 또한 발전하고 있음

단위 테스트 사용이유

단위 테스트를 사용하지 않은 프로젝트들은 처음에는 빠른 진척을 보이지만 코드베이스에서 무언가를 변경할때 마다 무질서도가 증가해

결국 개발속도가 현저히 느려지고 심지어 전혀 진행하지 못할정도로 느려짐

이런 개발 속도가 빠르게 감소하는 현상을 소프트웨어 엔트로피(sofrware entropy)라고도 함

따라서 테스트로 이러한 경향을 뒤집어 안전망 역활을 하고,
대부분의 희귀(regression)에 대한 보험을 제공하는 도구라 할 수있음

하지만 프로젝트 초반에 상당한 노력이 필요한 단점이 존재한다
그러나 앞서 말했듯이 프로젝트 후반에도 잘 성정할 수 있어서
장기적으로 그 비용을 메울수있다.

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

가장 널리 사용되는 두 가지 커버리지 지표(코드 커버리지와 분기커버리지)가 존재함 일반적으론 커버리지 숫자가 높을수록 더 좋다고 생각하지만,

실상은 그렇게 간단한 문제가 아님 지표는 중요한 피드백을 주긴하지만 품질을 효과적으로 측정하는 데 사용될 순 없음

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

이 지표는 하나 이상의 테스트로 실행된 코드 라인수와 제품 코드베이스의 전체 라인수의 비율을 나타냄

				실행 코드 라인수				
코드 커버리지 = --------------------
				전체 라인수

간단하게 예제를 하나 보겠음

    public static Boolean IsStringLong(String input) {
        if (input.length() > 5) {
            return false;
        }
        return true;
    }

    public static void main(String[] args) {
        boolean result = IsStringLong("abc");
        System.out.println(result);
    }

해당 예제는 제공된 문자열의 길이가 긴지를 판별하는 코드임
해당 코드에서 테스트가 실행하는 라인수는 4이며
테스트는 true를 반환하는 구문을 제외한 모든 코드라인을 통과함
따라서 코드 커버리지는 4/5 = 0.8 = 80%이다

이제 메서드를 리팩터링하고 불필요한 if문을 한줄로 처리하면 어떻게 될지 보자

    public static Boolean IsStringLong(String input) {
        return input.length() > 5;
    }

    public static void main(String[] args) {
        boolean result = IsStringLong("abc");
        System.out.println(result);
    }

리팩터링을 하고 코드 커버리지 숫자는 100퍼로 증가했다.
하지만 테스트 스위트를 개선하지는 못했다
단지 메서드 내 코드만을 바꿀뿐
해당 테스트가 검증하는 결과 개수는 여전히 같다

이 예제는 커버리지 숫자에 대해 얼마나 쉽게 장난칠수 있는지 보여준다

따라서 커버리지 지표는 괜찬은 부정 지표가 맞지만
좋지 않은 긍정 지표이다

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

이 지표는 테스트 스위트가 수행하는 코드 분기 수와 제품 코드베이스의 전체 분기 수에 대한 비율을 계산함

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

이 전의 예를 다시 보겠음

    public static Boolean IsStringLong(String input) {
        return input.length() > 5;
    }

    public static void main(String[] args) {
        boolean result = IsStringLong("abc");
        System.out.println(result);
    }

IsStringLong 메서드에 두 개의 분기가 있는데, 하나는 문자열 인수의 길이가 다섯 자를 초과하는 상황에 대한 것이고 다른 하나는 그렇지 않은 경우임

테스트는 이런 분기중 하나에 대해서만 적용되므로 분기 커버리지 지표는 1/2 = 0.5 = 50퍼이다
이전과 같이 if 문을 사용하던 말던 상관이 없다

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

분기 커버리지로 코드 커버리지보다 더 나은 결과를 얻을 수 있지만,
테스트 스위트의 품질을 결정하는 데 어떤 커버리지 지표도 의존할 수 없는 이유는 다음과 같음

  • 테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.
  • 외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.

단지 코드 경로를 통과하는 것이 아니라 실제로 테스트하려면,
단위 테스트에는 반드시 적절한 검증이 있어야함

다음으로 검증이 없는 테스트는 언제나 통과한다.
이 예제는

    public static void main(String[] args) {
        boolean result1 = IsStringLong("abc");
        boolean result2 = IsStringLong("abcdfe");
        System.out.println(result1);
        System.out.println(result2);
    }

코드 커버리지와 분기 커버리지가 둘다 100%를 나타내고 있다 그러나 검증하지 않기 때문에 전혀 쓸모가 없다.

테스트 대상 코드에 대해 각각의 결과를 철저히 검증한다면 분기 커버리지 지표와 함께 신뢰할 수 있는 구조라고 할 수 있나?
물론 아니다

두번째 문제는 모든 커버리지 지표가 테스트 대상 시스템이 메서드를 호출할 때 외부 라이브러리가 통과하는 코드 경로를 고려하지 않는다는점임

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

    @Test
    public static void main(String[] args) {
        int result = Parse("5");

        assertEquals(result, 5);

    }

분기 커버리지 지표는 100%로 표시되며, 테스트는 메서드 결과의 모든 구성 요소를 검증함

단지 값을 반환하는 한 줄이라 하더라도 단일한 구성 요소이기는 하지만,
이 테스트는 역시 완벽하지 못함

위 코드와 같은 간단한 메서드에도 꽤 많은 코드 경로가 존재하며
매개변수를 변경하면 다른결과로 이어질수있고, 테스트로부터 숨어있는 분기들이 많다(null , 빈 문자열)

수만은 예외 상황에 빠질 수 있지만, 테스트가 모든 예외상황을 다루는지 확인할 방법이 없음

이는 커버리지 지표가 외부 라이브러리의 코드 경로를 고려해야 한다는것이 아닌(고려하면 안됨)

해당 지표로는 단위 테스트가 얼마나 좋은지 나쁜지를 판단할 수 없다는것을 보여줌

따라서 커버리지 지표로 테스트를 판단할 수 없다.

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

커버리지 지표를 보는 가장 좋은 방법은 지표 그자체로 보는것이지 목표로 여겨서는 안됨

마찬가지로 특정 커버리지 숫자를 목표로 한다는 것은 단위 테스트의 목표와 반대되는 그릇된 동기 부여임

사람들은 높은 커버리지 숫자를 목표로 하는 순간 테스트 하는데 집중하는것이 아닌 높은 숫자를 달성하기 위한 방법을 찾기 시작해 테스트 본질이 달라짐

물론 시스템 핵심 부분은 커버리지를 높게 두는편이 좋음 하지만 이 높은 수준을 요구사항으로 삼는것은 좋지 않음

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

테스트 스위트의 품질을 측정하기 위해선 스위트 내 각 테스트를 하나씩 따로 평가하는것뿐이다.

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

  • 개발 주기에 통합돼 있다.
  • 코드베이스에서 가장 중요한 부분만을 대상으로 한다.
  • 최소한의 유지비로 최대의 가치를 끌어낸다



참고

collaborator: java에선 프로그램에서 작동하는 objects외 다른 objects를 뜻함
Ex: 로켓객체에서 엔진과 연료등이 있음

비용 편익 분석(cost-benefit analysis): 여러 가지 대안에 대해 비용과 이익을 분석해서 가장효과적인 대안을 찾는 방법론

안티패턴(anti-pattern): 처음에는 괜찮은것 같지만 미래에 문제를 야기하는 패턴

기업용 애플리케이션(enterprise application): 조직 내부 프로세스를 자동화 하거나 지원하기 위한 응용프로그램이며
다양한 형태지만 일반적으로 다음과 같은 특성이 존재

  • 높은 비즈니스 복잡도
  • 긴 프로젝트 수명
  • 중간 크기의 데이터
  • 낮은 수준이나 중간 수준 정도의 성능 요구

희귀(regression) : 특정사건(코드수정등)후에 기능이 의도한 대로 작동하지 않는 경우

커버리지 지표: 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타냄

profile
어제보다 개발 더 잘하기 / 많이 듣고 핵심만 정리해서 말하기 / 도망가지 말기 / 깃허브 위키 내용 가져오기

0개의 댓글