0. 前言

文章是翻译自Check官网的Tutorial,由于个人水平有限,所以翻译难免会有错误之处,烦请各位朋友指正。

还有几点要提前说明:

  1. 本文是使用教程,不包含安装教程,如果还没有安装,请移步Check安装教程(还没写)
  2. 使用本教程测试结果会输出到一个文件中(下面会介绍文件在哪儿),而终端的输出内容很少
  3. 本文中多次出现了“单元测试“这个概念,有的时候是指宏观上的单元测试,即我们要做的事;但有的时候是特指第一节中介绍到的那个代码模块,读者阅读的时候还请多多留意。

以下是正文。

本文和Unit Test Infected这篇文章类似,将建立一个libmoney库来表示货币,这个库可以转换不同类型的货币。

1. 如何写一个测试

用Check框架来写单元测试很容易。如果你要在某个文件里引用check,直接包含check.h文件即可,如下所示:

#include <check.h>

下面是一个单元测试的模块,里面有若干测试:

START_TEST (test_name)
{/* unit test code */
}
END_TEST

START_TEST/END_TEST这对标签实际上是用于设置允许测试的基础结构的宏(【译者注】也就是说在这对宏之间的代码允许被测试)。这对宏必须成对出现,如果缺少END_TEST会导致在编译的时候出现一大堆错误。

2. 使用CMake

【译者注】实际上前面还有一种使用Autotools的方法可供选择,但因为我对这个工具不是十分的熟悉,所以选择了CMake。

因为我们要创建一个处理money的库(【译者注:】后面会用libmoney来表示该库),所以要先在头文件money.h中创建接口,然后在money.c中实现,并创建一个文件check_money.c来存放单元测试的代码。为了能够把这些核心代码整合到build系统中,还需要其他的工具。在本例中,我们将使用CMake来负责这些。注意,您可以对Makefiles或者其他的build系统进行类似的操作。在作者看来,用CMake比直接用Makefiles要方便一些,因为CMake对运行测试提供了一些内置的支持。

注意,本文将不会多介绍CMake如何工作。如果你想要对CMake的工作原理多了解一些,请移步CMake project’s homepage。

这部分介绍的例子实际上是Check源码的一部分,你也不用花时间去剪切,黏贴甚至重新写一遍。找到Check源码的根目录,你会看到example文件夹。GNU/Linux 系统下的默认路径应该是(如果你已经安装好了Check)/usr/share/doc/check/example。这个路径下还有最新版本的教程。但是如果你想跟着教程一步步的走,可以把原来的三个文件删掉,然后新建money.hmoney.ccheck_money.c三个空白文件。

文件结构应该如下所示(【译者注:文件夹下应该还有其他的文件,那些是Autotool的配置文件,不会影响CMake的编译,可以不用在意】):

