CppUTest

Reference

CppUTest는 unit test framework이다. C++로 만들어졌으나, C언어에도 쉽게 적용가능하다.

Compile
g++ <test file> -I$(CPPUTEST_HOME)/include -L$(CPPUTEST_HOME)/lib -lCppUTest -lCppUTestExt

참고로 -I<include directory path>, -L<library directory path>, 그리고 -l<library file name>이다. library는 항상 lib로 시작하고, 확장자는 .a 이므로 이러한 정보는 생략하여 표시한다. 즉, -lCppUTest는 실제로 libCppUtest.a와 링크하라는 의미이다.

Mac에서는 brew를 이용하여 설차하였는데 설치된 경로는 /usr/local/opt/cpputest이었다. 이유는 알수 없으나 컴파일시에 $(CPPUTEST)의 괄호를 제거해주어야만 제대로 되었다.

'C' 카테고리의 다른 글

extern "C"의 사용  (0) 2014.05.26
Unity를 이용한 TDD - 2  (0) 2014.04.13
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04

extern “C”의 사용

Reference
기본 code
/* add.h */
int add(int a, int b);
/* add.c */
int add(int a, int b)
{
    return a + b;
}
/* hello.cpp */
#include <stdio.h>

#include "add.h"

int main(void)
{
    printf("hello world\n");
    add(3,5);

    return 0;
}

hello.cpp의 add 심볼을 보면, c++ 형태로 되어 있다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c hello.cpp 
Jungguui-MacBook-Pro:sand_project hyde1004$ nm hello.o
0000000000000070 s EH_frame0
000000000000003f s L_.str
                 U __Z3addii
0000000000000000 T _main
0000000000000088 S _main.eh
                 U _printf

add.c의 add 심볼 역시, c++ 형태이다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c add.c 
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
Jungguui-MacBook-Pro:sand_project hyde1004$ nm add.o
0000000000000038 s EH_frame0
0000000000000000 T __Z3addii
0000000000000050 S __Z3addii.eh

add.c를 다음과 같이 수정하고, 심볼을 살펴보면 c형태로 되어 있음을 확인할 수 있다.

/* add.c */
#ifdef __cplusplus
extern "C" {
#endif

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

#ifdef __cplusplus
}
#endif
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c add.c 
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
Jungguui-MacBook-Pro:sand_project hyde1004$ nm add.o
0000000000000038 s EH_frame0
0000000000000000 T _add
0000000000000050 S _add.eh

현재 상태에서 링크를 시도하면, hello.o와 add.o의 심볼이 달라서 에러가 발생한다. hello.o의 add()는 c++형태이고, add.o는 c형태이다.

Jungguui-MacBook-Pro:sand_project hyde1004$ g++ hello.o add.o 
Undefined symbols for architecture x86_64:
  "add(int, int)", referenced from:
      _main in hello.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

다음과 같이 hello.cpp를 수정하고 다시 링크를 해보자. 정상적으로 링크가 완료된다. hello.cpp의 extern "C"를 add.h로 옮기는 것도 괜찮을 것 같다.

/* hello.cpp */
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "add.h"

#ifdef __cplusplus
}
#endif

int main(void)
{
    printf("hello world\n");
    add(3,5);

    return 0;
}
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ -c hello.cpp 
Jungguui-MacBook-Pro:sand_project hyde1004$ nm hello.o
0000000000000070 s EH_frame0
000000000000003f s L_.str
                 U _add
0000000000000000 T _main
0000000000000088 S _main.eh
                 U _printf
Jungguui-MacBook-Pro:sand_project hyde1004$ g++ add.o hello.o

'C' 카테고리의 다른 글

CppUTest  (0) 2014.05.26
Unity를 이용한 TDD - 2  (0) 2014.04.13
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04

Unity를 이용한 TDD - 2

References
  1. Unity 의 examples/example_2

이번 글에서는 이전 편과는 다른 순서로 진행한다. 이전 편에서는 함수를 만들고, Test Case를 구성하고, Test Runner 순으로 진행했으나, 이번 편에서는 반대순으로 진행해본다. 왜냐하면 Test Runner을 만드는 관점을 충분히 이해하지 못했기 때문이다.

