본문 바로가기

문장·분기·조건 검증

코드 구조에 따른 커버리지 기준을 구분하고 단락 평가와 독립 조건 효과를 실제 사례로 추적합니다.

정원 거절 경로를 한 번 실행했다는 사실과 모든 거절 이유를 확인했다는 사실은 다릅니다. 커버리지는 정한 대상 중 테스트가 실행하거나 만족한 대상의 비율입니다. 먼저 무엇을 셀지 정하지 않으면 “100% 검증”이라는 말은 의미가 모호합니다.

서로 다른 대상

문장 커버리지는 실행 가능한 문장이 실행됐는지, 분기 커버리지는 결정의 각 결과 경로가 실행됐는지, 조건 커버리지는 복합 결정 안의 각 조건이 참·거짓으로 평가됐는지 봅니다. 경로 커버리지는 분기 조합의 전체 실행 경로를 다루지만 반복문이 있으면 가능한 경로가 매우 많거나 끝이 없을 수 있습니다.

다음 코드를 coverage-check.mjs에 저장합니다.

const cases = [[2, 0], [1, 1]];
for (const [x, y] of cases) {
  const a = x > 1;
  const b = y === 0;
  const decision = a && b;
  console.log({ x, y, a, b, decision });
}

수학적 조건값은 첫 사례에서 a=true,b=true, 둘째에서 둘 다 false입니다. 결정은 참과 거짓을 모두 가집니다. 하지만 원래 식을 if (x > 1 && y === 0)로 직접 실행하면 첫 조건이 거짓인 둘째 사례에서는 y === 0을 평가하지 않습니다. 이것이 단락 평가입니다. 위 코드는 두 조건값을 설명하기 위해 따로 계산한 것이며 실제 평가 경로 측정과 구분해야 합니다.

조건이 실제로 참·거짓으로 평가되도록 [2,1]을 더하면 x>1은 참인 채 y===0만 거짓으로 평가됩니다. 분기 참·거짓을 얻었다고 각 조건의 참·거짓 실행을 자동으로 얻는 것은 아닙니다.

SOFTWARE ENGINEERING · READING CLUB

같은 기준 사례에서 조건 하나씩 바꾸어 비교한다

A: x > 1, B: y === 0, 결정: A && B. 각 비교는 첫 기준 사례와 연결합니다.

같은 기준 사례에서 조건 하나씩 바꾸어 비교한다기준 [2,0]은 A참 B참 결정참입니다. 기준과 [1,0]은 B의논리값을참으로고정하고 A만바꿉니다. 기준과 [2,1]은 A를참으로고정하고 B만바꿉니다. 뒤두사례를 서로비교하는것은 독립조건효과비교가아닙니다.기준 [2, 0]A 참 · B 참결정 참A만 변경 [1, 0]A 거짓 · B 논리값 참결정 거짓 / B 평가 생략B만 변경 [2, 1]A 참 · B 거짓결정 거짓 / B 실제 평가B 논리값 고정A 참 고정
같은 기준 사례에서 조건 하나씩 바꾸어 비교한다기준 [2,0]은 A참 B참 결정참입니다. 기준과 [1,0]은 B의논리값을참으로고정하고 A만바꿉니다. 기준과 [2,1]은 A를참으로고정하고 B만바꿉니다. 뒤두사례를 서로비교하는것은 독립조건효과비교가아닙니다.기준 [2, 0]A 참 · B 참결정 참A만 변경 [1, 0]A 거짓 · B 논리값 참결정 거짓 / B 평가 생략B만 변경 [2, 1]A 참 · B 거짓결정 거짓 / B 실제 평가B 논리값 고정A 참 고정

단락 평가에서는 A가 거짓이면 B를 실행하지 않습니다. 논리값 비교와 실제 조건 평가의 커버리지를 구분합니다.

조건 하나가 결과를 바꾸는지

x>1 && y===0에서 [2,0]과 [1,0]을 비교하면 y의 논리값을 고정하고 x 조건의 변화가 결정을 바꿉니다. [2,0]과 [2,1]을 비교하면 x를 고정하고 y 조건의 변화가 결정을 바꿉니다. 이런 독립 효과를 확인하는 생각은 MC/DC를 이해하는 출발점입니다. 실제 적용에서는 단락 평가와 마스킹 등 채택한 기준을 구체적으로 정해야 합니다.

