함수 커버리지 (Function Coverage)#
함수 커버리지는 프로그램 내의 모든 함수가 테스트 중에 최소한 한 번 이상 호출되었는지를 측정하는 지표이다.
간단한 예제:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
|
이 계산기 클래스의 모든 함수를 테스트하기 위해서는 다음과 같은 테스트 코드가 필요하다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def test_calculator():
calc = Calculator()
# add 함수 테스트
assert calc.add(5, 3) == 8
# subtract 함수 테스트
assert calc.subtract(10, 4) == 6
# multiply 함수 테스트
assert calc.multiply(6, 2) == 12
# divide 함수 테스트
assert calc.divide(8, 2) == 4
|
이제 실제 개발에서 자주 마주치는 더 복잡한 예제:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| public class OrderProcessor {
public void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
applyDiscounts(order);
updateInventory(order);
sendConfirmation(order);
}
private void validateOrder(Order order) {
// 주문 유효성 검사
}
private void calculateTotal(Order order) {
// 총액 계산
}
private void applyDiscounts(Order order) {
// 할인 적용
}
private void updateInventory(Order order) {
// 재고 업데이트
}
private void sendConfirmation(Order order) {
// 주문 확인 메일 발송
}
}
|
이 주문 처리 시스템의 함수 커버리지를 확인하기 위한 테스트를 작성해보자:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @Test
void testOrderProcessing() {
OrderProcessor processor = new OrderProcessor();
Order testOrder = new Order();
// 모든 함수가 호출되는 시나리오 테스트
processor.processOrder(testOrder);
// 각 단계가 올바르게 실행되었는지 검증
verify(testOrder).isValid(); // validateOrder 호출 확인
verify(testOrder).getTotal(); // calculateTotal 호출 확인
verify(testOrder).getDiscounts(); // applyDiscounts 호출 확인
verify(testOrder).getItems(); // updateInventory 호출 확인
verify(testOrder).getCustomerEmail(); // sendConfirmation 호출 확인
}
|
함수 커버리지의 계산 방법은 다음과 같다:
1
2
3
4
5
6
| 함수 커버리지 = (호출된 함수의 수) / (전체 함수의 수) × 100%
예를 들어, OrderProcessor 클래스의 경우:
- 전체 함수 수: 6 (processOrder + 5개의 private 메서드)
- 호출된 함수 수: 6
따라서, 함수 커버리지 = (6/6) × 100% = 100%
|
함수 커버리지를 측정할 때 특별히 주의해야 할 상황#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class FileHandler {
public void processFile(String filename) {
try {
readFile(filename);
parseContent();
saveResults();
} catch (IOException e) {
handleError(e);
} finally {
cleanup();
}
}
private void readFile(String filename) throws IOException { … }
private void parseContent() { … }
private void saveResults() { … }
private void handleError(Exception e) { … }
private void cleanup() { … }
}
|
이러한 경우, 정상 실행 경로와 예외 발생 경로 모두를 테스트해야 한다:
1
2
3
4
5
6
7
8
9
10
| @Test
void testFileProcessing() {
FileHandler handler = new FileHandler();
// 정상 경로 테스트
handler.processFile("valid.txt");
// 예외 경로 테스트
handler.processFile("invalid.txt");
}
|
측정 방법#
함수 커버리지는 다음과 같은 공식으로 계산된다:
함수 커버리지(%) = (호출된 함수의 수 / 전체 함수의 수) × 100
- 가장 기본적이고 가벼운 커버리지 측정 방법이다.
- 함수의 내부 로직이나 실행 경로는 고려하지 않고, 단순히 함수가 호출되었는지만을 확인한다.
- 테스트의 기본적인 수준을 빠르게 확인할 수 있다.
- 구현이 간단하고 측정이 빠르다.
- 프로그램의 전반적인 테스트 상태를 빠르게 파악할 수 있다.
- 테스트되지 않은 함수를 쉽게 식별할 수 있다.
한계점#
- 함수 내부의 복잡한 로직이나 조건문을 고려하지 않아 테스트의 품질을 정확히 반영하지 못할 수 있다.
- 함수가 호출되었다고 해서 그 함수의 모든 기능이 테스트되었다고 보장할 수 없다.
- 다른 커버리지 기법들에 비해 테스트의 철저성을 보장하기 어렵다.
이러한 한계를 보완하기 위해, 함수 커버리지는 보통 다른 커버리지 지표들과 함께 사용된다:
- 구문 커버리지로 함수 내부의 코드 실행을 확인
- 분기 커버리지로 조건문의 다양한 경로를 검증
- 통합 테스트로 함수들 간의 상호작용을 확인
이처럼 여러 테스트 기법을 조합함으로써, 소프트웨어의 품질을 더욱 효과적으로 보장할 수 있다.
참고 및 출처#