2010-02-26 3 views

답변

19

단위 테스트에는 테스트를 수행 할 수있는 "절단면"또는 경계 만 필요합니다. 다른 함수를 호출하지 않거나 테스트 된 다른 함수 만 호출하는 C 함수를 테스트하는 것은 매우 간단합니다. 몇 가지 예는 계산 또는 논리 연산을 수행하는 기능이며 본질적으로 기능적입니다. 동일한 입력이 항상 동일한 결과를 가져 오는 의미에서 기능적입니다. 이러한 기능을 테스트하는 것은 일반적으로 단위 테스트로 간주되는 것의 작은 부분 임에도 불구하고 큰 이점을 가질 수 있습니다.

모조 또는 스텁의 사용과 같은보다 정교한 테스트도 가능하지만보다 동적 인 언어 나 C++와 같은 객체 지향 언어만큼 쉽지 않습니다. 이 접근하는 한 가지 방법은 # 정의를 사용하는 것입니다. 이것의 한 예는 OpenGL을 호출을 조롱하는 방법을 보여줍니다이 문서 Unit testing OpenGL applications이다. 이렇게하면 OpenGL 호출의 유효한 시퀀스가 ​​만들어 졌는지 테스트 할 수 있습니다.

또 다른 옵션은 약한 심볼을 활용하는 것입니다.예를 들어, 모든 MPI API 함수는 약한 기호이므로, 자신의 응용 프로그램에서 동일한 기호를 정의하면 구현시 라이브러리의 약한 구현보다 우선합니다. 라이브러리의 심볼이 약하지 않은 경우 링크시 중복 심볼 오류가 발생합니다. 그런 다음 전체 MPI C API의 모의 (mock)를 효과적으로 구현할 수 있습니다. 그러면 호출이 올바르게 일치하고 교착 상태를 유발할 수있는 추가 호출이 없는지 확인할 수 있습니다. dlopen()dlsym()을 사용하여 라이브러리의 약한 기호를로드하고 필요할 경우 호출을 전달할 수도 있습니다. MPI는 실제로 강한 PMPI 심볼을 제공하므로 dlopen() 및 친구들을 사용할 필요가 없습니다.

C에 대한 단위 테스트의 많은 이점을 알 수 있습니다. 약간 더 어렵고 Ruby 또는 Java로 작성된 것과 동일한 수준의 보상 범위를 얻을 수 없을 수도 있지만 확실히 가치가 있습니다 하기.

+3

지금까지 최선의 대답입니다. 실제 단위 테스트를 수행하기 위해 코드 도달 범위를 분리하는 주요 기술인 C에서 Dependency Injection의 실질적인 필요성을 처리하는 방법을 제시합니다. – kirakun

11

가장 기본적인 수준에서 단위 테스트는 다른 코드 비트를 실행하고 예상대로 작동하는지 알려주는 코드 비트입니다.

main() 함수를 사용하여 일련의 테스트 기능을 실행하는 새 콘솔 앱을 간단하게 만들 수 있습니다. 각 테스트는 앱에서 함수를 호출하고 성공을 위해 0을 반환하고 실패를 위해 다른 값을 반환합니다.

나는 약간의 예제 코드를 제공 하겠지만, 나는 C와 녹슬었고, 약간의 프레임 워크가 있기 때문에 조금 더 쉽게 만들 수있을 것이라고 확신한다.

+4

될 것입니다 :

위의 테스트 코드가 출력 다음 HTTP : //en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C – tobsen

+0

아하하 목록. 저보다 더 많은 프레임 워크가 있습니다. –

1

나는 assert를 사용한다. 그것은 실제로 프레임 워크가 아닙니다.

+4

assert는 테스트에 적합하지 않습니다. 테스트 프로그램이 첫 번째 실패시 종료되고 자동 테스트 및 테스트보고가 심각하게 제한되기 때문입니다. –

+0

Assert는 단위 테스트의 일부로 또는 함수가 제대로 호출되는 것을 보장하는 부분으로 사용될 수 있지만 "단위 테스트"가 아닙니다. –

3

