Structural testing을 이해하기 위해서는 먼저 "프로그램이 왜 실패하는가?", "어떤 조건에서 실패가 발생하는가?를 짚어야 한다.
사용자가 기대한 결과를 제공하지 못하는 경우를 프로그램 실패 (failure)라고 정의한다. 예를 들어 정상적으로 작동해야 하는 기능이 엉뚱한 값을 출력하거나, 프로그래밍 멈추거나, 충돌하는 것까지 모두 여기에 포함되어 있다.
이러한 실패는 결국 코드 내부에 존재하는 fault(버그)가 특정 입력에 의해 실행되면서 실패로 발전하는 과정을 갖는데, 이를 PIE 모델로 보면 아래와 같다.
이 세 단계 중 하나라도 충족되지 않으면 버그는 찾을 수 없게 된다. 즉, 테스트가 버그를 드러내려면 "버그를 실행시키고 전파되도록 만드는 입력""을 찾아야 한다.
테스트는 "미리 정의된 입력 (test case)과 기대 출력 (expected output)"을 비교하여 실패를 찾아내는 활동이다.
이때 동일한 프로그램이라도
두 관점을 모두 사용해야 결함을 충분히 탐지할 수 있다.
Blackbox가 요구사항 충족 여부에 초점을 둔다면, whitebox는 코드 내부 구조를 모두 충분히 실행해보는 것에 초점을 둔다.
구조 기반 테스트는 프로그램의 내부 구조, 즉 소스 코드 자체를 기준으로 테스트 케이스를 설계하는 기법이다. 이는 블랙박스 테스트와 달리 코드를 직접 읽고, 그 코드의 제어 흐름과 데이터 흐름을 모델링하여 테스트를 만든다.
사용자는 프로그램이 모든 입력에 대해, 주어진 시간 안에, 올바른 출력을 제공하기를 기대한다.
프로그램 실패란 프로그램 실행 결과가 요구사항을 만족하지 못할 때 발생한다.
테스트는 fault가 failure로 이어지는 세 단계를 모두 일으키는 입력을 찾아야 한다는 점에서 중요하다.
테스트란
여기서 test oracle이란 정답을 제공하는 기준이다.
블랙박스와 화이트박스 테스트의 관점을 나누기 위해 다음 세 가지를 정의한다.
Whitebox 테스트는 프로그램의 내부 구조를 보고 테스트 케이스를 선택한다는 점이 핵심이다.
다양한 테스트 기법이 있으나 비용과 효과 측면에서 whitebox testing인 brach coverage가 가장 효율성이 크다.
Structural testing에서는 보통 아래 두 모델을 사용한다.
소스 코드를 기반으로 그래프를 생성한 뒤에는 이 그래프를 기반으로 커버리지 기준을 충족시켜야 한다.
그래프 기반 모델링이 구조 기반 테스트의 핵심이다.
그래프는 소프트웨어 구조를 일반화하여 표현할 수 있는 강력한 수단이다. 테스트는 결국 그래프를 어떻게 방문할 것인가의 문제이다.
그래프는 다음 요소로 구성된다.
초기 노드에서 시작하여 마지막 노드에서 끝나는 실제 실행 흐름을 test path라고 한다. 특히, 하나의 진입점과 하나의 종료점을 갖는 그래프를 SESE (Single-Entry Single-Exit)라고 한다.
Test path가 특정 노드나 엣지를 포함할 때, 해당 노드/엣지를 visit 한다고 표현한다. 또한 test path가 subpath 전체를 순서대로 포함할 때 tour라고 표현한다.
모든 노드/엣지를 한 번 이상 방문하면 충족된다. Edge coverage가 Node coverage보다 조금 더 강력하다.
길이가 2인 모든 subpath를 방문하면 충족된다. 이때 test requriement는 다음과 같이 표현된다. 등
Loop가 있으면 경로가 무한히 길어지므로 complete path coverage는 불가능하다. 이를 다루기 위해 loop를 1회만 수행하거나, prime path를 도입하여 가장 합리적인 path를 선택하는 등의 방법이 제시되었다.
Prime Path Coverage는 EC, EPC, NC를 모두 포함한다.
시작과 끝이 같은 prime path를 Round Trip Coverage라고 한다. Loop 중심 coverage를 표현하지만, NC/EC보다 약한 경우도 있다.
Subpath를 순서대로 포함하되, 중간에 다른 노드를 거쳤다가 다시 돌아올 수 있다. (sidetrip)
Subpath의 노드들을 순서대로 방문하는 것을 detour라고 한다.
Branch coverage 100%를 달성해도 의미적 오류를 잡을 수 없다. 즉, 구조적 기준만으로는 의미적 오류를 잡을 수 없다.
데이터 흐름 기반 테스트는 값이 어디에서 정의되고 어떻게 사용되는지를 추적함으로써 테스트가 충분한지 판단한다. 단순히 노드와 엣지 같은 구조를 보는 구조적 테스트와 달리, 값이 실제로 어떻게 흐르는지 분석한다는 점에서 의미적인 오류를 찾는 데 효과적이다.
특정 변수를 정의하고 사용하는 지점이 와 일 때 두 지점을 연결하는 def-clear path가 존재해야 DU-pair가 성립한다.
def-clear란 경로 위에서 변수가 다시 정의되지 않는 경로를 뜻한다. 즉, 에서 정의된 값이 손상되지 않고 까지 도달하는 것을 의미한다.
DU-Path는 DU-pair를 만족시키는 simple path (자시 반복이 없는 경로)를 말한다. 변수 에 대해 def-clear 조건을 만족해야 한다. 테스트 경로가 특정 DU-Path를 따랐다고 인정되려면 다음 조건을 만족해야 한다.
데이터 흐름 기반 커버리지는 다음 세 단계로 구성된 테스트 강도 계층 구조를 갖는다.
구조 기반 테스팅에서는 prime path coverage가 매우 강력하지만, 데이터 흐름 기반 기준으로는 ADUP가 가장 강력하다.
CFG는 프로그램의 실행 가능한 모든 흐름을 노드와 엣지로 시각화한 모델이다.
예를 들어 if 문에서 각 return은 독립된 노드로 취급되어야 한다.
CFG의 모든 edge를 방문해야 한다.
길이 2인 모든 subpath를 방문해야 한다. EC에서 보이지 않던 숨겨진 버그를 EPC에서 탐지할 수 있다.
Simple path 중 더 이상 확장될 수 없는 path를 모두 커버해야 한다. Primt path coverage에서 infeasible 경로가 존재할 수 있다는 것을 밝힌다.
CFG 기반 테스트는 연구가 잘 되어 있으며 비교적 체계적이다. 하지만 실제 코드를 CFG로 옮길 때는 subtle 설계 결정이 필요하다.