제어 흐름 그래프는 문장 묶음을 노드, 이동 가능성을 간선으로 표현합니다. 순환 복잡도는 그래프의 독립 경로 수를 추정하는 지표입니다. 연결 요소가 P개인 그래프의 일반식은 E − N + 2P이며 한 프로그램의 단순 그래프는 P=1입니다. 어떤 조건·예외·루프를 노드와 간선으로 세었는지 없이 코드만 보고 수치를 비교하지 않습니다. 높다는 이유만으로 결함이 확정되는 것도 아닙니다.

작은 제어 흐름에서 계산하고 사례를 고른다

공식을 적는 것과 실행 경로를 찾는 것은 다른 작업입니다. cfg-check.mjs에 다음 코드를 저장해 실행합니다. 참가자 수는 0 이상의 정수, 정원은 1 이상의 정수라는 전제를 둡니다.

function seatText(count, capacity) {
  if (count >= capacity) return "FULL";
  return "AVAILABLE";
}
console.log(seatText(2, 2));
console.log(seatText(1, 2));

출력은 FULL, AVAILABLE 순서입니다. 두 return을 공통 종료 지점으로 연결하는 제어 흐름을 사용합니다. 노드와 간선을 다음과 같이 정하면 무엇을 세었는지 재현할 수 있습니다.

노드의미다음 노드
1count >= capacity 판정참이면 2, 거짓이면 3
2FULL 반환4
3AVAILABLE 반환4
4공통 종료없음

노드는 4개, 간선은 1→2·1→3·2→4·3→4의 4개, 연결 요소는 1개입니다. 따라서 V = 4 − 4 + 2 = 2입니다. 기저 경로는 1→2→4와 1→3→4로 잡고 각각 (count=2, capacity=2), (count=1, capacity=2)를 실행합니다. 첫 사례만 있으면 FULL 반환은 확인해도 빈자리 경로는 확인하지 못합니다. 시작 노드를 따로 추가해도 노드와 간선이 함께 하나씩 늘어 결과는 같습니다.

이 예제는 두 경로가 모두 가능한 단순한 코드입니다. 실제 프로그램에는 입력으로 실행할 수 없는 경로, 루프, 예외가 있을 수 있습니다. 순환 복잡도만큼 사례를 만들었다는 이유로 모든 경로·조건·요구의 옳음을 검증했다고 말하지 않습니다. 원자료의 기본 경로 테스트는 여기서 직접 계산하되, 루프·데이터 흐름 검사와 MC/DC의 여러 적용 기준은 후속 심화 범위로 둡니다.

독립 문제 1: 결정이 두 개일 때

아래 함수는 OPEN이 아니면 CLOSED, 열린 상태에서 만석이면 FULL, 그 외에는 AVAILABLE입니다. 모임 상태는 OPEN/CLOSED, 인원은 0 이상의 정수, 정원은 1 이상의 정수입니다.

function admissionText(status, count, capacity) {
  if (status !== "OPEN") return "CLOSED";
  if (count >= capacity) return "FULL";
  return "AVAILABLE";
}

두 판정 노드·세 반환 노드·공통 종료 노드를 사용해 간선 수와 순환 복잡도를 계산하고, 각 기저 경로의 입력과 기대 결과를 쓰세요. CLOSED 사례에서 만석 여부가 달라지면 어떤 경로가 추가되는지도 판단합니다.

힌트

첫 판정에서 CLOSED 반환으로 갈 때 두 번째 판정은 실행되지 않습니다. 반환 셋을 모두 공통 종료에 연결합니다.

해설

노드는 6개입니다. 첫 판정에서 CLOSED 반환 또는 둘째 판정으로 가는 간선 2개, 둘째 판정에서 FULL/AVAILABLE로 가는 간선 2개, 반환 셋에서 공통 종료로 가는 간선 3개라 총 7개입니다. V = 7 − 6 + 2 = 3입니다. (CLOSED, 0, 2)는 CLOSED, (OPEN, 2, 2)는 FULL, (OPEN, 1, 2)는 AVAILABLE 경로를 실행합니다. CLOSED에서 인원을 2로 바꾸어도 둘째 판정을 건너뛰므로 새 제어 경로가 생기지 않습니다. 인원·상태의 업무 규칙 검사는 이 경로 계산과 별도로 남깁니다.

코드 줄을 노드에 대응시키고 한 번씩 걷는다

방금 admissionText의 첫 if는 노드 1, 둘째 if는 노드 2, CLOSED/FULL/AVAILABLE return은 각각 3/4/5입니다. 노드 6은 코드에 적힌 별도 명령이 아니라 함수의 모든 반환을 합치는 개념적 종료점입니다. 그래프에서 capacity를 줄이지 않고 정원 판단을 표시합니다.