단위 테스트를 수행하는 가장 간단한 방법은 다른 코드와 링크됩니다 간단한 드라이버 코드를 구축하고, 각각의 경우에 각 기능 ...를 호출하고 함수의 결과 값을 주장하고 구축하는 것입니다 조금씩은 ... 그게 내가 어쨌든이 도움이

 
int main(int argc, char **argv){ 

    // call some function 
    int x = foo(); 

    assert(x > 1); 

    // and so on.... 

} 

희망, 안부, 톰 그것을 할 방법입니다. 본질적으로 분리 코드의 작은 조각을 테스트하는 방법에 대한 객체 지향 아무것도 없다

4

. 절차 언어에서는 함수와 그 집합을 테스트합니다.

당신이 필사적이고 필사적이어야한다면, 나는 작은 C 전처리 기와 gmake 기반 프레임 워크를 함께 썼다. 그것은 장난감으로 시작했고 실제로 자랐지는 않았지만 을 사용하여 중간 크기 (10,000 개 이상의 라인) 프로젝트를 개발하고 테스트했습니다.

Dave's Unit Test은 최소한 침입 적이지만 원래는 전처리 기반 프레임 워크에서는 불가능했던 일부 테스트를 수행 할 수 있습니다 (특정 조건에서 세그먼트 확장 오류가 발생할 수 있음을 요구할 수 있습니다. 당신을 위해).

또한 전 처리기를 많이 사용하는 것이 안전하게 수행되는 이유는 하드입니다.

1

C를 사용하면 단순히 기존 코드 위에 프레임 워크를 구현하는 것 이상을 수행해야합니다.

필자가 해왔 던 한 가지 사실은 코드를 테스트하기 위해 약간의 테스트를 실행할 수있는 테스트 모듈을 (메인과 함께) 만드는 것입니다. 이를 통해 코드와 테스트 사이클 사이에 아주 작은 단위로 증가시킬 수 있습니다.

큰 관심사는 테스트 할 수 있도록 코드를 작성하는 것입니다. 공유 변수 또는 상태에 의존하지 않는 작고 독립적 인 기능에 중점을 둡니다. "기능적"방식 (상태없이)으로 글쓰기를 시도하면 테스트하기가 더 쉬울 것입니다. 의존성이 항상 있거나 데이터베이스와 같이 느릴 수없는 경우 테스트 중에 데이터베이스 대신 사용할 수있는 전체 "모의"계층을 작성해야 할 수 있습니다.

원리 단위 테스트의 목표는 여전히 적용 등, 항상 주어진 상태로 재설정, 시험 일정 시험 코드를 확인 ... 내가 배치했다

내가 (다시 윈도우 전) C 코드를 작성

파일을 편집 한 다음 편집을 마쳤을 때 컴파일, 링크, 테스트를 실행 한 다음 빌드 결과, 테스트 결과 및 코드를 다른 창에 표시합니다. 휴식 시간 (컴파일되는 시간에 따라 수 시간이 소요됨) 후에 결과를 검토하고 곧바로 편집 할 수 있습니다. 요즘에는이 프로세스가 향상 될 수있을 것이라고 확신합니다.

6

libtap은 테스트가 실패 할 때 진단을 제공 할 수있는 많은 기능을 제공합니다. 사용 예 :

#include <mystuff.h> 
#include <tap.h> 

int main() { 
    plan(3); 
    ok(foo(), "foo returns 1"); 
    is(bar(), "bar", "bar returns the string bar"); 
    cmp_ok(baz(), ">", foo(), "baz returns a higher number than foo"); 
    done_testing; 
} 

다른 언어의 탭 라이브러리와 유사합니다.

3

다음은 라이브러리 함수를 호출 할 수있는 특정 함수에 대해 단일 테스트 프로그램에서 여러 테스트를 구현하는 방법의 예입니다.

#include <stdlib.h> 

int my_div(int x, int y) 
{ 
    if (y==0) exit(2); 
    return x/y; 
} 

우리는 다음 테스트 프로그램 작성 : 경우 부울 변수를 업데이트 assert을 재정의함으로써

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <setjmp.h> 

// redefine assert to set a boolean flag 
#ifdef assert 
#undef assert 
#endif 
#define assert(x) (rslt = rslt && (x)) 

// the function to test 
int my_div(int x, int y); 

// main result return code used by redefined assert 
static int rslt; 

// variables controling stub functions 
static int expected_code; 
static int should_exit; 
static jmp_buf jump_env; 

// test suite main variables 
static int done; 
static int num_tests; 
static int tests_passed; 

