반응형

제목: 테스팅 커버리지 측정으로 소프트웨어 품질 달성하기(Achieving Software Quality with Testing Coverage Measures)

저자: Joseph R. Horgan 2, Bellcore

문서유형: 저널 페이퍼( 10 페이지), 1994

 

C 프로그램을 위한 데이터 흐름 커버리지 툴인 ATAC(Automatic Test Analysis for C)에 대해 설명하고 이를 두 개의 실제 소프트웨어 프로젝트에 적용한 사례를 기술한 자료


 

커버리지 테스팅

  • 커버리지 테스팅은 테스터가 철저한 테스트셋을 생성하도록 도우며 테스트 완전성(test completeness)’에 대한 측정치를 제공한다.
  • 커버리지 테스팅의 개념이 문헌에 잘 기술되어 있는 반면 이런 개념을 실제 표준 프로그래밍 언어를 위해 구현한 툴이 별로 없음(따라서 대규모 프로젝트에 현실적으로 사용되는 사례도 흔치 않음)
  • 커버리지 테스팅은 테스팅 동안 미처 실행되지 못한 프로그램 코드 구성물(constructs)을 식별함. 이를 통해 중요한 소프트웨어 구성물이 테스팅 되도록 유도하고 테스트 완전성에 대한 분명한 체크리스트를 제공한다.
  • 커버리지 측정치(a coverage measure)는 주어진 프로그램의 테스트셋에 특정 값(수치)을 연결시키는데, 이는 해당 프로그램을 위한 해당 테스트셋의 완전성 정도를 나타낸다.

 

데이터 흐름 테스팅에서의 메트릭

기존 문헌을 기반으로 C 프로그램을 위한 데이터 흐름 커버리지 측정치(메트릭)를 아래와 같이 제안함

 

기본 블록(basic block) 커버리지

  • 블록은 보통 함께 실행되어지는 코드 일부분(, 분기가 없는 연속적인 코드 조각)을 나타냄. C 프로시져의 몸체가 기본 블록들의 시퀀스로 이루어진다고 볼 수 있음
  • 블록 커버리지가 스테이트먼트 커버리지(statement coverage)와 유사하지만 프로그램 구조에 더 민감하다.

 

테스트셋의 블록 커버리지 = 실행된(커버된) 기본 블록 수 / 프로그램의 총 블록 수

 

디시젼(decision) 커버리지

  • 기본 블록 커버리지가 필수이기는 하지만 테스트 완전성에 대한 충분한 측정치는 아님
  • 분기 프레디켓(a branch predicate)의 가능한 값들 각각을 위한 디시젼이 존재함
    예를 들어, 아래 sum.c 프로그램에서 프레디켓 k == 1이 참(true) 또는 거짓(false)이 될 수 있으므로 관련된 두 개의 디시젼이 존재함
  • 케이스 프레디켓(a case predicate)은 다수의 관련 디시젼을 갖게 될 수 있다.

 

테스트셋의 디시젼 커버리지 = 이 테스트에 의해 커버된 디시젼 수 / 프로그램의 총 디시젼 수

 

c-use, p-use, 그리고 all-uses 커버리지

  • 데이터 흐름 커버리지 테스팅은 테스터가 모든 정의-사용 쌍(all definition-use pairs)’을 커버하는 테스트 케이스를 구축하도록 유도함
  • 정의(a definition)는 변수에 어떤 값을 할당하는 스테이트먼트를 의미
    예를 들어, sum.c 프로그램의 스테이트먼트 i = 1; 에서 변수 i가 정의되고, 스테이트먼트 sum += i; 에서는 i가 사용되며(a use), 이 둘이 함께 하나의 정의-사용 쌍(a def-use pair)’을 이룬다.
  • 이 사용(use)이 계산식(a computational expression)에서 발생하면 해당 쌍이 ‘c-use’로 불리고, 그게 아니라 프레디켓 내부에서 발생하면(, i <= n에서의 i) ‘p-use’로 불린다.
  • all-uses c-use이거나 또는 p-use 이다

 