조건 두 개가 반환 경로 세 개를 나눈다

OPEN이 아닌지를 검사한다. 반환하면 호출이 끝난다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · READING CLUB

조건 두 개가 반환 경로 세 개를 나눈다

숫자는 본문의 admission 코드와 대응합니다. 조건이 거짓일 때만 다음 조건으로 진행합니다.

조건 두 개가 반환 경로 세 개를 나눈다노드1 status가 OPEN이 아닌지 판정: 참이면3 CLOSED 반환, 거짓이면2 정원 판정. 노드2 참가수 이상 정원: 참이면4 FULL 반환, 거짓이면5 AVAILABLE 반환. 3,4,5 모두 개념적 종료6에 합류. 여섯 노드와 일곱 변. 1 status!== OPEN?2 count>= capacity?3 returnCLOSED4 returnFULL5 returnAVAILABLE6 종료개념적 합류TFTF
조건 두 개가 반환 경로 세 개를 나눈다노드1 status가 OPEN이 아닌지 판정: 참이면3 CLOSED 반환, 거짓이면2 정원 판정. 노드2 참가수 이상 정원: 참이면4 FULL 반환, 거짓이면5 AVAILABLE 반환. 3,4,5 모두 개념적 종료6에 합류. 여섯 노드와 일곱 변. 1 status!== OPEN?2 count>= capacity?3 returnCLOSED4 returnFULL5 returnAVAILABLE6 종료개념적 합류TFTF

마름모는 조건, 사각형은 반환, 더 둥근 종료 상자는 개념적 합류입니다. 화살표는 가능한 제어 이동, T/F는 조건의 참/거짓입니다. 6은 별도 JavaScript 문장이 아닙니다. 넓은 화면과 좁은 화면 모두 N=6, E=7입니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 첫 판정부터 FULL 반환까지 진행합니다.
관계도의 보충 설명
전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명노드 1은 status가 OPEN이 아닌지 판정한다. 참이면 3 CLOSED 반환, 거짓이면 2 정원 판정으로 이동한다. 노드 2는 참가 수가 정원 이상인지 판정한다. 참이면 4 FULL 반환, 거짓이면 5 AVAILABLE 반환으로 이동한다. 3,4,5 모두 개념적 종료 6에 합류한다. 여섯 노드와 일곱 변이다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 판정

OPEN이 아닌지를 검사한다.

  • 분기 조건 1: status OPEN
  • 분기 조건 2: 노드 1=false
  • 노드 2로 이동
정원 판정

참가 수가 정원과 같다.

  • 분기 조건 1: count2>=capacity2=true
  • 노드 4로 이동
FULL 반환

반환하면 호출이 끝난다.

  • 분기 조건 1: return FULL
  • 분기 조건 2: 방문 1→2→4→6
  • 개념적 종료 6; 다른 return은 실행 안 함

조건 두 개가 반환 경로 세 개를 나눈다

OPEN이 아닌지를 검사한다. 반환하면 호출이 끝난다. 각 단계의 값과 전환 이유를 비교합니다.

SOFTWARE ENGINEERING · READING CLUB

조건 두 개가 반환 경로 세 개를 나눈다

숫자는 본문의 admission 코드와 대응합니다. 조건이 거짓일 때만 다음 조건으로 진행합니다.

조건 두 개가 반환 경로 세 개를 나눈다노드1 status가 OPEN이 아닌지 판정: 참이면3 CLOSED 반환, 거짓이면2 정원 판정. 노드2 참가수 이상 정원: 참이면4 FULL 반환, 거짓이면5 AVAILABLE 반환. 3,4,5 모두 개념적 종료6에 합류. 여섯 노드와 일곱 변. 1 status!== OPEN?2 count>= capacity?3 returnCLOSED4 returnFULL5 returnAVAILABLE6 종료개념적 합류TFTF
조건 두 개가 반환 경로 세 개를 나눈다노드1 status가 OPEN이 아닌지 판정: 참이면3 CLOSED 반환, 거짓이면2 정원 판정. 노드2 참가수 이상 정원: 참이면4 FULL 반환, 거짓이면5 AVAILABLE 반환. 3,4,5 모두 개념적 종료6에 합류. 여섯 노드와 일곱 변. 1 status!== OPEN?2 count>= capacity?3 returnCLOSED4 returnFULL5 returnAVAILABLE6 종료개념적 합류TFTF

