zoukankan      html  css  js  c++  java
  • (一)cmockery的readme翻译

    关于如何使用cmockery单元测试框架的信息请看readme.md
    编译
    ---------
    编译cmockery库和样例程序,运行./configure,然后make。在windows命令行环境下运行vsvar.bat
    然后进入到当前工程下的windows子文件夹然后运行nmake
    本代码已经在linux(ubuntu)和windows中使用vc++7和vc++8进行了测试

    ================================================================================

    Cmockery 单元测试框架

    Cmockery 是一个轻量级的库,作者用它来做C单元测试

    本文档基本上从博文:http://blog.csdn.net/oncoding/article/details/4324421中获取的,只是其中的示例代码是我从原版的文档中粘贴过来的。

    目录

    • 动机
    • 概述
    • 执行测试
    • 异常处理
    • 出错处理
    • 断言
    • 断言宏
    • 动态内存分配
    • 模拟函数
    • 返回值
    • 参数检测
    • 测试状态
    • 实例

    动机

        如今已经有很多的C单元测试框架可用,但大多数框架相当复杂,并且对最新的编译器技术有所依赖。有些开发会使用老版本的编译器, 这样就很难使用那些测试框架。另外,许多测试框架会假设待测代码(程序或模块)执行的平台是同一个。正因为这种假设,很多测试框架需要在待测模块中引入C标准库的头文件,这不符合惯例,或者在待测模块中实现一个不完全的C库。

        Cmockery 只需要测试程序与标准C库链接,这能最大限度地减少与标准库头文件的冲突。此外, Cmockery尽量避免使用编译器中比较新的一些特性。

    概述

        Cmockery 的测试与Cmockery库,标准C库,待测模块链接在一起,最总被编译成一个可以独立运行的程序。在测试过程中,待测模块的任何外部信息都应被模拟,即用测试用例中定义的函数返回值来替换。即使会出现待测代码在实际运行环境和测试环境运行时有差异,仍可视为有效。因为它的目的在于代码模块在功能面上的逻辑测试,不必要求所有的行为都和目标环境一致。

        如果不做一些修改,无法将一个模块编译成可执行程序。因此 UNIT_TESTING 预定义应当定义在执行Cmockery单元测试的应用程序中, 编译待测代码时,可用条件编译决定是否参与单元测试。

    执行测试

    Cmockery单元测试用例就是函数,签名为void function(void **state) . Cmockery 测试程序将(多个)测试用例的函数指针初始化到一个表中,使用unit_test*() 宏. 这个表会传给 run_tests() 宏来执行测试用例。 run_tests()将适当的异常/信号句柄,以及其他数据结构的指针装入到测试函数。当单元测试结束时, run_tests() 会显示出各种定义的测试是否成功。

    使用run_tests()

    run_test.c

    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. // A test case that does nothing and succeeds.
    6. void null_test_success(void **state) {
    7. }
    8. int main(int argc, char* argv[]) {
    9. const UnitTest tests[] = {
    10. unit_test(null_test_success),
    11. };
    12. return run_tests(tests);
    13. }

    异常处理

        在测试函数被run_tests() 执行之前,断言/信号句柄会被一个句柄重载,它可以在异常发生时,显示错误并退出测试函数。 如果一个异常发生在测试函数的外部,例如Cmockery本身,程序将终止执行并返回错误码。

    出错处理

        当测试函数执行run_tests() 时,如果错误出现了,当前的测试函数将中断,测试程序继续执行下一测试函数。测试失败的通过CMockery函数fail() 给出最终的标志。导致Cmockery库测试失败的事件如下:

    • 断言
    • 异常
    • 内存泄漏
    • 装载和拆除函数不匹配
    • 模拟返回值出错
    • 模拟返回值没用
    • 预计参数值出错
    • 预计参数值没用

    断言

        运行时的断言宏和C标准库的assert() 相似,需要在待测模块中使用Cmockery的mock_assert() 函数重定义。 通常mock_assert() 表示 测试的失败 . 如果一个函数被expect_assert_failure() 宏调用, 那么在这个函数中就要调用mock_assert() ,测试将进行。若没有调用mock_assert() ,表示次函数测试失败。

    mock_assert()的使用

    assert_module.c

    1. #include <assert.h>
    2. // If unit testing is enabled override assert with mock_assert().
    3. #if UNIT_TESTING
    4. extern void mock_assert(const int result, const char* const expression,
    5. const char * const file, const int line);
    6. #undef assert
    7. #define assert(expression)
    8. mock_assert((int)(expression), #expression, __FILE__, __LINE__);
    9. #endif // UNIT_TESTING
    10. void increment_value(int * const value) {
    11. assert(value);
    12. (*value) ++;
    13. }
    14. void decrement_value(int * const value) {
    15. if (value) {
    16. *value --;
    17. }
    18. }

    assert_module_test.c
    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. extern void increment_value(int * const value);
    6. /* This test case will fail but the assert is caught by run_tests() and the
    7. * next test is executed. */
    8. void increment_value_fail(void **state) {
    9. increment_value(NULL);
    10. }
    11. // This test case succeeds since increment_value() asserts on the NULL pointer.
    12. void increment_value_assert(void **state) {
    13. expect_assert_failure(increment_value(NULL));
    14. }
    15. /* This test case fails since decrement_value() doesn't assert on a NULL
    16. * pointer. */
    17. void decrement_value_fail(void **state) {
    18. expect_assert_failure(decrement_value(NULL));
    19. }
    20. int main(int argc, char *argv[]) {
    21. const UnitTest tests[] = {
    22. unit_test(increment_value_fail),
    23. unit_test(increment_value_assert),
    24. unit_test(decrement_value_fail),
    25. };
    26. return run_tests(tests);
    27. }

    断言宏

        Cmockery提供了一系列的断言宏,在测试程序的使用方法和C标准中的用法一致。 当断言错误发生时,Cmockery的断言宏会将这个这个错误输出到标准错误流,并把这个测试标记为失败。 由于标准C库中assert()的是限制,Cmockery的assert_true() 和 assert_false() 宏只能显示导致断言失败的表达式。Cmockery中和具体类型相关的断言宏, assert_{类型}_equal() and assert_{类型}_not_equal(), 显示那些导致断言失败的数据, 这样可以增加数据的可视化,辅助调试那些出错的测试用例。

    assert_{类型}_equal()宏的使用

    assert_macro.c

    1. #include <string.h>
    2. static const char* status_code_strings[] = {
    3. "Address not found",
    4. "Connection dropped",
    5. "Connection timed out",
    6. };
    7. const char* get_status_code_string(const unsigned int status_code) {
    8. return status_code_strings[status_code];
    9. };
    10. unsigned int string_to_status_code(const char* const status_code_string) {
    11. unsigned int i;
    12. for (i = 0; i < sizeof(status_code_strings) /
    13. sizeof(status_code_strings[0]); i++) {
    14. if (strcmp(status_code_strings[i], status_code_string) == 0) {
    15. return i;
    16. }
    17. }
    18. return ~0U;
    19. }

    assert_macro_test.c
    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. extern const char* get_status_code_string(const unsigned int status_code);
    6. extern unsigned int string_to_status_code(
    7. const char* const status_code_string);
    8. /* This test will fail since the string returned by get_status_code_string(0)
    9. * doesn't match "Connection timed out". */
    10. void get_status_code_string_test(void **state) {
    11. assert_string_equal(get_status_code_string(0), "Address not found");
    12. assert_string_equal(get_status_code_string(1), "Connection timed out");
    13. }
    14. // This test will fail since the status code of "Connection timed out" isn't 1
    15. void string_to_status_code_test(void **state) {
    16. assert_int_equal(string_to_status_code("Address not found"), 0);
    17. assert_int_equal(string_to_status_code("Connection timed out"), 1);
    18. }
    19. int main(int argc, char *argv[]) {
    20. const UnitTest tests[] = {
    21. unit_test(get_status_code_string_test),
    22. unit_test(string_to_status_code_test),
    23. };
    24. return run_tests(tests);
    25. }


    动态内存分配

        为了能用Cmockery测试待测模块中的内存泄漏,缓存溢出和underflows问题,应该将对malloc() 、 calloc() 和free() 的调用分别替换成test_malloc() 、test_calloc() 和 test_free() 。每次释放块内存是使用test_free() ,如果一个内存块被 标记为测试失败 ,它将检测内存崩溃。所有块的内存分配使用 test_*() 函数,Cmockery库将跟踪它们。当测试完成时,如果有任何分配的块没有被释放(内存泄漏), 这些信息会被记录,此测试标记为失败。

    为了简单起见,Cmockery会在同一进程中执行所有的测试。因此,一个测试程序的所有测试用例,共用一个单独的地址空间, 也就是说,一个测试用例的内存崩溃,会导致整个测试程序的提前结束。

    Cmockery的分配器的使用

    allocate_module.c

    1. #include <malloc.h>
    2. #if UNIT_TESTING
    3. extern void* _test_malloc(const size_t size, const char* file, const int line);
    4. extern void* _test_calloc(const size_t number_of_elements, const size_t size,
    5. const char* file, const int line);
    6. extern void _test_free(void* const ptr, const char* file, const int line);
    7. #define malloc(size) _test_malloc(size, __FILE__, __LINE__)
    8. #define calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__)
    9. #define free(ptr) _test_free(ptr, __FILE__, __LINE__)
    10. #endif // UNIT_TESTING
    11. void leak_memory() {
    12. int * const temporary = (int*)malloc(sizeof(int));
    13. *temporary = 0;
    14. }
    15. void buffer_overflow() {
    16. char * const memory = (char*)malloc(sizeof(int));
    17. memory[sizeof(int)] = '!';
    18. free(memory);
    19. }
    20. void buffer_underflow() {
    21. char * const memory = (char*)malloc(sizeof(int));
    22. memory[-1] = '!';
    23. free(memory);
    24. }

    allocate_module_test.c
    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. extern void leak_memory();
    6. extern void buffer_overflow();
    7. extern void buffer_underflow();
    8. // Test case that fails as leak_memory() leaks a dynamically allocated block.
    9. void leak_memory_test(void **state) {
    10. leak_memory();
    11. }
    12. // Test case that fails as buffer_overflow() corrupts an allocated block.
    13. void buffer_overflow_test(void **state) {
    14. buffer_overflow();
    15. }
    16. // Test case that fails as buffer_underflow() corrupts an allocated block.
    17. void buffer_underflow_test(void **state) {
    18. buffer_underflow();
    19. }
    20. int main(int argc, char* argv[]) {
    21. const UnitTest tests[] = {
    22. unit_test(leak_memory_test),
    23. unit_test(buffer_overflow_test),
    24. unit_test(buffer_underflow_test),
    25. };
    26. return run_tests(tests);
    27. }

    模拟的函数

        一个单元测试最好能将待测函数或模块从外部依赖中隔离。 这就会用到模拟函数,它通过动态或静态方式链接到待测模块中去。 当被测代码直接引用外部函数是,模拟函数必须静态链接。 动态链接是一个简单的过程,将一个函数指针放到一个表中,给待测模块中一个测试用例定义的模拟函数引用。

    (模拟函数)返回值

        为了简化模拟函数的实现,Cmockery 提供了给模拟函数的每个测试用例存放返回值的功能,使用的是will_return() 函数。然后,这些值将通过每个模拟函数调用mock() 返回。 传给will_return() 的值,将分别添加到每个函数所特有的队列中去。连续调用 mock() ,将从函数的队列中移除一个返回值。 这使一个模拟函数通过多次调用mock() ,来返回(多个)输出参数和(一个)返回值成为可能。 此外,一个模拟函数多次调用(多个)返回值的做法也是可以的。

    will_return()的使用

    database.h

    1. typedef struct DatabaseConnection DatabaseConnection;
    2. /* Function that takes an SQL query string and sets results to an array of
    3. * pointers with the result of the query. The value returned specifies the
    4. * number of items in the returned array of results. The returned array of
    5. * results are statically allocated and should not be deallocated using free()
    6. */
    7. typedef unsigned int (*QueryDatabase)(
    8. DatabaseConnection* const connection, const char * const query_string,
    9. void *** const results);
    10. // Connection to a database.
    11. struct DatabaseConnection {
    12. const char *url;
    13. unsigned int port;
    14. QueryDatabase query_database;
    15. };
    16. // Connect to a database.
    17. DatabaseConnection* connect_to_database(const char * const url,
    18. const unsigned int port);

    customer_database.c
    1. #include <stddef.h>
    2. #include <stdio.h>
    3. #include <database.h>
    4. #ifdef _WIN32
    5. #define snprintf _snprintf
    6. #endif // _WIN32
    7. // Connect to the database containing customer information.
    8. DatabaseConnection* connect_to_customer_database() {
    9. return connect_to_database("customers.abcd.org", 321);
    10. }
    11. /* Find the ID of a customer by his/her name returning a value > 0 if
    12. * successful, 0 otherwise. */
    13. unsigned int get_customer_id_by_name(
    14. DatabaseConnection * const connection,
    15. const char * const customer_name) {
    16. char query_string[256];
    17. int number_of_results;
    18. void **results;
    19. snprintf(query_string, sizeof(query_string),
    20. "SELECT ID FROM CUSTOMERS WHERE NAME = %s", customer_name);
    21. number_of_results = connection->query_database(connection, query_string,
    22. &results);
    23. if (number_of_results != 1) {
    24. return -1;
    25. }
    26. return (unsigned int)results[0];
    27. }

    customer_database_test.c
    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. #include <database.h>
    6. extern DatabaseConnection* connect_to_customer_database();
    7. extern unsigned int get_customer_id_by_name(
    8. DatabaseConnection * const connection, const char * const customer_name);
    9. // Mock query database function.
    10. unsigned int mock_query_database(
    11. DatabaseConnection* const connection, const char * const query_string,
    12. void *** const results) {
    13. *results = (void**)mock();
    14. return (unsigned int)mock();
    15. }
    16. // Mock of the connect to database function.
    17. DatabaseConnection* connect_to_database(const char * const database_url,
    18. const unsigned int port) {
    19. return (DatabaseConnection*)mock();
    20. }
    21. void test_connect_to_customer_database(void **state) {
    22. will_return(connect_to_database, 0x0DA7ABA53);
    23. assert_true(connect_to_customer_database() ==
    24. (DatabaseConnection*)0x0DA7ABA53);
    25. }
    26. /* This test fails as the mock function connect_to_database() will have no
    27. * value to return. */
    28. void fail_connect_to_customer_database(void **state) {
    29. will_return(connect_to_database, 0x0DA7ABA53);
    30. assert_true(connect_to_customer_database() ==
    31. (DatabaseConnection*)0x0DA7ABA53);
    32. }
    33. void test_get_customer_id_by_name(void **state) {
    34. DatabaseConnection connection = {
    35. "somedatabase.somewhere.com", 12345678, mock_query_database
    36. };
    37. // Return a single customer ID when mock_query_database() is called.
    38. int customer_ids = 543;
    39. will_return(mock_query_database, &customer_ids);
    40. will_return(mock_query_database, 1);
    41. assert_int_equal(get_customer_id_by_name(&connection, "john doe"), 543);
    42. }
    43. int main(int argc, char* argv[]) {
    44. const UnitTest tests[] = {
    45. unit_test(test_connect_to_customer_database),
    46. unit_test(fail_connect_to_customer_database),
    47. unit_test(test_get_customer_id_by_name),
    48. };
    49. return run_tests(tests);
    50. }

    参数检测

        除了存储模拟函数的返回值之外,Cmockery还提供了对模拟函数参数期望值的存储功能,使用的是 expect_*()函数,一个模拟函数的参数可以通过check_expected()宏来做有效的验证.

        连续调用expect_*()宏,是用队列中的一个参数值来检测给定的参数。 check_expected()检测一个的函数参数,它与expect_*()相对应,即将出队的值。 如果参数检验失败,这个测试将标记为失败。 此外,如果调用check_expected()时,队列中没有参数值出队,测试也会失败。

    expect_*()的使用

    product_database.c

    1. #include <database.h>
    2. // Connect to the database containing customer information.
    3. DatabaseConnection* connect_to_product_database() {
    4. return connect_to_database("products.abcd.org", 322);
    5. }

    product_database_test.c
    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <cmockery.h>
    5. #include <database.h>
    6. extern DatabaseConnection* connect_to_product_database();
    7. /* Mock connect to database function.
    8. * NOTE: This mock function is very general could be shared between tests
    9. * that use the imaginary database.h module. */
    10. DatabaseConnection* connect_to_database(const char * const url,
    11. const unsigned int port) {
    12. check_expected(url);
    13. check_expected(port);
    14. return (DatabaseConnection*)mock();
    15. }
    16. void test_connect_to_product_database(void **state) {
    17. expect_string(connect_to_database, url, "products.abcd.org");
    18. expect_value(connect_to_database, port, 322);
    19. will_return(connect_to_database, 0xDA7ABA53);
    20. assert_int_equal(connect_to_product_database(), 0xDA7ABA53);
    21. }
    22. /* This test will fail since the expected URL is different to the URL that is
    23. * passed to connect_to_database() by connect_to_product_database(). */
    24. void test_connect_to_product_database_bad_url(void **state) {
    25. expect_string(connect_to_database, url, "products.abcd.com");
    26. expect_value(connect_to_database, port, 322);
    27. will_return(connect_to_database, 0xDA7ABA53);
    28. assert_int_equal((int)connect_to_product_database(), 0xDA7ABA53);
    29. }
    30. /* This test will fail since the mock connect_to_database() will attempt to
    31. * retrieve a value for the parameter port which isn't specified by this
    32. * test function. */
    33. void test_connect_to_product_database_missing_parameter(void **state) {
    34. expect_string(connect_to_database, url, "products.abcd.org");
    35. will_return(connect_to_database, 0xDA7ABA53);
    36. assert_int_equal((int)connect_to_product_database(), 0xDA7ABA53);
    37. }
    38. int main(int argc, char* argv[]) {
    39. const UnitTest tests[] = {
    40. unit_test(test_connect_to_product_database),
    41. unit_test(test_connect_to_product_database_bad_url),
    42. unit_test(test_connect_to_product_database_missing_parameter),
    43. };
    44. return run_tests(tests);
    45. }
        

    测试状态

        Cmockery允许每个测试用例多次装载和卸载函数的做法, 装载函数,通过unit_test_setup()或unit_test_setup_teardown() 宏给出, 支持多个测试用例公共的通用初始化; 此外,卸载函数,通过unit_test_teardown() 或 unit_test_setup_teardown() 宏,给出一个测试用例在执行失败情况下的代码路径。

    unit_test_setup_teardown()的使用

    key_value.c

    1. #include <stddef.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. typedef struct KeyValue {
    5. unsigned int key;
    6. const char* value;
    7. } KeyValue;
    8. static KeyValue *key_values = NULL;
    9. static unsigned int number_of_key_values = 0;
    10. void set_key_values(KeyValue * const new_key_values,
    11. const unsigned int new_number_of_key_values) {
    12. key_values = new_key_values;
    13. number_of_key_values = new_number_of_key_values;
    14. }
    15. // Compare two key members of KeyValue structures.
    16. int key_value_compare_keys(const void *a, const void *b) {
    17. return (int)((KeyValue*)a)->key - (int)((KeyValue*)b)->key;
    18. }
    19. // Search an array of key value pairs for the item with the specified value.
    20. KeyValue* find_item_by_value(const char * const value) {
    21. unsigned int i;
    22. for (i = 0; i < number_of_key_values; i++) {
    23. if (strcmp(key_values[i].value, value) == 0) {
    24. return &key_values[i];
    25. }
    26. }
    27. return NULL;
    28. }
    29. // Sort an array of key value pairs by key.
    30. void sort_items_by_key() {
    31. qsort(key_values, number_of_key_values, sizeof(*key_values),
    32. key_value_compare_keys);
    33. }

    key_value_test.c

    1. #include <stdarg.h>
    2. #include <stddef.h>
    3. #include <setjmp.h>
    4. #include <string.h>
    5. #include <cmockery.h>
    6. /* This is duplicated here from the module setup_teardown.c to reduce the
    7. * number of files used in this test. */
    8. typedef struct KeyValue {
    9. unsigned int key;
    10. const char* value;
    11. } KeyValue;
    12. void set_key_values(KeyValue * const new_key_values,
    13. const unsigned int new_number_of_key_values);
    14. extern KeyValue* find_item_by_value(const char * const value);
    15. extern void sort_items_by_key();
    16. static KeyValue key_values[] = {
    17. { 10, "this" },
    18. { 52, "test" },
    19. { 20, "a" },
    20. { 13, "is" },
    21. };
    22. void create_key_values(void **state) {
    23. KeyValue * const items = (KeyValue*)test_malloc(sizeof(key_values));
    24. memcpy(items, key_values, sizeof(key_values));
    25. *state = (void*)items;
    26. set_key_values(items, sizeof(key_values) / sizeof(key_values[0]));
    27. }
    28. void destroy_key_values(void **state) {
    29. test_free(*state);
    30. set_key_values(NULL, 0);
    31. }
    32. void test_find_item_by_value(void **state) {
    33. unsigned int i;
    34. for (i = 0; i < sizeof(key_values) / sizeof(key_values[0]); i++) {
    35. KeyValue * const found = find_item_by_value(key_values[i].value);
    36. assert_true(found);
    37. assert_int_equal(found->key, key_values[i].key);
    38. assert_string_equal(found->value, key_values[i].value);
    39. }
    40. }
    41. void test_sort_items_by_key(void **state) {
    42. unsigned int i;
    43. KeyValue * const kv = *state;
    44. sort_items_by_key();
    45. for (i = 1; i < sizeof(key_values) / sizeof(key_values[0]); i++) {
    46. assert_true(kv[i - 1].key < kv[i].key);
    47. }
    48. }
    49. int main(int argc, char* argv[]) {
    50. const UnitTest tests[] = {
    51. unit_test_setup_teardown(test_find_item_by_value, create_key_values,
    52. destroy_key_values),
    53. unit_test_setup_teardown(test_sort_items_by_key, create_key_values,
    54. destroy_key_values),
    55. };
    56. return run_tests(tests);
    57. }

    实例

        一个很小基于命令行的计算器程序calculator.c

    与计算器程序的测试程序calculator_test.c ,它包含了本文所涉及的所有特性的一个实例。

  • 相关阅读:
    zoj3814
    cf249D
    codeforces 461C
    uva 11584
    Codeforces Round #247 (Div. 2) C D
    AOAPC I: Beginning Algorithm Contests (Rujia Liu) Volume 6. Mathematical Concepts and Methods
    AOAPC I: Beginning Algorithm Contests -- Training Guide (Rujia Liu) Chapter 3. Data Structures Fundamental Data Structures
    Codeforces Round #257 (Div. 2)
    DAY 16 PYTHON入门
    DAY 15 PYTHON入门
  • 原文地址:https://www.cnblogs.com/cfzhang/p/713629b4a113a7607f2f60526cd639b5.html
Copyright © 2011-2022 走看看