/* all_test.c */
#include "unity_fixture.h"

static void RunAllTests(void)
{
    RUN_TEST_GROUP(add);
    RUN_TEST_GROUP(abstract);
}

int main(int argc, char * argv[])
{
    return UnityMain(argc, argv, RunAllTests);
}

all_test.c는 Unity를 실행해주는 main( )이다. RunAllTests( )에는 Test Group을 나열해 준다. Test Group은 동일한 모듈을 테스트할 Test Cases을 모아놓은 Container이다. 여기서는 add와 abstract을 Test한다.

/* TestAdd_Runner.c    */
#include "unity.h"
#include "unity_fixture.h"

TEST_GROUP_RUNNER(add)
{
    RUN_TEST_CASE(add, positive_add);
    RUN_TEST_CASE(add, negative_add);
}

TEST_GROUP_RUNNER(abstract)
{
    RUN_TEST_CASE(abstract, positive_abstract);
    RUN_TEST_CASE(abstract, negative_abstract);
}

TestAdd_Runner.c는 Test Runner이다. (원래는 add만 있었음으로 파일이름이 TestAdd_Runner.c였으나, 나중에 abstract을 추가함) 여기는 main( )에 있던 Test Group에 Test Case을 등록하는 것이다. 즉, Test Group에 따른 모든 Test Case를 적어두는 것이다. 이전의 example_1과 비교하면 Test Runner만드는 법이 매우 쉽다. 또한 ruby script를 쓸 필요도 없다. 다만, 만약 여기에 포함되지 않은 Test Case가 있다면, Test되지 않는다. 따라서, Test Case를 추가했다면, 반드시 TEST_GROUP_RUNNER에 추가해주어야 한다.

/* TestAdd.c */
#include "add.h"
#include "abstract.h"
#include "unity.h"
#include "unity_fixture.h"

TEST_GROUP(add);

TEST_SETUP(add)
{

}

TEST_TEAR_DOWN(add)
{

}

TEST(add, positive_add)
{
    TEST_ASSERT_EQUAL(3, add(1,2));
    TEST_ASSERT_EQUAL(7, add(3,4));
    TEST_ASSERT_EQUAL(43, add(10, 33));
}

TEST(add, negative_add)
{
    TEST_ASSERT_EQUAL(-2, add(-1,-1));
    TEST_ASSERT_EQUAL(-4, add(-3, -1));
    TEST_ASSERT_EQUAL(-12, add(-10,-2));
}

TEST_GROUP(abstract);

TEST_SETUP(abstract)
{

}

TEST_TEAR_DOWN(abstract)
{

}

TEST(abstract, positive_abstract)
{
    TEST_ASSERT_EQUAL(5, abstract(8,3));
    TEST_ASSERT_EQUAL(8, abstract(10,2));
    TEST_ASSERT_EQUAL(2, abstract(10,8));
}

TEST(abstract, negative_abstract)
{
    TEST_ASSERT_EQUAL(-3, abstract(5, 8));
    TEST_ASSERT_EQUAL(-10, abstract(10, 20));
    TEST_ASSERT_EQUAL(-7, abstract(10, 17));
}

TestAdd.c (처음에는 add Test Case만 있었으나, abstract을 추가)에는 Test Case를 서술한다. 예제에서는 add와 abstract을 별도 함수로 분리하였으나, 여기 예제처럼 하나의 파일로도 가능하다.
모든 Test Group에서는 TEST_SETUP( ), TEST_TEAR_DOWN( )은 반드시 있어야 하며 그렇지 않으면 컴파일 에러가 발생한다.

다음은 test를 위한 source이다.

/* add.c */
#include "add.h"

int add(int a, int b)
{
    return a + b;
}
/* abstract.c */
#include "abstract.h"

int abstract(int a, int b)
{
    return a - b;
}
/* add.h */
int add(int a, int b);
/* abstract.h */
int abstract(int a, int b);