마름모는 조건, 사각형은 반환, 더 둥근 종료 상자는 개념적 합류입니다. 화살표는 가능한 제어 이동, T/F는 조건의 참/거짓입니다. 6은 별도 JavaScript 문장이 아닙니다. 넓은 화면과 좁은 화면 모두 N=6, E=7입니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 첫 판정부터 FULL 반환까지 진행합니다.

관계도의 보충 설명

전체 도표의 표기·관계·조건
구분전체 도표의 내용
관계 설명노드 1은 status가 OPEN이 아닌지 판정한다. 참이면 3 CLOSED 반환, 거짓이면 2 정원 판정으로 이동한다. 노드 2는 참가 수가 정원 이상인지 판정한다. 참이면 4 FULL 반환, 거짓이면 5 AVAILABLE 반환으로 이동한다. 3,4,5 모두 개념적 종료 6에 합류한다. 여섯 노드와 일곱 변이다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 판정

OPEN이 아닌지를 검사한다.

  • 분기 조건 1: status OPEN
  • 분기 조건 2: 노드 1=false
  • 노드 2로 이동
정원 판정

참가 수가 정원과 같다.

  • 분기 조건 1: count2>=capacity2=true
  • 노드 4로 이동
FULL 반환

반환하면 호출이 끝난다.

  • 분기 조건 1: return FULL
  • 분기 조건 2: 방문 1→2→4→6
  • 개념적 종료 6; 다른 return은 실행 안 함
입력실제 방문 노드건너뛴 판단결과
CLOSED, count=0, capacity=21→3→6노드 2CLOSED
OPEN, count=2, capacity=21→2→4→6없음FULL
OPEN, count=1, capacity=21→2→5→6없음AVAILABLE

간선은 1→3, 1→2, 2→4, 2→5, 3→6, 4→6, 5→6입니다. 같은 CLOSED 입력에서 count만 2로 바꾸어도 첫 return에서 끝나므로 새 경로를 추가하지 않습니다. 각 기본 경로는 다른 두 경로에 없는 return 간선을 하나씩 갖습니다. 이것이 이 작은 그래프에서 세 경로를 서로 구별할 수 있는 직접 근거입니다.

일반 프로그램에서는 그래프에 그려진 경로도 조건이 서로 모순이면 실행할 수 없습니다. 루프의 반복 횟수까지 다루는 전체 경로와 이 예제의 기본 경로는 다릅니다. NIST SP500-235의 기본 경로·실현 불가능 경로 구분을 참고하되, V개 사례를 만들었다는 사실을 모든 입력·예외·요구 검증으로 확대하지 않습니다.

코어 테스트에서 관찰하기

저장소 최상위에서 다음 명령으로 런타임의 코드 커버리지 보고를 볼 수 있습니다. Node.js 24에서 이 옵션은 실험적 기능으로 표시됩니다. 보고서 형식과 집계 방식은 런타임 버전에 따라 달라질 수 있습니다.

node --experimental-test-coverage --test dev-note/scripts/software-engineering-lab/core.test.mjs

보고서의 함수·문장·분기 수치는 도구가 정의한 집계입니다. 요구 R1~R6이 모두 적절한 사례로 검증됐는지는 요구 추적 표와 따로 비교합니다. 커버리지가 높아도 틀린 기대 결과를 가진 테스트는 틀린 동작을 승인할 수 있습니다.

독립 문제 2: 빈 테스트로 높아진 비율

테스트가 register를 여러 조건으로 호출하지만 결과를 전혀 단언하지 않습니다. 코드 커버리지가 높아졌습니다. 그 수치가 증명하는 것과 증명하지 못하는 것을 구분하고 개선안을 두 가지 적으세요.

힌트

실행과 옳음의 판단을 분리합니다. 요구에서 기대 값을 얻을 근거가 필요합니다.

해설

집계 대상 코드가 실행됐음을 보여 줍니다. 거절 이유·명단 보존·중복 금지가 맞다는 증거는 없습니다. 각 요구에서 도출한 코드와 상태 단언을 추가하고, 중복/정원 검사 순서를 바꾸는 결함을 주입해 테스트가 실패하는지 확인합니다. 비율을 낮추지 않는 것이 목표가 아니라 놓친 규칙을 발견하는 것이 목표입니다.

화이트박스 테스트는 내부 구조를 이용하고 블랙박스 테스트는 입력·출력 명세를 이용합니다. 두 방법은 서로 보완하며 다음 문서의 테스트 수준 구분과는 다른 축입니다.