---
# System prepended metadata

title: Unit test
tags: [unit test, cmocka]

---

# Unit test

###### tags: `unit test` `cmocka`

- [土砲寫unit test](#土砲寫unit-test)
- [cmocka](#cmocka)

## 土砲寫unit test

### 一個簡單的unit test

先來一個例子，首先寫個`sum()`

```c=
int sum(int a, int b)
{
    return (a + b);
}
```

接著寫一個測試

```c=
int test_sum()
{
    return (3 == sum(1, 2) ? 0 : 1);
}

void main(void)
{
    if(test_sum() == 0)
    {
        printf("Success!\n");
    }
    else
    {
        printf("Failure!!\n");
    }
}
```

build它，執行它，gcov它

```bash
$ gcc -o test -fprofile-arcs -ftest-coverage sum.c
$ ./test
Success!
$ gcov -f sum.c
Function 'main'
Lines executed:80.00% of 5

Function 'test_sum'
Lines executed:100.00% of 2

Function 'sum'
Lines executed:100.00% of 2

File 'sum.c'
Lines executed:88.89% of 9
Creating 'sum.c.gcov'
$
```

compile的時候加上 `-fprofile-arcs -ftest-coverage` 可以檢查coverage

### Wrap Function

單元測試只測單元邏輯，不測其他。所以遇到refer functions就用wrap的加強記憶。

首先來個開檔

```c=
#include <stdlib.h>

int getSomethingInFile(const char *path)
{
    FILE *fp = fopen(path, "r");

    if(!fp)
    {
        return -1;
    }
    return 0;
}
```

但是因為要測試開檔失敗的，所以寫個wrap

```c=
FILE *__wrap_fopen(const char *filename, const char *mode)
{
    return NULL;
}

int test_getSomethingInFile_fail(void)
{
    return (-1 == getSomethingInFile("abc")? 0 : -1);
}

void main(void)
{
    if(test_getSomethingInFile_fail() == 0)
    {
        printf("Success!\n");
    }
    else
    {
        printf("Failure!!\n");
    }
}
```

然候compiler的時候加上 `-Wl,--wrap=fopen`

```bash
$ gcc -o test -fprofile-arcs -ftest-coverage -Wl,--wrap=fopen sum.c
$ ./test
Success!
Success!
$ gcov sum.c
File 'sum.c'
Lines executed:85.71% of 21
Creating 'sum.c.gcov'

$ gcov -f sum.c
Function 'main'
Lines executed:75.00% of 8

Function 'test_getSomethingInFile_fail'
Lines executed:100.00% of 2

Function 'test_sum'
Lines executed:100.00% of 2

Function 'getSomethingInFile'
Lines executed:80.00% of 5

Function 'sum'
Lines executed:100.00% of 2

Function '__wrap_fopen'
Lines executed:100.00% of 2

File 'sum.c'
Lines executed:85.71% of 21
Creating 'sum.c.gcov'

$
```

### 測試code拆開

把測試程式從 `sum.c` 拔出來成為 `test_sum.c`，然候把 `sum.c` include進來

```c=
#include "sum.c"

// test code
```

build一下

```bash
$ gcc -o test -fprofile-arcs -ftest-coverage -Wl,--wrap=fopen test_sum.c
$ ./test
Success!
Success!
$ gcov -f test_sum.c
Function 'main'
Lines executed:75.00% of 8

Function 'test_getSomethingInFile_fail'
Lines executed:100.00% of 2

Function 'test_sum'
Lines executed:100.00% of 2

Function '__wrap_fopen'
Lines executed:100.00% of 2

Function 'getSomethingInFile'
Lines executed:80.00% of 5

Function 'sum'
Lines executed:100.00% of 2

File 'test_sum.c'
Lines executed:85.71% of 14
Creating 'test_sum.c.gcov'

File 'sum.c'
Lines executed:85.71% of 7
Creating 'sum.c.gcov'

$
```

## cmocka

一個unit test framework

### Installation

裝起來

```bash
$ sudo apt-get install libcmocka0 libcmocka-dev
```

### Run test

以上面的例子測試code可以改寫如下，判斷回傳值都改用 `assert_int_equal()`

```c=
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

#include "sum.c"

FILE *__wrap_fopen(const char *filename, const char *mode)
{
    return NULL;
}

void test_sum(void **state)
{
    assert_int_equal(3, sum(1, 2));
}

void test_getSomethingInFile_fail(void **state)
{
    assert_int_equal(-1, getSomethingInFile("abc"));
}

int main(void)
{
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_sum),
        cmocka_unit_test(test_getSomethingInFile_fail),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
```

build的時候記得link library起來

```bash
$ gcc -o test -fprofile-arcs -ftest-coverage -Wl,--wrap=fopen test_sum.c -lcmocka
```

跑一下可以看到cmocka的輸出

```bash
$ ./test
[==========] tests: Running 2 test(s).
[ RUN      ] test_sum
[       OK ] test_sum
[ RUN      ] test_getSomethingInFile_fail
[       OK ] test_getSomethingInFile_fail
[==========] tests: 2 test(s) run.
[  PASSED  ] 2 test(s).

$ gcov -f test_sum.c
Function 'main'
Lines executed:100.00% of 3

Function 'test_getSomethingInFile_fail'
Lines executed:100.00% of 3

Function 'test_sum'
Lines executed:100.00% of 3

Function '__wrap_fopen'
Lines executed:100.00% of 2

Function 'getSomethingInFile'
Lines executed:80.00% of 5

Function 'sum'
Lines executed:100.00% of 2

File 'test_sum.c'
Lines executed:100.00% of 11
Creating 'test_sum.c.gcov'

File 'sum.c'
Lines executed:85.71% of 7
Creating 'sum.c.gcov'

$
```

### mock

mock是個好東西，一定要用

如上面例子中的__wrap_fopen，我們需要讓fopen有不同的return value，所以我們放個 `mock()`，用 `will_return` 可以丟不同的值進去

```c=
FILE *__wrap_fopen(const char *filename, const char *mode)
{
    return (FILE *)mock();
}

void test_getSomethingInFile_success(void **state)
{
    FILE *f = malloc(1);
    will_return(__wrap_fopen, f);
    assert_int_equal(0, getSomethingInFile("abc"));
}

void test_getSomethingInFile_fail(void **state)
{
    will_return(__wrap_fopen, NULL);
    assert_int_equal(-1, getSomethingInFile("abc"));
}
```

`mock()` 類似 `queue`，可以丟多個不同的值進到mock，再依照順序拿回來


## Ref

- [cmocka: https://api.cmocka.org/](https://api.cmocka.org/)
- [CMocka单元测试框架（支持mock功能）](https://blog.csdn.net/benkaoya/article/details/100933141)