경로(path) 커버리지와 du-경로(du-path) 커버리지

  • 프로그램 제어 흐름에 의해 정의되는 어떤 스테이트먼트 시퀀스를 경로(a path)라 지칭. 경로 커버리지는 모든 가능한 경로를 빠트리지 않고 커버하는 것을 목표로 하는 기준이지만 대개 현실적으로 실현 불가능함
  • 한 변수의 정의(definition)로부터 그것의 사용(use)으로 까지의 경로(중간에 해당 변수의 재정의가 없음)‘du-경로(a du-path)’라고 부른다.
  • 아래 프로그램에서는 i = 1;에서 시작하여 while 문 몸체를 한 번 거치고 계속되는 시퀀스의 실행이 du-경로의 한 예이다. 하지만 스테이트먼트 i++;i를 재정의하므로 i = 1;에서부터 sum += i;까지의 루프를 두 번 이상 가진 경로들은 du-경로가 아니다. 

[sum.c 프로그램: 0~n 까지 숫자의 Sum과 Product을 계산]

 

제안된 데이터 흐름 커버리지 기준의 계층도

아래 계층도는 위에 언급된 커버리지 기준을 상부로 갈 수록 더 강력한 순서로 배치한 것이다.

  • 블록 커버리지가 디시젼 커버리지 보다 약하고, 디시젼 커버리지는 p-use 커버리지 보다 약하다.
  • c-use 커버리지는 블록 커버리지와 디시젼 커버리지 보다 강력 하지만 p-use 커버리지와는 독립적 관계에 있다. c-usep-use 커버리지는 둘 다 all-uses 보다 약하다.
  • 커버리지들 간의 관계가 테스팅 전략에 활용됨. 예를 들면, 디시젼 테스팅을 착수하기에 앞서 블록 테스팅 완전하게 할 것을 권고. 디시젼 테스팅이 더 효과적이기는 하지만 또한 더 많은 시간과 노력을 요구하므로 그 전에 블록 커버리지를 빠짐 없이 달성하는게 신중한 접근방법이다.

 

[제어 흐름 및 데이터 흐름 커버리지의 계층도]

 

ATAC 소프트웨어 커버리지 툴

  • ATAC는 데이터 흐름 커버리지 측정 기준을 사용하여 테스트셋의 완전성을 평가한다. , ATAC가 소스 코드의 정적 분석과 실행 경로의 동적 분석으로 부터 수집된 데이터를 사용하여 데이터 흐름 커버리지 적정성(dataflow coverage adequacy)’을 계산함
  • ATAC에 의해 측정되는 프로그램 구성물(program constructs)에 블록, 디시젼, c-uses, p-uses, 그리고 all-uses가 포함된다.
  • ATAC 전처리기(preprocessor) C 소스 코드를 분석하고, 소스 프로그램에 대한 데이터 흐름 정보를 포함하는 파일을 생성한다(이게 분석 단계에서 사용됨). 또한 전처리기가 ATAC 런타임 루틴을 호출하도록 인스트루멘테이션화된 소스 코드의 수정 버전을 생성한다. 이 수정된 소스 코드가 자동으로 컴파일되고 링크 되어서 실행 프로그램을 생성한다.
  • 테스팅 동안에는 수정된 프로그램으로부터 불려진 ATAC 런타임 루틴이 데이터 흐름 커버리지 실행 슬라이스를 기록한다(이게 분석 단계에서 사용됨).
  • 분석 단계에서는 테스터가 각 데이터 흐름 커버리지 측정 기준에 대한 커버리지 값을 요청할 수 있고, 테스트 케이스에 의해 커버되지 않은 소스 코드 구성물을 디스플레이하도록 요청 할 수도 있다.

 

 ATAC 사용하기

