6 분 소요

1. Dynamic Test

동적 테스트이란, 소프트웨어를 실행하지 않고 소스코드를 분석하는 것을 의미한다.동적 테스트의 목적은 결함을 발견할 목적 으로 프로그램을 실행하는 것이다. 프로그램의 입력이 가질수 있는 조합은 무수히 많고, 동작 조건 또한 무수히 많기 때문에 완벽한 테스트는 불가능하다. 결함이 없음을 증명할 수 없다. 완벽한 테스트는 불가능하기 때문에, 주어진 시간과 인력으로 오류를 발견할 확률이 가장 높은 테스트 케이스를 찾아내고 실행하는 것이 무엇보다 중요하다. 좋은 테스트 케이스는 결함을 많이 발견하는 테스트 케이스이다.

Q1)개발자가 직접 테스트를 수행해도 될까?
A1)개발자 자기 자신이 개발한 프로그램을 직접 테스트 하는것은 목적 자체를 기능의 정상동작으로 보기 때문에 그다지 효과적이지 못하다.

2. Test case

Test case는 요구사항이 제대로 구현되었는지 검증하기 위해 테스트 조건, 입력 값, 예상 출력 값, 실제 수행한 결과를 기록한 것을 의미한다.

3. Regression Test

회귀 결함은 이전에 제대로 작동하던 기능에 문제가 생기는것을 의미한다. Regression Test란 정상적으로 동작하던 기능이 SW 수정 후, 회귀 결함이 존재하는지 확인하는 테스트를 의미한다. 새로운 결함이 조치되거나, 리팩토링, 새로운 기능의 추가/변경 등이 이루어질 경우 회귀 결함이 발생할 수 있다.

4. 동적 테스트의 4가지 단계

4.1 단위(Unit) 테스트

단위 테스트란, 설계된 모듈이 정확하게 구현이 되었는지 개발자가 직접 테스트 하는 것을 의미한다.

4.2 통합(Integration) 테스트

통합 테스트란, 모듈과 모듈을 통합하여 테스트하는 것을 의미한다.

  • Big Bang 기법: 모듈을 한꺼번에 통합하여 테스트 하는 방법
  • Bottom-Up 기법: 하위 모듈부터 테스팅 하고 상위 모듈로 점진적으로 통합하는 방법
  • Top-Down 기법: 가장 상위 모듈부터 하위 모듈로 점진적으로 통합하는 방법이며, 상위 모듈 테스팅 시 하위 모듈에 대한 스텁이 필요 (*Stub: 함수가 호출되면 미리 정해놓은 값을 반환하는 방식)

4.3 시스템(System) 테스트

모듈이 모두 통합된 후, 요구사항 명세서에 맞게 만들어 졌는지 확인하는 테스트이다. 고객에게 시스템이 전달되기 전, 시스템을 개발한 조직이 주체가 되는 마지막 테스이다.

4.4 인수(Acceptance) 테스트

시스템이 사용자에게 인수되기 전, 사용자에 의해 실시되는 테스트이다. 실제 사용자가 운영하는 환경에서 실시한다.

5. 구조 기반 테스트(Structure-based testing)

구조 기반 테스트란 소스코드 내의 모든 독립적인 경로를 수행해 봄으로써 테스트하는 방법이다. 화이트박스 테스트 라고도 부른다.

5.1 Function Coverage

함수 커버리지는 테스트한 함수/전체 함수를 의미한다. 아래 소스 코드에의 함수 커버리지는 4/5 이다. 전체 함수는 main() 문을 포함하여 5개이고, 테스트 된 함수는 util1(), util4(), util3(), main() 4개이기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 void util1() { // function 1
  util4();
 }
 void util2() { // function 2
 
 }
 void util3() { // function 3
 
 }
 void util4() { // function 4
 
 }
 void main() { // function 5
 util1();
 util3();
 }

5.2 Statement Coverage

구문 커버리지는 테스트한 구문/전체 구문을 의미한다. (※ 구문이란 ‘;’로 끝나는 명령 라인) 라인 커버리지 라고도 한다.
Q1) i를 0으로 테스트 한 경우 구문 커버리지는 몇인가?
A1) 1/3
Q2) i를 -1로 테스트 한 경우 구문 커버리지는 몇인가?
A2) 3/3