.
|-- Makefile.am
|-- README
|-- CMakeLists.txt
|-- cmake
|   |-- config.h.in
|   |-- FindCheck.cmake
|-- src
|   |-- CMakeLists.txt
|   |-- main.c
|   |-- money.c
|   `-- money.h
`-- tests|-- CMakeLists.txt`-- check_money.c

最靠上的CMakeLists.txt包含了对库和类型的配置检查,同时定义了要处理的子文件夹。cmake/FindCheck.camke文件包含了对Check在系统中定位的指令,并设置buid来使用Check。如果系统没有安装pkg-config,cmake/FindCheck.camke将不能成功地定位到Check。这种情况下,Check的安装位置必须要手动确认,将下面这一行添加到test/CMakeLists.txt(假设Check被安装到了C:\\Program Files\\check):

set(CHECK_INSTALL_DIR "C:/Program Files/check")

src/Makefile.am建立一个libmoney作为Libtool的存档文件,并将其链接到一个被简单命名为main的应用。应用的行为不是本教程的重点,重点是任何一个我们想要进行单元测试的函数不能写在main.c中,其实也就是说,只有main()函数本身可以写在main.c中。如果你是测试整个应用,单元测试显然有点不太合适:你应该使用系统测试工具,比如Autotest。如果你想用Check来测试main(),可以将其重命名为类似_myproject_main()一类的,然后围绕着它来写测试。

基础已经全部搞定,我们可以搞开发了。src/money.h现在仅包含标准的C头文件模板:

/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/#ifndef MONEY_H
#define MONEY_H#endif /* MONEY_H */

src/money.c文件是空的,并且tests/check_money.c仅包含一个空的main()函数:

/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/int main(void)
{return 0;
}

在Unix系统中使用如下命令为项目创建一个Build系统来buildmainlibmoney.la:(【译者注】这里的意思是项目的根目录下执行)

$ cmake .
$ make

build完成后运行check_money测试:

$ make test

你会发现根本就没有错误输出,因为目前为止我们还没有编写任何的测试代码。

【译者注】这里我想要说明一下,用作者的这种编译方式是不会影响结果的,但是编译的输出文件会与源文件混在一起,我想这种事情很多码农都会有点受不了,我就是这种,所以我新建了一个build文件夹,专门用来存放这些输出文件。当然如果你不介意这种事,大可不必像我这样做。

  1. 创建并进入到build文件夹下:

    $ mkdir build
    $ cd build
  2. 用cmake命令,因为是在build文件夹下,所以要处理的CMakeList.txt在上一级文件夹下:

    $ cmake ..
  3. 编译,同上

    $ make
    $ make test
    

3. 小增量测试

Test Infected文章开始于一个Money类,我们也从这个类开始。当然,C里面没有类,但是我们也不是真的需要类。Test Infected提到的一种编程方法是我们应该在实现代码之前就写好单元测试,就这方面而言,我们将会比Test Infected的作者们更加严谨(显然这些作者并没有真的做到这些)

下面是修改后的check_money.c文件。

--- tests/check_money.1.c    2017-10-19 20:28:18.000000000 -0400
+++ tests/check_money.2.c    2017-10-19 20:28:18.000000000 -0400
@@ -1,24 +1,38 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/+#include <check.h>
+#include "../src/money.h"
+
+START_TEST(test_money_create)
+{
+    Money *m;
+
+    m = money_create(5, "USD");
+    ck_assert_int_eq(money_amount(m), 5);
+    ck_assert_str_eq(money_currency(m), "USD");
+    money_free(m);
+}
+END_TEST
+int main(void){return 0;}

单元测试应该能够正常运行,正常结束。如果其意外退出,或者发出信号,它就会失败,并显示一个通用的错误信息。(注意:可以想象,你是想找到一个意外退出或者一个信号的,并且在Check中有相应的断言函数来确定我们应该想检查信号还是检查意外退出)。如果我们想获得更多关于失败的信息,我们需要使用一些函数来指出代码的失败。下面介绍两个函数:ck_assert_int_eq(用于确定两个整数是否相等)和ck_assert_str_eq(用于确定两个空终止字符串是否相等)。如果给定的参数不相等,两个函数(实际上是宏)都会发出一个错误的信号。

另一种使用ck_assert_int_eqck_assert_str_eq的方法是,直接在ck_assert中写判断的表达式。这个函数仅接受一个布尔型参数,而且参数必须是TRUE才能通过测试。第二个断言测试可以像下面这样写:

ck_assert (strcmp (money_currency (m), "USD") == 0);

ck_assert可以发现并报告错误,但并不会在单元测试结果中打印出任何用户提供的信息。想要连同发现的错误一起打印用户定义的信息,可以用ck_assert_msg。第一个参数是一个布尔型参数,剩下的参数支持varargs(可变长参数),并且接受printf风格的格式化字符串参数,这个在函数调试的时候相当有用。举个例子,第二个断言测试还可以这么写:

ck_assert_msg (strcmp (money_currency (m), "USD") == 0,"Was expecting a currency of USD, but found %s", money_currency(m));

如果觉得使用ck_assert()的时候布尔型参数太过复杂,还可以使用另一个函数ck_abort()ck_abort_msg(),他俩会无条件报错。第二个断言测试可以重写为如下形式:

if (strcmp (money_currency (m), "USD") != 0)
{ck_abort_msg ("Currency not set correctly on creation");
}

为了用户使用方便,ck_assert不接受任何的用户提供的消息,而是用更合适的信息来代替。(其实相当于向ck_assert_msg传了一个空的信息)。所以你可以按以下形式来编写测试:

ck_assert (money_amount (m) == 5);

相当于:

ck_assert_msg (money_amout (m) == 5, NULL);

如果money_amout (m) != 5,那么上述测试语句将会打印发生错误的文件,行号,以及信息“Assertion money_amount (m) == 5”。

现在可以用make test来编译运行测试套件,我们将得到一大堆的编译错误。当然像这样故意写不能编译的测试代码来运行看起来有点奇怪,但是要记得我们现在在做什么:在创建单元测试的同时,换一种角度来说,我们其实也就定义了money接口的需求。编译出现了错误在某种程度上来说,实际上实在告诉我们对于接口的实现不符合刚刚定义的需求(【其实就没有实现】)。如果我们所做的仅仅是修改源代码以便单元测试编译通过,实际上我们是在单元测试的指导之下取得了进展,这就是我们要做的。

【译者注】前面这一段实际上应该是对应本节开始时提到的:在编程之前写好单元测试。我们现在别说接口的实现,其实连测试代码里用到的接口都没有定义,所以运行这个测试文件肯定会报一大堆错误。但是正如作者所言,我们是要遵循这么一个原则,在编程之前写好单元测试。这样可以让我们对函数的需求有个明确的想法,而不断修改源代码以让单元测试的编译成功,实际上也正好就是让我们的源代码逐步完善的过程。

修改后的money.h文件:

--- src/money.1.h    2017-10-19 20:28:18.000000000 -0400
+++ src/money.2.h    2017-10-19 20:28:18.000000000 -0400
@@ -1,24 +1,31 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/#ifndef MONEY_H#define MONEY_H+typedef struct Money Money;
+
+Money *money_create(int amount, char *currency);
+int money_amount(Money * m);
+char *money_currency(Money * m);
+void money_free(Money * m);
+#endif /* MONEY_H */

现在去编译我们的代码,会再次通过测试。然而,一旦我们想要在check_money文件的main()函数中使用libmoney中的函数,就会遇到问题,因为我们还没有真的实现这些函数。

【译者注】我在做上面这部分的时候,使用make编译时是报错了的,报测试代码中使用的几个函数都没有定义,但是并没有影响到后续的开发,所以读者在这里如果跟作者不同,我认为大可不必在意。

4. 创建一个套件(suite)

为了能够用Check来运行单元测试,我们必须创建一些测试用例,将他们全部放在测试套件中,然后用套件运行器运行他们。这样相较于直接运行测试用例是要多花点功夫,但是这是一次性的。下面是一个新版本的check_money.c文件。注意我们还特意导入了stdlib.h文件以便使用系统定义的EXIT_SUCCESSEXIT_FILURE

--- tests/check_money.2.c    2017-10-19 20:28:18.000000000 -0400
+++ tests/check_money.3.c    2017-10-19 20:28:18.000000000 -0400
@@ -1,38 +1,65 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/+#include <stdlib.h>#include <check.h>#include "../src/money.h"START_TEST(test_money_create){Money *m;m = money_create(5, "USD");ck_assert_int_eq(money_amount(m), 5);ck_assert_str_eq(money_currency(m), "USD");money_free(m);}END_TEST+Suite * money_suite(void)
+{
+    Suite *s;
+    TCase *tc_core;
+
+    s = suite_create("Money"); /* 创建名为Money的套件 */
+
+    /* Core test case */
+    tc_core = tcase_create("Core");    /* 创建名为Core的测试用例 */
+
+    tcase_add_test(tc_core, test_money_create);   /* 将定义的单元测试加进来 */
+    suite_add_tcase(s, tc_core);  /* 将测试用例集合加入到套件中 */
+
+    return s;
+}
+int main(void){
-    return 0;
+    int number_failed;
+    Suite *s;
+    SRunner *sr;
+
+    s = money_suite();
+    sr = srunner_create(s);  /* 创建套件运行器 */
+
+    srunner_run_all(sr, CK_NORMAL);   /* 运行套件 */
+    number_failed = srunner_ntests_failed(sr);   /* 获得错误数量 */
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;}

money_suite()中的代码很好理解:我们创建了一个套件,一个测试用例,并将测试用例添加到套件中,然后把上面创建的单元测试添加到测试用例中。但是为什么要把这个过程写成独立的函数,而不是直接写到main()函数中呢?因为当有新的测试要添加到money_suite()中时,我们就不需要对main()做任何的改变,可以使main保持整洁、简单。

单元测试是定义为内部静态函数的。也就是说,向测试用例添加单元测试的代码必须与单元测试位于同一个编译单元。这又为将创建测试套件放在单独的函数中提供了一个理由:后面你可能希望一个套件对应一个源文件;并且定义一个头文件,在文件中定义一个拥有唯一命名的套件创建函数,为所有套件创建函数提供原型,并封装了这些函数后面定义单元测试的位置和方式的详细信息。有关这种方法的的示例,请参见为Check本身定义的测试程序。

main()函数源码需要解释一下。我们将根据在money_suite()函数中创建的Suite创建一个类型为SRunner的套件运行器对象。接着我们运行suite,并使用CK_NORMAL标志来指定我们需要打印运行的结果的摘要,以及所有可能发生的错误的列表。再下面获得运行时发生的错误的数量,并根据这个来决定如何返回。Automake创建的check目标会根据返回值来决定测试时通过了还是失败了。

由于现在测试实际上是由check_money来运行,使用make test我们会再次遇到链接错误。你可以自己尝试一下。原因是money.h里的接口还没有在money.c中实现。让我们用尽可能快的方案来实现money.c中的函数(【译者注】这里的实现是故意写成错误的,这样在运行单元测试时就会报错)。

--- src/money.1.c    2017-10-19 20:28:18.000000000 -0400
+++ src/money.3.c    2017-10-19 20:28:18.000000000 -0400
@@ -1,20 +1,42 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/+#include <stdlib.h>
+#include "money.h"
+
+Money *money_create(int amount, char *currency)
+{
+    return NULL;
+}
+
+int money_amount(Money * m)
+{
+    return 0;
+}
+
+char *money_currency(Money * m)
+{
+    return NULL;
+}
+
+void money_free(Money * m)
+{
+    return;
+}

注意我们使用#include <stdlib.h>语句获得了NULL的定义。现在,当我们运行make test时,代码编译并链接,但是我们的单元测试还是失败了。尽管如此,这依旧是一个进步,我们可以专注于如何让测试代码编译通过。

【译者注】这里终端的输出仅仅会告诉你单元测试有没有通过,错在哪个文件,但具体的信息(比如错在哪一行,什么类型的错误)其实输出到了一个日志文件中,在Testing\\Tempoary\\LastTest.log文件中可以看到。

5. SRunner输出

SRunner中运行测试的函数有以下两种:

void srunner_run_all (SRunner *sr, enum print_output print_mode);void srunner_run (SRunner * sr, const char *sname, consta char *cname, enum print_output print_mode);

这俩函数做两件事:

  1. 它们会运行存储在其中套件的所有测试用例,并收集所有的测试结果。测试用例和套件的选择取决于使用的特定函数。

    如果环境变量CK_RUN_CASE或者CK_RUN_SUITE被定义了,那么srunner_run_all将会为所有定义的期望的套件运行所有的定义的测试用例。如果定义了,这些变量将会包含测试套件或者测试用例的名称,以这种方式来选择套件或测试用例。

    srunner_run根据snametname参数来运行套件或者用例。这些参数中某个赋值为NULL意味着“任意套件或用例”。

  2. 它们根据print_mode指定的形式来输出结果。

对于已经运行的SRunner,同样有一个独立的输出函数:

void srunner_print (SRunner *sr, enum print_output print_mode);

Check中定义的print_out的参数print_mode可以有以下几种选择:

CK_SILENT

​ 不输出任何信息。如果你使用这个标志,你同样需要以编程的方式检查SRunner对象,单独输出,或者使用测试日志。

CK_MINIMAL

​ 只输出测试的摘要(运行数量,通过数量,失败数量,错误)。

CK_NORMAL

​ 输出测试的摘要,并且每个失败的测试都会输出信息。

CK_VERBOSE

​ 输出测试摘要,以及每个测试的信息(通过或者失败)

CK_ENV

​ 从环境变量CK_VERBOSITY值中获取输出模式,这个变量的值可以设置为“silent”,“minimal”,“normal”,“verbose”。如果变量没找到,或者值无法识别,那么输出模式将被设置为CK_NORMAL

CK_SUBUNIT

​ 按照子单元测试运行者协议来输出运行过程。

main()中指定CK_NORMAL标志,然后运行make test。输出的单元测试应该如下所示:

Running suite(s): Money
0%: Checks: 1, Failures: 1, Errors: 0
check_money.c:9:F:Core:test_money_create:0: Assertion 'money_amount (m)==5' failed:
money_amount (m)==0, 5==5
FAIL: check_money
=====================================================
1 of 1 test failed
Please report to check-devel AT lists.sourceforge.net
=====================================================

注意在Automake 1.13之前的版本,运行make test的测试的输出就是是单元测试程序的输出。但从1.13版本的Automake,将会并行运行所有的单元测试然后将输出存储到日志文件中。上述的输出内容可以在日志文件中找到。

摘要中的第一行告诉我们0%的测试通过,剩下的告诉我们共计一个测试,并且在这些测试中,1个失败,0个错误。第二行告诉我们失败发生的精确位置,以及这是什么类型的错误(P是指通过,F表示失败,E表示错误)。

在此之后我们可以看到由Automake所产生的的更高一级的输出:check_money文件运行失败了,并且在configure.ac中给定的bug-report的所在文件也被打印出来了。

接着我们来实现money_amout函数,这样的话,它就可以通过测试了。首先我们需要创建一个Money的结构体,然后实现函数来返回正确的数量:

--- src/money.3.c    2017-10-19 20:28:18.000000000 -0400
+++ src/money.4.c    2017-10-19 20:28:18.000000000 -0400
@@ -1,42 +1,47 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/#include <stdlib.h>#include "money.h"+struct Money
+{
+    int amount;
+};
+Money *money_create(int amount, char *currency){return NULL;}int money_amount(Money * m){
-    return 0;
+    return m->amount;}char *money_currency(Money * m){return NULL;}void money_free(Money * m){return;}

我们来重新运行make test,然后…咋回事??输出的结果如下所示:

Running suite(s): Money
0%: Checks: 1, Failures: 0, Errors: 1
check_money.c:5:E:Core:test_money_create:0: (after this point)
Received signal 11 (Segmentation fault)

这些输出什么意思呢?注意现在我们是有个错误,而不是失败。这就意味着我们的单元测试意外退出了,或者发出了信号。后面的信息有提到“after this point”,这个意思是,错误出现在这个点(check_money.c, line 5)之后,错误发出的信号是signal 11(即,分段错误)。运行达到的最后的断点一般设置在单元测试入口处,以及每次调用ck_assert()ck_abort()ck_assert_int_*()ck_assert_str_*(),或者特殊的函数mark_point()。举个例子,如果我们写上这么一些测试代码:

stuff_that_worts ();
mark_point ();
stuff_that_dies ();

那么断点就会被标记在mark_point()

我们的测试失败得这么惨是因为我们还没有实现money_create()来创建Money。接下来我们就实现它,以及相应的money_free(),还有money_currency(),为了使我们的单元测试通过,要写成下面这样:

--- src/money.4.c    2017-10-19 20:28:18.000000000 -0400
+++ src/money.5.c    2017-10-19 20:28:18.000000000 -0400
@@ -1,47 +1,58 @@/** Check: a unit test framework for C* Copyright (C) 2001, 2002 Arien Malec** This library is free software; you can redistribute it and/or* modify it under the terms of the GNU Lesser General Public* License as published by the Free Software Foundation; either* version 2.1 of the License, or (at your option) any later version.** This library is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU* Lesser General Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with this library; if not, write to the* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,* MA 02110-1301, USA.*/#include <stdlib.h>#include "money.h"struct Money{int amount;
+    char *currency;};Money *money_create(int amount, char *currency){
-    return NULL;
+    Money *m = malloc(sizeof(Money));
+
+    if (m == NULL)
+    {
+        return NULL;
+    }
+
+    m->amount = amount;
+    m->currency = currency;
+    return m;}int money_amount(Money * m){return m->amount;}char *money_currency(Money * m){
-    return NULL;
+    return m->currency;}void money_free(Money * m){
+    free(m);return;}

最终,我们可以得到的输出为:

Test project /mnt/d/Projects/TEST/check/money/buildStart 1: check_money
1/1 Test #1: check_money ......................   Passed    0.01 sec100% tests passed, 0 tests failed out of 1Total Test time (real) =   0.01 sec

Check官方使用教程——翻译相关推荐

  1. db4o官方入门教程翻译--06.集合和数组

    .Net方向的关于使用db4o的资料很少,硬着头皮看官方的英文版入门教程.由于英文不太好比较费劲,感觉一定有朋友会遇到相同的问题.所以想将入门教程做一个简单的翻译,水平有限,不太明白的地方可以对照英文 ...

  2. 【Houdini官方入门教程翻译】地形生成(更新ing)

    文章目录 0.概述 1.使用Heightfields来塑造地形 01 选择Terrain布局,创建一个高度场 02 使用径向菜单,给高度场添加噪声并模糊边缘 03 添加高度场扭曲 04 添加网络框使得 ...

  3. 【Houdini官方入门教程翻译】概述——UV和纹理贴图

    文章目录 概述 UV视图介绍 UV Quickshade节点 UV Project节点 UV Flatten节点 UV Edit节点 UV Layout节点 UDIMS UV 属性(Attribute ...

  4. 【Houdini官方入门教程翻译】概述——文件管理

    文章目录 概述 项目目录 场景文件 | .HIP 数字资产 | .HDA Houdini Apprentice 和 Houdini Indie 的文件 备份 File SOP节点 文件依赖路径 [$H ...

  5. 【Houdini官方入门教程翻译】概述——建模工具

    文章目录 几何体类型 创建几何体 多边形(Polygon)建模 通用工具节点 细分曲面(Subdivision Surfaces)建模 加面(Surfacing)工具 布尔运算 变形(Deform)工 ...

  6. Dapper官方教程翻译8:Dapper方法之QueryMultiple(转)

    Dapper官方教程翻译8:Dapper方法之QueryMultiple 2019年02月28日 10:42:22 Day_and_Night_2017 阅读数:120 QueryMultiple方法 ...

  7. Unity3D Shader官方教程翻译(三)

    Unity3D Shader官方教程翻译(三) 1.Shader语法:Pass 1个Pass块可以使一个几何物体被一次渲染. Pass { [Name and Tags] [RenderSetup] ...

  8. OpenCV-Python (官方)中文教程(部分三)

    [部分二]:https://blog.csdn.net/Thomson617/article/details/103961274 第七章.相机标定与3D重构 42.摄像头标定 在图像测量过程以及机器视 ...

  9. hibernate官方新手教程 (转载)

    hibernate官方新手教程第一部分 - 第一个Hibernate程序 首先我们将创建一个简单的控制台(console-based)Hibernate程序.我们使用内置数据库(in-memory d ...

最新文章

  1. MySQL半同步安装以及参数
  2. GIT使用总结(二)
  3. SmartRules让MindManager的交互图变得更加智能
  4. PHP学习笔记4:字符串与正则
  5. java如何爬取304_HTTP 304错误的详细讲解
  6. VeeValidate 的使用场景以及配置
  7. 双亲委派机制_面试官:双亲委派机制的原理和作用是什么?
  8. 使用 vue-i18n 切换中英文
  9. springframework报错_应对报错信息的必杀技!
  10. [NOIP2015] 提高组 洛谷P2661 信息传递
  11. linux的常用的软件,Linux常用的软件和命令
  12. uva11922(强行用rope替代spaly)
  13. python利器-python利器去广告版
  14. three 查看版本号
  15. .NET单元测试(五):多线程
  16. OC 获取view相对位置_【黑苹果系列】小白教程之DSD补丁篇 | 7分钟教你优雅定制最关键的OC补丁(clover通用)...
  17. 前端和后端哪个工资更高呢?
  18. 性价比超高的51单片机学习板与开发板
  19. 创业之前你需要了解的九个真相
  20. 网页动态连线背景-蜘蛛网

热门文章

  1. ARM开发(4)基于STM32的矩阵键盘按键控制TM1629A LED显示
  2. pytorch官方教程中文版(一)PyTorch介绍
  3. 细数社交巨无霸Facebook与美国媒体的“爱恨情仇”
  4. HTML + CSS 学习
  5. php模板引擎 smarty笔记
  6. 谷歌安卓以AAB替换APK安装包
  7. 闲谈swi与ucos-终结
  8. ps制作食品网页总结
  9. Windows 使用QT读取IP地址并修改IP地址
  10. ZOJ 3898 Stean 矩形法求积分