컴파일하는 법은 다음과 같다. 실제 프로젝트에 이용할때는 Makefile에 src, Test, Unity src로 분리하여 구성하는 것이 좋겠다.

gcc src/add.c                                 \
    src/abstract.char                        \
    -Isrc/include 

    -I../Unity-master/extras/fixture/src     \
    -I../Unity-master/src/                     \
    ../Unity-master/src/unity.c             \
    ../Unity-master/extras/fixture/src/unity_fixture.c     \ 

    test/TestAdd.c                             \    
    test/test_runners/TestAdd_Runner.c         \
    test/test_runners/all_tests.c

'C' 카테고리의 다른 글

CppUTest  (0) 2014.05.26
extern "C"의 사용  (0) 2014.05.26
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04

volatile

References

volatile 키워드에 대해서 찾아보게 된 계기는 ‘Head First Design Patterns’의 싱글톤 패턴에서 volatile을 사용하고 있었기 때문이었다.

C언어 문법 중 완전히 이해되지 않은 부분이 volatile이었는데, 오늘에야 그 의미를 이해하게 되었다. volatile의 의미는 컴파일 최적화를 하지 않는다는 것이다. 그것은 컴파일러의 최적화 옵션과 관련이 있다.

  • 코드는 순서대로 실행되지 않는다.
  • 코드대로 기계어로 변환되지 않는다.

아래는 위키에서 가져온 코드이다.

static volatile int foo;

void bar (void)
{
    foo = 0;

    while (foo != 255);
}

사실 위 코드는 foo값이 255가 될때까지 기다린다. 그러나 volatile 키워드가 없다면, 컴파일러는 아래와 같이 최적화한다.

void bar_optimized(void)
{
    foo = 0;

    while (true);
}

사실 컴파일러 입장에서는 명백하다. foo는 0이고 변경되지 않기 때문에 항상 while은 참이 되므로, foo를 비교할 필요없다고 여기게 된다. 그러나, 실제로는 하드웨어 인터럽트등을 통해 값이 변경될 수 있다는 것이다.
‘Head First Design Patterns’에서는 멀티스레딩에 대한 내용을 서술하면서, 위의 코드에서와 비슷한 상황을 설명하고 있다. 그러나 References의 마지막 링크에서는 조금 다른 이야기를 하고 있으므로 참조할 필요가 있다.

Unity를 이용한 TDD

Reference
  • Unity 의 examples/example_1

Unity는 C언어를 이용한 TDD framework이다. Embedded에 적용할 수 있도록 header file 2개와 c파일 하나로 구성되어 있다.
다만, Test Runner하는 부분이 성가신데, 이는 ruby script를 이용하여 해결할 수 있다.

다음과 같이 단순한 예제를 구성하였다.

/* add.h */
#include <stdint.h>
int32_t add(int32_t a, int32_t b);
/* add.c */
#include <stdint.h>

int32_t add(int32_t a, int32_t b)
{
    return (a+b);
}
/* test_add.c */
#include "add.h"
#include "unity.h"

void setUp(void)
{
    /* 생략시 에러 발생 */
}

void tearDown(void)
{
    /* 생략시 에러 발생 */
}

void test_add(void)
{
    TEST_ASSERT_EQUAL(7, add(3,4));
}

파일은 Unity중 필수적으로 필요한 것만 복사하여 구성하였다.

.
├── add.c
├── add.h
├── test_add.c
└── unity
    ├── generate_test_runner.rb
    ├── unity.c
    ├── unity.h
    └── unity_internals.h

1 directory, 7 files

컴파일 및 실행하는 방법은 다음과 같다.

ruby unity/generate_test_runner.rb test_add.c
gcc -Iunity unity/unity.c add.c test_add_Runner.c
./a.out

test_add.c:11:test_add:PASS
-----------------------
1 Tests 0 Failures 0 Ignored
OK

이러한 방법은 실제로 사용하기에는 다소 불편하다.

  • Test Runner를 만드는 방법이 복잡하다. ruby script를 이용하면 되지만, 번거롭고 ruby가 설치되어 있어야 한다.
  • Test Case가 변경되면, Test Runner도 같이 변경해주어야 한다.
  • shell command에서 명령을 내리기에는 손이 많이 간다.

