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

코드 적용 범위의 정의와 이를 측정하는 일반적인 4가지 방법을 알아보세요.

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

코드 적용 범위는 테스트에서 실행되는 소스 코드의 비율을 측정하는 측정항목입니다. 이를 통해 적절한 테스트가 이루어지지 않은 영역을 파악할 수 있습니다.

이러한 측정항목을 기록하면 다음과 같은 결과가 나오는 경우가 많습니다.

파일 % 문 % 브랜치 % 함수 % Lines 노출되지 않은 선
file.js 90% 100% 90% 80% 89,256
coffee.js 55.55% 80% 50% 62.5% 10-11, 18

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

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

코드 적용 범위를 수집하고 계산하는 일반적인 방법에는 함수, 행, 브랜치, 문이 있습니다.

텍스트 노출 범위의 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 문이나 루프와 같이 코드에서 실행된 분기 또는 결정 지점의 비율을 측정합니다. 테스트에서 조건문의 true 및 false 브랜치를 모두 검사할지 결정합니다.

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

  1. coffeeName 체크표시만으로 calcCoffeeIngredient 호출
  2. coffeeNamecup 체크표시를 사용하여 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({});
  });
});