1
2
3
4
5
6
7
8
9
#include <stdio.h>
...
void test(int i){
    print("test\n"); // state 1
    if(i<0){
        print("call i<0\n"); // state 2
        print("call i<0\n"); // state 3
    }
}

5.3 Decision Coverage

결정 커버리지는 테스트한 decision 수/전체 decision 수를 의미한다.
참/거짓을 한 번씩 테스트 해보는 것이 목적이다.
결정 커버리지를 100% 달성하기 위해서는 전체 조건문수 * 2 만큼의 test case가 필요하다. Branch 커버리지 라고도 한다.

아래 코드에서 전체 decison 수는 총 4개 이다.(조건문이 2개니까)
결정 커버리지 계산 시, 분기 1 거짓 은 항상 실행된다고 계산하면 안된다.
결정 커버리지는 if(..)의 전체 내용이 참인지 거짓인지만 중요하다. 개별적인 condition은 중요하지 않다.
Q1) k를 0으로 테스트한 경우 결정 커버리지는 몇인가?
A1) 2(분기 1 거짓, 분기 2 거짓)/4
Q2) k를 1로 테스트한 경우 결정 커버리지는 몇인가?
A2) 2(분기 1 참, 분기2 참)/4
Q3) 결정 커버리지 100%를 달성하기 위한 최소 test case 개수는?
A3) 2개

1
2
3
4
5
6
7
8
9
10
11
12
void test(int k){
    if(k>0){ // 분기 1 참
        ...
    }
    // 분기 1 거짓
    if(k==1){ //분기 2 참
     ...   
    }
    else{ //분기 2 거짓
     ...
    }
}

5.4 Condition Coverage

조건 커버리지란 테스트한 Condition 수/전체 Condition 수를 의미한다.
Q1) 아래 조건 커버리지를 100% 달성하기 위해서는 최소 몇 개의 test case가 필요한가?
A1) 전체 test case 수는 총 4개 이며, 최소 2개의 test case가 필요하다. 예를 들면,
test case1) a=1, b=0;
test case2) a=0, b=1;
조건 커버리지 계산 시, if(..)의 전체 내용이 참인지는 중요하지 않다. 개별적인 condition만 중요하다. 즉, 조건 커버리지를 100% 달성 하더라도, if문 안을 실행하지 않을 수도 있다.

1
2
3
4
5
6
7
void test(int a, int b) {
..
    if(a==1 && b==1) { // condition1: a==1, condition2: b==1
        ...
    }
}
...

5.5 Condition/Decision Coverage

조건/결정 커버리지란 조건 커버리지와 결정 커버리지의 최소한의 조합으로 전체 조건식과 개별 조건식 모두 true와 false가 한번 씩 수행되도록 test case를 생성하는 방법이다. 아래 두 단점을 보완하기 위한 커버리지 이다.

  • 조건 커버리지를 100% 달성 하더라도 결정 커버리지는 100%를 달성할 수 없다.
  • 결정 커버리지를 100% 달성 하더라도 조건 커버리지는 100%를 달성할 수 없다.

기존의 condition coverage와 decision coverage의 한계를 보완한다.
Q1) 아래 조건/결정 커버리지를 100% 달성하기 위해서는?
A1) 총 2개의 test case가 필요하다. 예를 들면,
test case1) a=1, b=1, c=1, d=1;
test case2) a=0, b=-1, c=-2, d=0;

진리표는 아래와 같다.

Decision l Condition A Conditon B Conditon C Condition D
True l a=1 a=1 a=1 a=1
False l a=0 b=-1 b=-2 b=0

조건/결정 커버리지 입장에서는 decision coverage와 condition coverage를 각각 100% 만족 시킬 수 있는 test case를 찾아서 조합하면 되는 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
void test(int a, int b) {
..
    if(a==1 && b==1 || c==1 && d==1) { 
    // Conditon A : a==1;
    // Conditon B : b==1;
    // Conditon C : c==1;
    // Conditon D : d==1;
        ...
    }

}
...

5.6 Modifed Condition/Decision Coverage

