일반적인 코드 적용 범위의 네 가지 유형

코드 적용 범위가 무엇인지 알아보고 이를 측정하는 일반적인 4가지 방법을 살펴보세요.

'코드 적용 범위'라는 문구를 들어 보셨나요? 이 게시물에서는 테스트의 코드 적용 범위와 이를 측정하는 네 가지 일반적인 방법을 살펴봅니다.

코드 적용 범위란 무엇인가요?

코드 적용 범위는 테스트에서 실행하는 소스 코드의 비율을 측정하는 측정항목입니다. 이렇게 하면 적절한 테스트가 부족할 수 있는 영역을 식별하는 데 도움이 됩니다.

이러한 측정항목은 보통 다음과 같이 기록됩니다.

파일 문(%) 분기 비율 % 함수 선% 커버되지 않은 선
file.js 90% 100% 90% 80% 89,256개
coffee.js 55.55% 80% 50% 62.5% 10~11세, 18세

새로운 기능과 테스트를 추가할 때 코드 적용 범위 비율이 증가하면 애플리케이션이 철저하게 테스트되었다는 확신을 더할 수 있습니다. 하지만 아직 발견할 수 있는 것이 더 많이 있습니다.

코드 적용 범위의 네 가지 일반적인 유형

코드 적용 범위는 함수, 선, 브랜치, 문 적용 범위라는 4가지 일반적인 방법으로 수집하고 계산할 수 있습니다.

4가지 유형의 텍스트 범위

각 코드 적용 범위 유형이 백분율을 계산하는 방법을 알아보려면 커피 재료를 계산하는 다음 코드 예를 참고하세요.

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

calcCoffeeIngredient 함수를 확인하는 테스트는 다음과 같습니다.

/* coffee.test.js */

import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown');
    expect(result).to.deep.equal({});
  });
});

라이브 데모에서 코드와 테스트를 실행하거나 저장소를 확인해 보세요.

함수 범위

코드 적용 범위: 50%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
}

function isValidCoffee(name) {
  // ...
}

함수 범위는 간단한 측정항목입니다. 이는 테스트에서 호출하는 코드의 함수 비율을 캡처합니다.

코드 예에는 calcCoffeeIngredientisValidCoffee의 두 함수가 있습니다. 테스트는 calcCoffeeIngredient 함수만 호출하므로 함수 적용 범위는 50%입니다.

선 적용 범위

코드 적용 범위: 62.5%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

줄 적용 범위는 테스트 모음이 실행한 실행 가능한 코드 줄의 비율을 측정합니다. 코드 한 줄이 실행되지 않은 상태로 남아 있다면 코드의 일부가 테스트되지 않았음을 의미합니다.

코드 예에는 8줄의 실행 코드 (빨간색과 녹색으로 강조표시됨)가 있지만 테스트에서는 americano 조건 (2줄)과 isValidCoffee 함수 (1줄)를 실행하지 않습니다. 따라서 라인 커버리지는 62.5%가 됩니다.

선언문(예: function isValidCoffee(name)let espresso, water;)은 실행 파일이 아니므로 줄 범위는 고려되지 않습니다.

지점 범위

코드 적용 범위: 80%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...

  if (coffeeName === 'espresso') {
    // ...
    return { espresso };
  }

  if (coffeeName === 'americano') {
    // ...
    return { espresso, water };
  }

  return {};
}
…

브랜치 적용 범위는 코드에서 실행된 브랜치 또는 결정 지점(예: if 문 또는 루프)의 비율을 측정합니다. 테스트가 조건문의 참과 거짓 분기를 모두 검사할지 여부를 결정합니다.

코드 예시에는 5개의 브랜치가 있습니다.

  1. coffeeName체크 표시.calcCoffeeIngredient에 전화 거는 중
  2. coffeeName님, cup 체크 표시.님과 calcCoffeeIngredient님에게 전화 거는 중
  3. 커피는 에스프레소입니다 체크 표시.
  4. X 표시를 클릭합니다. 커피는 아메리카노입니다.
  5. 기타 커피 체크 표시.

