# 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)