Modified Condition/Decision Coverage란 전체 조건식에 독립적으로 영향을 주는 조건을 추가하여 test case를 개발하는 방법이다.
개별 조건이 전체 조건에 영향을 미치는 조건을 추가한다. 즉, 각 조건이 decision에 독립적으로 영향을 주는 test case있어야 한다.
5.5의 Condition/Decision Coverage 문제점을 보완하기 위함이며, 개발자의 논리오류(||를 &&로 착각해서 작성하는 등)를 발견하기 위함이다. MC/DC Coverage를 100%달성하기 위한 test case 수는 전체 개별 조건 수 +1개 이다.

Q1) 아래 MC/DC를 100% 달성하기 위해서는?
A1) 개별 조건이 총 A, B 개 이므로 총 3개의 test case가 필요하다. 예를 들면,

Decision l Condition A Conditon B 필요 여부
True l a=true a=true O
False l a=true b=false O
False l a=false b=true O
False l a=false b=false X

마지막 test case는 굳이 구행할 필요가 없다. 왜냐하면, a와 b가 각각 false로 변할 때, decision이 false로 변화하기 때문이다.
즉, A,B의 독립적인 영향을 확인했다.

1
2
3
4
5
..
if (A && B) {
    ...
}
...

Q2) 아래 MC/DC를 100% 달성하기 위해서는?
A2) 개별 조건이 총 A, B 개 이므로 총 3개의 test case가 필요하다. 예를 들면,

Decision l Condition A Conditon B 필요 여부
True l a=true a=true X
True l a=true b=false O
True l a=false b=true O
False l a=false b=false O

첫 번째 test case는 굳이 구행할 필요가 없다. 왜냐하면, a와 b가 각각 true로 변할 때, decision이 true로 변화하기 때문이다.
즉, A,B의 독립적인 영향을 확인했다.

1
2
3
4
5
..
if (A || B) {
    ...
}
...

6. 명세 기반 테스트(Specification-based testing)

명세 기반 테스트란 소스코드 자체는 제외하고 출력 값에만 초점을 두고 테스트하는 방법이다. 블랙박스 테스트라고도 부른다.

6.1 구문 테스팅(Syntax Testing)

명세 기반 테스트 방법 중, 가장 단순한 방법으로, 입력 값을 적합(valid)과 부적합(invalid)으로 분류한뒤 예상되는 결과를 검증하는 기법이다.
예를 들면, e-mail 주소를 입력하는 경우, 아래와 같이 test case를 만들어 볼 수 있다.

No 입력 유형 입력 값 예상 결과
1 길이가 너무 짧은 경우 asf 에러 메시지
2 알파벳이 아닌 경우 #$$1 에러 메시지
3 xxx@xxx.com 형태인 경우 abc@defg.com 정상 처리

6.2 동등분할(Equivalence Partitioning)

입력값의 범위가 정해져 있을 경우, 각 범위의 대표값을 이용하여 테스트하는 방법이다.

6.3 경계값 분석(Boundary Value Analysis)

입력 범위에서 경계값을 입력값으로 설정하여 테스트하는 방법이다.

6.4 에러 추정(Error Guessing)

과거의 경험이나 HARA를 통해 생성된 테스트 케이스를 이용하는 방법이다.

6.5 페어와이즈(Pairwise)

각각 test case에서 입력 값들이 최소 한번씩 조합을 이루게하여 테스트하는 방법이다.
MS의 페어와이즈 tool(PICT)을 이용해서 자동으로 테스트 케이스를 생성할 수도 있다.
간단하게는 Site에서도 확인 가능하다.

6.6 백투백(Back to Back)

두 개 이상의 테스트 대상 시스템에 동일한 입력값으로 실행하여 결과를 비교 분석하는 테스트 방법이다. 예를 들면, Simulink 모델의 input에 대한 output 값과, code generation 된 source code의 input에 대한 output 값을 비교하는 방법이다.

6.7 결함 주입(Fault Injection)

임의의 결함을 주입하여 테스트 하는 방법이다. 크게 컴파일 시간에 결함을 주입하는 방법과, 실시간으로 결함을 주입하는 방법이 있다.