Unity example에 포함된 makefile을 다음과 같이 수정하였다.

/* makefile */
# ==========================================
#   Unity Project - A Test Framework for C
#   Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
#   [Released under MIT License. Please refer to license.txt for details]
# ==========================================

UNITY_ROOT=./unity
C_COMPILER=gcc
TARGET=testAdd
SRC_FILES=$(UNITY_ROOT)/unity.c add.c test_add.c test_add_Runner.c
INC_DIRS=-I$(UNITY_ROOT)

CLEANUP = rm -f *.o ; rm -f $(TARGET)

all: clean default

default:
        ruby $(UNITY_ROOT)/generate_test_runner.rb test_add.c
        $(C_COMPILER) $(INC_DIRS) $(SRC_FILES) -o $(TARGET)
        ./$(TARGET)

clean:
        $(CLEANUP)

디렉토리 구조나 makefile을 좀 더 적절하게 변경하는 것은 추후에 추가할 예정이다.

그런데 example_1을 따라해보니, 실제로 매우 불편했다.

  • 나 스스로는 Test Runner를 만들지 못하겠다. script를 써야만 했다.
  • Test Group하나만 가능하다. 즉 Test Case 모음은 하나의 파일에만 존재해야 했다. Test Runner를 만들기 위해서 ruby scriptt로 2개의 Test Case 파일을 시도하면 에러가 발생했다. ( main( ) 함수가 2개 생기기 때문이다. )

이러한 문제는 example_2에서 해결되었다.

2.2 헤더 파일 만들기