// utility function 
void TestStart(char *name) 
{ 
    num_tests++; 
    rslt = 1; 
    printf("-- Testing %s ... ",name); 
} 

// utility function 
void TestEnd() 
{ 
    if (rslt) tests_passed++; 
    printf("%s\n", rslt ? "success" : "fail"); 
} 

// stub function 
void exit(int code) 
{ 
    if (!done) 
    { 
     assert(should_exit==1); 
     assert(expected_code==code); 
     longjmp(jump_env, 1); 
    } 
    else 
    { 
     _exit(code); 
    } 
} 

// test case 
void test_normal() 
{ 
    int jmp_rval; 
    int r; 

    TestStart("test_normal"); 
    should_exit = 0; 
    if (!(jmp_rval=setjmp(jump_env))) 
    { 
     r = my_div(12,3); 
    } 

    assert(jmp_rval==0); 
    assert(r==4); 
    TestEnd(); 
} 

// test case 
void test_div0() 
{ 
    int jmp_rval; 
    int r; 

    TestStart("test_div0"); 
    should_exit = 1; 
    expected_code = 2; 
    if (!(jmp_rval=setjmp(jump_env))) 
    { 
     r = my_div(2,0); 
    } 

    assert(jmp_rval==1); 
    TestEnd(); 
} 

int main() 
{ 
    num_tests = 0; 
    tests_passed = 0; 
    done = 0; 
    test_normal(); 
    test_div0(); 
    printf("Total tests passed: %d\n", tests_passed); 
    done = 1; 
    return !(tests_passed == num_tests); 
} 

을, 당신이 계속

생각에는 우리는 다음과 같은 모듈을 테스트 할 어설 션이 실패하고 여러 테스트를 실행하여 성공한 횟수와 실패한 횟수를 추적합니다.

각 테스트를 시작할 때 assert 매크로에서 사용하는 변수 인 rslt을 1로 설정하고 스텁 기능을 제어하는 ​​변수를 설정하십시오.스텁 중 하나가 두 번 이상 호출되면 제어 변수 배열을 설정하여 스텁이 다른 호출에 대한 여러 조건을 확인할 수 있습니다.

많은 라이브러리 함수가 약한 기호이므로 테스트 프로그램에서 재정 의하여 함수를 호출 할 수 있습니다. 테스트 할 함수를 호출하기 전에 많은 수의 상태 변수를 설정하여 스텁 함수의 동작을 제어하고 함수 매개 변수의 조건을 확인할 수 있습니다.

그런 정의를 다시 할 수없는 경우 스텁 함수에 다른 이름을 지정하고 테스트 할 코드에서 심볼을 다시 정의하십시오. 예를 들어, fopen을 스터핑하려고하지만 약한 기호가 아닌 경우 스텁을 my_fopen으로 정의하고 테스트 할 파일을 -Dfopen=my_fopen으로 컴파일하십시오.

이 특정 경우에 테스트 할 함수는 exit을 호출 할 수 있습니다. exit이 테스트중인 기능으로 돌아갈 수 없기 때문에 이것은 까다 롭습니다. 이것은 setjmplongjmp을 사용하는 것이 의미있는 드문 경우 중 하나입니다. 테스트 할 함수를 입력하기 전에 setjmp을 사용하면 스텁 된 exit에서 longjmp으로 전화하여 테스트 케이스로 직접 되돌아갑니다.

다시 정의 된 exit에는 실제로 프로그램을 종료할지 확인하기 위해 확인하는 특수 변수가 있으며 _exit을 호출합니다. 이 작업을 수행하지 않으면 테스트 프로그램이 정상적으로 종료되지 않을 수 있습니다.

또한이 테스트 스위트는 시도한 테스트와 실패한 테스트의 수를 계산하고 모든 테스트가 통과되면 0을 반환하고 그렇지 않으면 1을 반환합니다. 그런 식으로 make은 테스트 실패를 확인하고 이에 따라 조치 할 수 있습니다. 당신은 여전히 ​​사용할 C의 단위 테스트 프레임 워크에 대한 목록을 시도하는 프레임 워크를 찾는 경우

-- Testing test_normal ... success 
-- Testing test_div0 ... success 
Total tests passed: 2 

을 그리고 리턴 코드가 0

관련 문제