테스트는 Coffee is Americano 조건을 제외한 모든 브랜치를 다룹니다. 따라서 브랜치 커버리지는 80%입니다.

명세서 범위

코드 적용 범위: 55.55%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

문 적용 범위는 테스트가 실행하는 코드의 문 비율을 측정합니다. 언뜻 보기에는 '선 적용 범위와 같지 않나요?'라는 의문이 들 수 있습니다. 실제로 문 적용 범위는 줄 적용 범위와 비슷하지만 여러 문이 포함된 한 줄의 코드를 고려합니다.

코드 예시에는 8줄의 실행 코드가 있지만 9개의 문이 있습니다. 두 개의 진술이 포함된 줄을 찾을 수 있나요?

정답 확인하기

다음 줄입니다. espresso = 30 * cup; water = 70 * cup;

테스트에서는 9개의 설명 중 5개만 다루므로 명세서 사용 범위는 55.55%입니다.

항상 한 줄에 하나의 문을 작성하면 줄 적용 범위는 문 적용 범위와 비슷해집니다.

어떤 유형의 코드 적용 범위를 선택해야 할까요?

대부분의 코드 적용 범위 도구에는 다음 네 가지 유형의 일반적인 코드 적용 범위가 포함됩니다. 우선순위를 지정할 코드 범위 측정항목을 선택하는 것은 특정 프로젝트 요구사항, 개발 관행, 테스트 목표에 따라 달라집니다.

일반적으로 명세서 범위는 간단하고 이해하기 쉬운 측정항목이므로 좋은 출발점이 됩니다. 문 적용 범위와 달리 분기 적용 범위 및 함수 적용 범위는 테스트가 조건 (브랜치) 또는 함수 호출 여부를 측정합니다. 따라서 구문 적용 후에 자연스럽게 진행됩니다.

높은 명세서 범위를 달성한 후에는 브랜치 적용 범위 및 함수 적용 범위로 이동할 수 있습니다.

테스트 적용 범위가 코드 적용 범위와 동일한가요?

아니요. 테스트 적용 범위와 코드 적용 범위는 혼동되는 경우가 많지만 다릅니다.

  • 테스트 범위: 테스트 모음이 소프트웨어의 기능을 얼마나 잘 다루는지를 측정하는 정성적 측정항목입니다. 관련된 위험 수준을 확인하는 데 도움이 됩니다.
  • 코드 적용 범위: 테스트 중에 실행된 코드의 비율을 측정하는 정량적 측정항목입니다. 테스트에 포함되는 코드의 양에 관한 것입니다.

간단한 비유를 들어보겠습니다. 웹 애플리케이션을 집이라고 상상해 보세요.

  • 테스트 범위는 테스트가 집안의 방을 얼마나 잘 포괄하는지 측정합니다.
  • 코드 적용 범위는 테스트가 얼마나 통과했는지 측정합니다.

100% 코드 적용이 버그가 없다는 의미는 아닙니다

테스트 시 높은 코드 적용 범위를 달성하는 것이 분명히 바람직한 일이지만 100% 코드 적용 범위가 코드에 버그나 결함이 없음을 보장하지는 않습니다.

100% 코드 커버리지를 달성하기 위한 무의미한 방법

다음 테스트를 고려해 보세요.

/* coffee.test.js */

// ...
describe('Warning: Do not do this', () => {
  it('is meaningless', () => { 
    calcCoffeeIngredient('espresso', 2);
    calcCoffeeIngredient('americano');
    calcCoffeeIngredient('unknown');
    isValidCoffee('mocha');
    expect(true).toBe(true); // not meaningful assertion
  });
});

이 테스트는 함수, 행, 브랜치 및 명령문 적용 범위를 100% 달성하지만, 실제로 코드를 테스트하지는 않기 때문에 의미가 없습니다. expect(true).toBe(true) 어설션은 코드가 올바르게 작동하는지와 관계없이 항상 통과합니다.