아래는 입력 데이터에 따라 1~n 까지의 정수의 SumProduct을 계산하는 sum.c 프로그램에 ATAC 툴을 사용하는 예를 보여주고 있다.

 

표준 컴파일러와 링커 대신에 ATAC를 사용하여 대상 프로그램을 컴파일하고 링크함 

>atacCC -o sum sum.c

 

ATAC가 인스트루먼트화된 실행가능한 프로그램(sum)과 데이터 흐름 테이블(sum.atac)을 생성함. 프로그램 실행 동안에 ATAC의 런타임 루틴이 해당 프로그램의 일상 동작을 방해하지 않으면서 실행 경로 정보(sum.trace)를 수집함. 아래처럼 입력 값 50을 주어 sum을 실행시킴 

>sum

Enter an Integer and 0 for +, 1 for * : 5 0

n=5, sum=15

 

n = 5에 대한 정확한 출력 값이 계산됨. 이 첫 테스트 케이스에 의해 달성된 커버리지를 확인함 

>atac -s sum.trace sum.atac

%blocks        %decisions     %C-Uses        %P-Uses

90(9/10)       60(3/5)        50(3/6)        75(3/4)        == total ==

 

10개 블록 중 9개가 커버되었음을 알 수 있음. ATAC가 커버되지 않은 블록을 디스플레이 하도록 요청할 수 있음 

>atac -mb sum.trace sum.atac

그 결과가 아래 그림과 같다.

[sum.c 프로그램에서 아직 커버되지 않은 블록]

 

이제 더 높은 커버리지를 달성하려는 의도로 다른 입력 값을 주어 sum의 기능을 테스트함 

>sum

Enter an Integer and 0 for +, 1 for *: 5 1

n=5, prod=120

>sum

Enter an integer and 0 for +, 1 for *: 0 0

n=0, sum=0

 

이 테스트 결과가 우리의 기대치와 일치함. 이제 ATAC를 통해 커버리지를 확인함 

>atac -s sum.trace sum.atac

%blocks        %decisions     %C-Uses        %P-Uses

100(10)        100(5)         83(5/6)        100(4)         == total ==

 

sum.c에 있는 6개의 c-uses 중 하나를 우리가 커버하지 않았음을 알게됨. 이 커버되지 않은 c-use를 디스플레이하도록 ATAC에게 요청할 수 있음 

>atac -mc sum.trace sum.atac 

그 결과가 아래 그림과 같다.

[sum.c에서 누락된 c-use]


 

c-use를 커버하기 위해 또 다른 입력 값으로 sum을 다시 테스트함 

>sum

Enter an integer and 0 for +, 1 for * : 0 1

n=0, prod=1

 

>atac -s sum.trace sum.atac

%blocks        %decisions     %c-uses        %P-Uses

100(10)        100(5)         100(6)         100(4)        == total ==

 

이제 우리가 sum.c의 완전한 커버리지를 달성함(, ATAC 측정 기준 관련해서는 위 4개의 테스트로 sum.c 프로그램이 완전하게 커버되었고, 이 테스트에 비추어 봐서는 해당 프로그램이 정확하다).

 

하지만 이 테스트는 sum.c 프로그램이 음수 입력 값에도 정확하게 동작하는지에 대해서는 전혀 말해 주지 않고 있음을 상기해야 한다. 예를 들어, 아래 같은 추가 테스트를 해 보면 해당 프로그램이 정확하지 않음을 알 수 있다. 

>sum

Enter an integer and 0 for +, 1 for * : -5 0

n=-5, sum=0

 

이는 ATAC 커버리지 테스팅과 더불어 기능 테스팅(functional testing)도 수행 하는게 중요함을 보여준다.

 

sum.c 같은 사소한 프로그램에서는 완전한 커버리지 테스팅을 하는게 비교적 쉽지만 대규모의 복잡한 프로그램에서는 완전 커버리지(full coverage)를 달성하는게 훨씬 어렵다.

 

 

반응형

+ Recent posts