C 프로젝트에서 간단하게 유닛 테스트 하기

computerphilosopher·2022년 9월 3일
1

이 포스팅에서는 C 프로젝트에서 assert만을 이용한 유닛 테스트 방안을 구현한다. UnityCheck를 도입하는 것이 귀찮을 정도의 소규모 프로젝트에서는 유용하게 쓰일 수 있다.

사용자가 -t 옵션을 주었을 경우에 테스트 코드만을 실행하고 꺼지는 예제를 구현해보자. 다음은 테스트 적용 전의 코드이다. -a 플래그가 있으면 덧셈 결과를, -s 플래그가 있으면 뺄셈 결과를 출력하는 예제이다.

#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>

int add(int a, int b) {
    return a+b;
}

int sub(int a, int b) {
    return a-b;
}

int main(int argc, char *argv[]) {
    bool addflag = false;
    bool subflag = false;
    int c = 0;
    while ((c = getopt(argc, argv, "as")) != -1) {
        switch (c) {
            case 'a': 
                addflag = true;
                break;
            case 's': 
                subflag = true;
                break;
        }
    }

    if (addflag) {
        printf("%d\n", add(1, 2));
    }
    if (subflag) {
        printf("%d\n", sub(2, 1));
    }

    return 0;
}

이제 여기에 -t 플래그와 유닛 테스트를 실행하는 코드를 추가한다. assertion을 유도하기 위해 일부러 틀린 결과를 넣었다.

#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>

int add(int a, int b) {
    return a+b;
}

int sub(int a, int b) {
    return a-b;
}

void unit_test() {
    assert(add(1, 2) == 3);
    assert(sub(2, 1) == 0);
}

int main(int argc, char *argv[]) {
    bool addflag = false;
    bool subflag = false;
    int c = 0;
    while ((c = getopt(argc, argv, "ast")) != -1) {
        switch (c) {
            case 'a': 
                addflag = true;
                break;
            case 's': 
                subflag = true;
                break;
            case 't': 
                unit_test();
                return 0;
        }
    }

    if (addflag) {
        printf("%d\n", add(1, 2));
    }
    if (subflag) {
        printf("%d\n", sub(2, 1));
    }

    return 0;
}

컴파일 후 실행하면 다음과 같은 메시지와 함께 테스트가 실패한다. 16 라인에서 실패했다는 정보가 출력되기 때문에 어느 테스트가 실패했는지 쉽게 알 수 있다.

Assertion failed: (sub(2, 1) == 0), function unit_test, file main.c, line 16.

라인 넘버만으로는 가독성이 만족스럽지 않다면 테스트 함수를 분리하면 된다. unit_test 함수를 add_test와 sub_test로 분리해보자.


void add_test() {
    assert(add(1, 2) == 3);
}

void sub_test() {
    assert(add(2, 1) == 0);
}

void unit_test() {
    add_test();
    sub_test();
}

이후 프로그램을 실행하면 다음과 같은 Assertion 메시지가 나온다.

Assertion failed: (add(2, 1) == 0), function sub_test, file main.c, line 19.

테스트 이름이 적혀 나오기 때문에 가독성이 더 좋다. 간단한 예를 보이기 위해 테스트 함수를 main.c 파일에 포함시켰지만, 실제 프로젝트에서 사용할때는 코드를 분리해놓는 것이 좋을 것이다.

파일이나 소켓, 혹은 데이터베이스를 읽고 쓰는 코드가 없는 간단한 프로그램이라면 이런 방식으로 유닛 테스트를 쉽게 할 수 있다. 그렇지 않다면 원칙적으로는 Mocking을 해야 하는데, 다형성을 언어 차원에서 지원하지 않는 C에서 Mocking을 하는 것은 어려운 일이다. 이럴 때에는 유닛 테스트 전용 라이브러리를 도입하거나, 완벽한 유닛 테스트를 포기하고 통합 테스트를 섞는 것을 추천한다.

0개의 댓글