잘못된 측정항목은 측정항목이 없는 것보다 더 좋지 않습니다.

잘못된 측정항목은 보안에 대한 잘못된 인식을 줄 수 있으며, 이는 측정항목이 전혀 없는 것보다 더 좋지 않습니다. 예를 들어 100% 코드 적용 범위를 달성하는 테스트 모음이 있지만 모든 테스트가 무의미하다면 코드가 제대로 테스트되었다는 잘못된 보안 인식을 가질 수 있습니다. 애플리케이션 코드의 일부를 실수로 삭제하거나 중단하면 애플리케이션이 더 이상 올바르게 작동하지 않더라도 테스트는 계속 통과됩니다.

이러한 시나리오를 방지하려면 다음과 같이 하세요.

  • 테스트 검토. 테스트를 작성 및 검토하여 의미가 있는지 확인하고 다양한 시나리오에서 코드를 테스트합니다.
  • 코드 적용 범위를 테스트 효과나 코드 품질의 유일한 측정 기준이 아니라 가이드라인으로 사용하세요.

다양한 유형의 테스트에서 코드 적용 범위 사용

세 가지 일반적인 테스트 유형에서 코드 적용 범위를 사용하는 방법을 자세히 살펴보겠습니다.

  • 단위 테스트: 여러 소규모 시나리오와 테스트 경로를 포함하도록 설계되었으므로 코드 적용 범위를 수집하는 데 가장 적합한 테스트 유형입니다.
  • 통합 테스트: 통합 테스트를 위한 코드 적용 범위를 수집하는 데 도움이 될 수 있지만 주의해서 사용해야 합니다. 이 경우 소스 코드의 더 큰 부분에 대한 커버리지를 계산하므로 어느 테스트가 실제로 코드의 어느 부분을 다루는지 결정하기 어려울 수 있습니다. 그럼에도 불구하고 통합 테스트의 코드 적용 범위를 계산하는 것은 잘 격리된 단위가 없는 기존 시스템에 유용할 수 있습니다.
  • 엔드 투 엔드 (E2E) 테스트. E2E 테스트의 코드 적용 범위는 복잡하기 때문에 측정하기가 까다롭고 어렵습니다. 코드 적용 범위를 사용하는 대신 요구사항 적용 범위가 더 나은 방법일 수 있습니다. 이는 E2E 테스트가 소스 코드에 집중하는 것이 아니라 테스트의 요구사항을 충족하는 데 중점을 두기 때문입니다.

결론

코드 적용 범위는 테스트의 효과를 측정하는 데 유용한 측정항목일 수 있습니다. 코드의 중요한 로직을 제대로 테스트함으로써 애플리케이션의 품질을 개선하는 데 도움이 될 수 있습니다.

그러나 코드 적용 범위는 하나의 측정항목일 뿐입니다. 또한 테스트 품질이나 애플리케이션 요구사항과 같은 다른 요소도 고려해야 합니다.

100% 코드 적용 범위를 목표로 하는 것은 아닙니다. 대신 단위 테스트, 통합 테스트, 엔드 투 엔드 테스트, 수동 테스트 등 다양한 테스트 방법을 통합한 균형 잡힌 테스트 계획과 함께 코드 적용 범위를 사용해야 합니다.

전체 코드 예시를 확인하고 코드 적용 범위가 적절한 테스트를 확인하세요. 이 라이브 데모로 코드와 테스트를 실행할 수도 있습니다.

/* coffee.js - a complete example */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  if (!isValidCoffee(coffeeName)) return {};

  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  throw new Error (`${coffeeName} not found`);
}

function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */

import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have americano', () => {
    const result = calcCoffeeIngredient('americano');
    expect(result.espresso).to.equal(30);
    expect(result.water).to.equal(70);
  });

  it('should throw error', () => {
    const func = () => calcCoffeeIngredient('mocha');
    expect(func).toThrowError(new Error('mocha not found'));
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown')
    expect(result).to.deep.equal({});
  });
});