헤더 파일에 포함되는 내용은 다음과 같다

  • 외부에 공개할 매크로의 정의(함수형 매크로)
  • 외부에 공개할 상수의 정의(#define, enum 등의 정의)
  • 외부에 공개할 구조체, 공용체의 정의
  • 외부에 공개할 자료형의 정의 (typedef를 이용한 자료형의 정의)
  • 외부에 공개할 함수의 원형 선언
  • 외부에 공개할 전역 변수의 extern 선언
  • 이 라이브러리를 이용할 때 필요한 다른 헤더 파일의 인클루드문(예를 들어 함수의 인자나 반환값으로 FILE *형을 이용한다면 stdio.h를 인클루드해야 한다.)
2.2.1 매크로 정의

보통 매크로 함수는 일반적인 함수와 구분해주기 위해 이름을 모두 대문자로 기술하는 것이 관례이다. 꼭 외부에 공개할 필요가 있는 매크로만을 헤더 파일에 기술한다.

매크로가 중복 정의된 경우 gcc는 다음과 같이 처리한다.

  • 두 정의가 같은 내용인 경우, 에러를 발생하지 않고 통과한다.
  • 두 정의가 서로 다른 경우, 컴파일 시에 “redefined”를 알리는 경고가 발생하고, 나중에 정의된 값을 취한다.

매크로 이름의 충돌을 확실히 방지하고 싶다면 아래와 같이 경고를 발생시키는 편이 낫다.

#ifndef MAX
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#else
#warning Duplicated definition of MAX
#endif
2.2.2 상수 정의
2.2.3 구조체, 공용체 정의

헤더 파일에서 구조체를 정의하는 경우, “이 구조체는 라이브러리의 사용자가 멤버 변수를 자유롭게 조작해도 무방하다.”는 의사를 암시적으로 포함하는 셈이 되므로 이에 유의해야 한다.

2.2.4 자료형 정의
2.2.5 함수의 원형 선언

함수 원형은 반드시 명시적으로 선언하고, 컴파일시에 -Wall 옵션을 주어 관련 경고를 모두 출력하도록 하자.

2.2.6 전역 변수의 extern 선언
2.2.7 다른 라이브러리 헤더 파일의 인클루드

원칙적으로는, 라이브러리를 컴파일할 때에만 필요한 경우라면, .c에, 라이브러리를 이용할 때 필요한 경우라면 .h에 인클루드하는 것으로 정해두면 된다.

2.2.8 다중 인클루드 문제

인클루드의 중첩은 다중 인클루드를 유발한다. 다중 인클루드가 발생하면 같은 단어에 대해 #define의 정의나 typedef의 정의가 여러 번 등장하게 된다.

#define의 재정의
  • 같은 단어에 대해 같은 정의를 내리는 경우, 오류나 경고로 처리하지 않는다.
  • 같은 단어에 대해 다른 정의를 내리는 경우, 경고를 출력하고, 나중에 정의된 뜻을 따른다.
typedef의 재정의
  • 같은 자료형에 대해 중복된 정의를 내리면 무조건 에러로 처리한다.

이러한 문제들을 회피하기 위해, #ifndef#endif로 묶어서 같은 헤더 파일 본문이 여러 번 반복되지 않도록 해둔다.

#ifndef    _STRING_H_INCLUDED_
#define    _STRING_H_INCLUDED_
/* header file 본문 */
#endif

'C' 카테고리의 다른 글

Unity를 이용한 TDD - 2  (0) 2014.04.13
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 1.4 헤더 파일 만들기  (0) 2014.03.04
gdb 사용법  (0) 2014.03.03
1.4.7 const 활용

포인터를 const로 정의하면, 그 포인터가 가리키는 값을 변경할 수 없다.

int test(void)
{
    char s[] = "test";
    const char * p = s;
    p[0] = 'T';    /* const 포인터가 가리키는 주소의 값을 변경하려고 시도    */

    return 0;
}
1.4.8 const를 인자로 활용
void strcopy(char * dst, const char * src)
{
    while (*src != '\0')
        *(dst++) = *(src++);
    *dst = '\0';
}
  • const char *로 정의한 포인터가 가리키는 값은 변경할 수 없다. (경고)
  • const char * 포인터를 char *로 형변환 하는 것은 허용되지 않는다. (경고)

const를 반환하는 함수의 활용 예

typedef enum { Black, White, Blue, Red, Green } Color;

const char * ColorName(Color c)
{
    const char * s;
    switch(c)
    {
        case Black: s = "Black" break;
        case White: s = "White" break;
        case Blue : s = "Blue"  break;
        case Red  : s = "Red"   break;
        case Green: s = "Green" break;
        default   : s = "Unknown";
    }

    return s;
}
1.4.9 const를 사용할 때 주의사항

const로 선언할 수 있는 것은 모두 const로 선언하는 것이 좋을 것처럼 여겨진다. 하지만 실제로는 그렇지 않다. 특히 함수가 깊게 중첩되는 경우에는 const를 사용하지 않는 것이 좋다.

'C' 카테고리의 다른 글

Unity를 이용한 TDD - 2  (0) 2014.04.13
volatile  (0) 2014.04.09
Unity를 이용한 TDD  (0) 2014.04.02
[도서] 쉽게 배우는 C프로그래밍 테크닉 - 2.2 헤더 파일 만들기  (0) 2014.03.04
gdb 사용법  (0) 2014.03.03

GDB 사용법

다음 예제는 introduction to GDB a tutorial - Harvard CS50 (http://www.youtube.com/watch?v=sCtY--xRUyI)에서 가져왔다. 프로그램은 factorial을 구하는 것으로 버그를 가지고 있어서 제대로 동작하지 않는다.

/* filename : factorial.c */
#include <stdio.h>

int main(void)
{
        int num;
        int factorial;
        int i;

        do
        {
                printf("Enter a positive integer: ");
                scanf("%d",&num);
        }
        while(num<0);

        for(i=0; i<=num;i++)
                factorial = factorial * i;

        printf("%d! = %d\n", num, factorial);

        return 0;
}
Compile하는 법

Compile option으로 -g을 넣는다.

gcc -g factorial.c`)
gdb 실행법
gdb ./factorial
gdb 종료법
quit
break point 걸기
break main
line별로 실행하기
next # go to next line
run
기타
list

+ Recent posts