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