单元测试——专题

1、为什么需要单元测试?,国内开发现状是什么样子的?
以下文章转发自:https://blog.csdn.net/happylee6688/article/details/37962283
前言

最近,在网上看到过一个调查,调查的内容是“程序员在项目开发中编写单元测试的情况”。当然,至于调查的结果,我想聪明的你已经可以猜到了。高达 58.3% 的比例,一般情况下不写单元测试,只有偶尔的情况才会写写。16.6% 的程序员从来都不写单元测试。只有很少的一部分程序员才会在自己的代码中进行单元测试,并保证方法测试通过。看到这些,你想到了什么?

现状

虽然,这个调查可能会有些片面性,但这也基本反应了国内程序员的开发现状,很少有程序员能够比较认真的去编写单元测试。而且,甚至有的程序员根本就不知道为什么要写单元测试(这一点让我很郁闷)。他们经常会说,公司里不是有测试人员嘛,测试应该是他们要做的事,我们的工作只是开发(这位仁兄肯定没有学过软件工程)。当然,这些并不是偶然的,正如佛经里边说的“因果循环”,有果必有因。那么,到底是什么原因,导致程序员对单元测试这么不感冒呢?

发现

通过与几个朋友的讨论,以及网上的调查,主要有这几种原因,导致程序员对单元测试很排斥,或许说很不以为意。

不知道怎么编写单元测试
项目没有要求,所以不编写
单元测试价值不高,完全是浪费时间
业务逻辑比较简单,不值得编写单元测试
不管怎样,集成测试将会抓住所有的 bug,用不着进行单元测试
在项目的前期还是尽量去编写单元测试,但是越到项目的后期就越失控
为了完成编码任务,没有足够的时间编写单元测试。编写单元测试会导致不能按时完成编码任务,导致项目延期

很显然,这几种原因归根结底,无外乎就是不了解单元测试,自认为很聪明,自己懒不想去测试,对项目的时间、进度把控不好。下面,我将一 一进行分析,剖析出程序员的开发心理,以此来给朋友们提个醒,最终聪明反被聪明误。

剖析

不知道怎么编写单元测试

这个问题在于,还没有接触过单元测试,同时,也没有体会过企业级的代码开发。不知道同时也不了解单元测试能带给你什么。设想一下,当你开发完一个功能模块的时候,你如何确定你的模块没有 bug 呢?如果涉及到具体的业务,你会执行 debug 模式,然后一点一点的深入到代码中去查看吗?如果你一直都是这样,那么你早就已经 OUT 了。赶快去了解一下单元测试的工具吧,你会收获很大的。

项目没有要求,所以不编写

这个问题反映出了一种现象,同时也是一种习惯。项目有没有要求,只能说明项目的管理上不严格,并不是程序员不编写单元测试的理由。他们在以往的开发中,并没有养成写单元测试的好习惯。可想而知,他们的代码质量,我就不敢恭维了。给个建议,尝试着写漂亮的代码,之所以因为漂亮,是指得健康、简洁、健壮。当然,完成漂亮的代码就离不开单元测试了。

单元测试价值不高,完全是浪费时间

这种说法其实是错误的。为什么这么说呢?在日常的开发中,代码的完工其实并不等于开发的完工。如果没有单元测试,那么如何保证代码能够正常运行呢?测试人员做的只是业务上的集成测试,也就是黑盒测试,对单个的方法是没有办法测试的,而且,测试出的 bug 的范围也会很广,根本不能确定 bug 的范围,还得去花时间来确定 bug 出在什么地方。难道这就不浪费时间了吗?甚至,这样的方式,时间浪费的会更多。

业务逻辑比较简单,不值得编写单元测试

所谓的业务逻辑比较简单,其实是相对的。当你对某一块业务逻辑很熟悉的时候,你自然会认为它很简单。然而,单元测试的必要性并不是仅仅在于测试代码的功能是否正确,还在于,当其他同事在了解你的业务的时候,能够很快的通过单元测试来熟悉代码的功能,甚至不用去读代码,就能够知道它做了哪些事情。因此,写单元测试不仅是解放了自己,更方便了别人。

项目前期还在尽量写测试,到了后期就失控了

这种问题的原因在于,对项目进度、项目中的技术点研究时间、人员的沟通、业务需求的熟悉程度等没有把控好。这个问题的出现并不是个人的问题,而是反映了项目管理中存在的问题。当然,个人的原因也存在,就是如何在有限的时间里,提高效率。这一点需要大家好好思考一下了。我的建议,多做计划,根据实际情况变更计划。多和项目组长、组成员进行沟通。及时反应项目中存在的问题。

为了完成编码任务,没有足够的时间编写单元测试

这个问题在于,程序员领取的任务较为复杂,或者自己的开发效率有待提高。其实,开发任务是包括编码和单元测试的。在领任务的时候,应该跟据自身的能力,跟组长或经理沟通好,以便留出一定的测试时间。当然,提高自己的编码效率也是很有必要的。至于如何提高开发效率,网上有很多这样的文章,这里就不再赘述了。

重要性

测试常常是程序员十分厌倦的一个活动。测试能给我们带来什么?了解这些是非常重要的,测试不可能保证一个程序是完全正确的,但是测试却可以增强我们对程序完整的信心,测试可以让我们相信程序做了我么期望它做的事情。测试能够使我们尽早的发现程序的 bug 和不足。

一个 bug 被隐藏的时间越长,修复这个 bug 的代价就越大。在《快速软件开发》一书中已引用了大量的研究数据指出:最后才修改一个 bug 的代价是在 bug 产生时修改它的代价的10倍。

当然,我们主要讨论的是单元测试。单元测试是一个方法层面上的测试,也是最细粒度的测试。用于测试一个类的每一个方法都已经满足了方法的功能要求。在开发中,对于自己开发的模块,只有在通过单元测试之后,才能提交到 SVN 库 或者 Git 库。

应用

正是由于测试在开发中的重要地位,才会在IT界刮起了 TDD 的旋风。TDD,也就是测试驱动开发模式。它旨在强调在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完成全部功能的开发。

工具

说了这么多,那么都有什么工具(框架)能帮助我们完成可重复的单元测试呢?下面我就介绍几个常用的。这里只介绍 Java 语言的。其他语言的请问谷老师(Google)。

JUnit(推荐使用JUnit4)

JUnit 在日常开发中还是很常用的,而且 Java 的各种 IDE (Eclipse、MyEclipse、IntelliJ IDEA)都集成了 JUnit 的组件。当然,自己添加插件也是很方便的。JUnit 框架是 Java 语言单元测试当前的一站式解决方案。这个框架值得称赞,因为它把测试驱动的开发思想介绍给 Java 开发人员并教给他们如何有效地编写单元测试。

TestNG

TestNG,即Testing Next Generation,下一代测试技术。是根据JUnit和NUnit思想,采用 jdk 的 annotation 技术来强化测试功能并借助XML 文件强化测试组织结构而构建的测试框架。TestNG 的强大之处还在于不仅可以用来做单元测试,还可以用来做集成测试。

这里仅仅是介绍一下有哪些最常用的 Java 单元测试工具,对于如何使用这些工具,在后续的博客中会有介绍,这里就不再多说了。有兴趣的请关注后续的文章。

结束语

俗话说,一屋不扫,何以扫天下。开发中,我们自己的代码都不能保证功能的正确性,那么还有什么效率可言呢?做再多的任务,写再多的代码也只不过是在搭鸡窝,做着机器一样的重复的工作。IT界有一个原则,DRY原则 —— Don’t Repeat Yourself !只有通过对自己的工作不断的检查,不断的测试,才能不断的突破,不断的脱颖而出,当然,你才能不断的提高。

Test Day Day Up! Experience Day Day Up! And Money Day Day Up Too!

哎呀妈呀,中国式英语又出来了!


作者:紫竹风
来源:CSDN
原文:https://blog.csdn.net/happylee6688/article/details/37962283
版权声明:本文为博主原创文章,转载请附上博文链接!

2、怎么实现单元测试?
C开发中的单元测试,原文转发自:https://www.cnblogs.com/linux-sir/archive/2012/08/25/2654557.html

最近在写C代码的过程中,感觉自己在重复一项必不可少的环节,就是自测代码,这使我想起以前在写JAVA时的Junit带来的快捷和方便,于是马上行动,经百度、谷歌几轮后,发现Cunit工具,看名字,就可猜到它与Junit同属一宗。网上的相关内容也都非常雷同,这里不再详述,有兴趣的话,可以直奔官方文档:http://cunit.sourceforge.net/doc/index.html

经过仔细观赏,要借用Cunit来提高自己的编码效率和质量,有必要先搞清它的几项要点:
首先仔细观察下图:

可以看出Cunit也是有组织的,呵呵,主要分几个角色,Registry,Suite及Test方法。可以通过下面例子,体会到这种组织关系。
按官方文档说明,使用Cunit的主要步骤有:

  1. Write functions for tests (and suite init/cleanup if necessary).
  2. Initialize the test registry - CU_initialize_registry()
  3. Add suites to the test registry - CU_add_suite()
  4. Add tests to the suites - CU_add_test()
  5. Run tests using an appropriate interface, e.g. CU_console_run_tests
  6. Cleanup the test registry - CU_cleanup_registry

本人英文不咋地,就不献丑翻译了,直接用英文理解吧(要努力用英文).
下面我们结合一个小实例,来体验一下Cunit的便利吧(学编程,最有效的方式还是自己动手)
先编写一个具体两个简单功能的函数,然后写Testcase来测试它。
文件主要有:

  1. strformat.h :字符串功能函数的接口文件
    2)strformat.c :字符串功能函数的实现
    3)testcase.c : 测试用例及Cunit运行环境
    4)makefile :
    下面直奔代码:
    代码:strformat.h
    复制代码

1 /* strformat.h —
2 *
3 * Filename: strformat.h
4 * Description: 字符串操作头文件
5 * Author: magc
6 * Maintainer:
7 * Created: 一 8月 20 22:57:19 2012 (+0800)
8 * Version:
9 * Last-Updated: 六 8月 25 10:31:30 2012 (+0800)
10 * By: magc
11 * Update #: 15
12 * URL:
13 * Keywords:
14 * Compatibility:
15 *
16 /
17
18 /
Commentary:
19 * 为的是体验Cunit而临时写的几项功能函数,没有多大实际意义,仅仅是为了写测试类
20 *
21 *
22 /
23
24 /
Change Log:
25 *
26 *
27 /
28
29 /
Code: /
30
31 #ifndef _strformat_h
32 #define _strformat_h
33
34 typedef char * string;
35
36 /
************************************************************************
37 *功能描述:返回字符串的长度
38 *参数列表:
39 *返回类型:
40 */
41 int string_lenth(string word);
42 /

43 *功能描述:返回字符串的大写形式
44 *参数列表:
45 *返回类型:
46 */
47 string to_Upper(string word);
48 /

49 *功能描述:连接字符串
50 *参数列表:
51 *返回类型:
52 *************************************************************************/
53 string add_str(string word1 ,string word2);
54
55
56
57 #endif
58
59
60 /
strformat.h ends here */

复制代码

代码:strformat.c
复制代码

1 /* strformat.c —
2 *
3 * Filename: strformat.c
4 * Description: 字符串操作
5 * Author: magc
6 * Maintainer:
7 * Created: 一 8月 20 22:56:36 2012 (+0800)
8 * Version:
9 * Last-Updated: 六 8月 25 10:33:07 2012 (+0800)
10 * By: magc
11 * Update #: 33
12 * URL:
13 * Keywords:
14 * Compatibility:
15 *
16 /
17
18 /
Commentary:
19 * 此代码仅为体验Cunit而临时撰写。
20 *
21 *
22 /
23
24 /
Change Log:
25 *
26 *
27 /
28
29 /
Code: /
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <stdarg.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include “strformat.h”
39
40
41 /
*************************************************************************
42 函数名称:字符串相加
43 功能描述:
44 输入参数:
45 返 回:
46 /
47 string add_str(string word1 ,string word2){
48 return (strcat(word1, word2));
49 }
50
51 /

52 函数名称:将字符串转换成大写格式
53 功能描述:
54 输入参数:
55 返 回:
56 /
57 string to_Upper(string word){
58 int i;
59 for(i = 0;word[i] !=’\0’ ;i++){
60 if(word[i]<‘z’ && word[i]>‘a’){
61 word[i] -= 32;
62 }
63 }
64 return word;
65
66 }
67
68 /

69 函数名称:字符串长度
70 功能描述:
71 输入参数:
72 返 回:
73 *************************************************************************/
74 int string_lenth(string word){
75 int i;
76 for(i = 0 ;word[i] != ‘\0’;i++){
77
78 }
79 return i;
80 }
81
82 /
strformat.c ends here */

复制代码

测试代码: testcase.c
复制代码

1 /* testcase.c —
2 *
3 * Filename: testcase.c
4 * Description: 测试实例
5 * Author: magc
6 * Maintainer:
7 * Created: 一 8月 20 23:08:53 2012 (+0800)
8 * Version:
9 * Last-Updated: 五 8月 24 16:09:40 2012 (+0800)
10 * By: magc
11 * Update #: 135
12 * URL:
13 * Keywords:
14 * Compatibility:
15 *
16 /
17
18 /
Commentary:
19 * 当前文件用来定义测试方法,suite,及registry信息,若测试方法有变化,只需要修改当前文件即可。
20 * 第一步:书写测试函数的代码,建议以"test_"为前缀。
21 * 第二步:将测试方法归类,即将相似功能的测试方法放到一个数组里,以便把它们指定给一个suite
22 * 第三步:创建suite,可按功能或模块,生成多个test suite,
23 * 第四步:书写测试方法的总调用方法,AddTests(),用来统一启动测试方法。
24 /
25
26 /
Change Log:
27 *
28 *
29 /
30
31 /
Code: /
32 #include <assert.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <string.h>
37 #include <stdarg.h>
38 #include <stdlib.h>
39 #include <stdio.h>
40
41 #include <CUnit/Basic.h>
42 #include <CUnit/Console.h>
43 #include <CUnit/CUnit.h>
44 #include <CUnit/TestDB.h>
45 #include “strformat.h”
46
47 /
*************************************************************************
48 函数名称:测试string_lenth()方法
49 功能描述:
50 输入参数:
51 返 回:
52 /
53 void test_string_lenth(void){
54 string test = “Hello”;
55 int len = string_lenth(test);
56 CU_ASSERT_EQUAL(len,5);
57 }
58
59 /

60 函数名称:测试方法to_Upper()
61 功能描述:
62 输入参数:
63 返 回:
64 /
65
66 void test_to_Upper(void){
67 char test[] = “Hello”;
68 CU_ASSERT_STRING_EQUAL(to_Upper(test),“HELLO”);
69
70 }
71
72 /

73 函数名称:测试方法 add_str()
74 功能描述:
75 输入参数:
76 返 回:
77 /
78 void test_add_str(void){
79 char test1[] = “Hello!”;
80 char test2[] = “MGC”;
81 CU_ASSERT_STRING_EQUAL(add_str(test1,test2),“Hello!MGC”);
82
83 }
84
85 /

86 数组名称:将多个测试方法打包成组,以供指定给一个Suite
87 功能描述:每个suite可以有一个或多个测试方法,以CU_TestInfo数组形式指定
88 /
89 // CU_TestInfo是Cunit内置的一个结构体,它包括测试方法及描述信息
90 CU_TestInfo testcase[] = {
91 {“test_for_lenth:”,test_string_lenth },
92 {“test_for_add:”,test_add_str },
93 CU_TEST_INFO_NULL
94 };
95
96 CU_TestInfo testcase2[] = {
97 {“test for Upper :”,test_to_Upper },
98 CU_TEST_INFO_NULL
99 };
100
101 /

102 函数名称:suite初始化过程
103 功能描述:
104 输入参数:
105 返 回:
106 /
107 int suite_success_init(void){
108 return 0;
109
110 }
111
112 /

113 函数名称:suite清理过程,以便恢复原状,使结果不影响到下次运行
114 功能描述:
115 输入参数:
116 返 回:
117 /
118 int suite_success_clean(void){
119 return 0;
120 }
121
122 //定义suite数组,包括多个suite,每个suite又会包括若干个测试方法。
123 CU_SuiteInfo suites[] = {
124 {“testSuite1”,suite_success_init,suite_success_clean,testcase},
125 {“testsuite2”,suite_success_init,suite_success_clean,testcase2},
126 CU_SUITE_INFO_NULL
127 };
128
129 /

130 函数名称:测试类的调用总接口
131 功能描述:
132 输入参数:
133 返 回:
134 /
135 void AddTests(){
136 assert(NULL != CU_get_registry());
137 assert(!CU_is_test_running());
138
139 if(CUE_SUCCESS != CU_register_suites(suites)){
140 exit(EXIT_FAILURE);
141
142 }
143 }
144 /

145 功能描述:运行测试入口
146 参数列表:
147 返回类型:
148 /
149
150 int RunTest(){
151 if(CU_initialize_registry()){
152 fprintf(stderr, " Initialization of Test Registry failed. ");
153 exit(EXIT_FAILURE);
154 }else{
155 AddTests();
156 /
Automated Mode *****************
157 CU_set_output_filename(“TestMax”);
158 CU_list_tests_to_file();
159 CU_automated_run_tests();
160 //
/
161
162 /
Basice Mode *******************
163 CU_basic_set_mode(CU_BRM_VERBOSE);
164 CU_basic_run_tests();
165 //
/
166
167 /Console Mode ********************
168 CU_console_run_tests();
169 //
/
170
171 CU_cleanup_registry();
172
173 return CU_get_error();
174
175 }
176
177 }
178 /
***************************************
179 *功能描述:测试类主方法
180 *参数列表:
181 *返回类型:
182 *************************************************************************/
183
184 int main(int argc, char * argv[])
185 {
186 return RunTest();
187
188 }
189
190
191
192
193
194 /
testcase.c ends here */

复制代码

注:
1)注意结合上面Cunit的组织结构图,理解Cunit中几个角色的关系(CU_TestInfo,CU_SuiteInfo各以数组的形式,将多个Test和Suite组织起来)。
2)Cunit有几种运行模式,如automated,basic,console,有的是可以交互的,有的是没有交互,直接出结果的。

代码:makefile

IINC=-I/usr/local/include/CUnit
LIB=-L/usr/local/lib/

all: strformat.c testcase.c
gcc -o test $(INC) $(LIB) $^ -lcunit -static

注:
1)Cunit安装很简单,从官方地址上下载源代码后,在本机依次执行
./configure
make
sudo make install
安装成功后相关的库及头文件安装到默认路径下。编译时添加选项:
-I/usr/local/include/CUnit
-L/usr/local/lib/
就如makefile中的一样。

下面我们欣赏一下Cunit的常见几种运行模式
1)Automated Mode
先将testcase.c中156~159代码放开注释,此时便是以automated模式运行,此模块没有交互能力,直接生成XML格式的报表,先make,然后运行后,在当前目录下生成两个报表
TestMax-Listing.xml和TestMax-Results.xml(前者是测试用例的列表,后者是测试用例的测试结果) ,但这两个文件是不能直接观看的,要查看这两个文件,需要使用如下xsl和dtd文件:CUnit-List.dtd和CUnit-List.xsl用于解析列表文件, CUnit-Run.dtd和CUnit-Run.xsl用于解析结果文件。这四个文件在CUnit包里面有提供,安装之后在$(PREFIX) /share/CUnit目录下,默认安装的话在/home/lirui/local/share/CUnit目录下。在查看结果之前,需要把这六 个文件:TestMax-Listing.xml, TestMax-Results.xml, CUnit-List.dtd, CUnit-List.xsl, CUnit-Run.dtd, CUnit-Run.xsl拷贝到一个目录下,然后用浏览器打开两个结果的xml文件就可以了。
如下图所示:

2) Console Mode
在testcase.c中将其它模式代码注释掉,放开168行代码,便转换成Console模式了。console模式支持交互,如支持运行,查看错误等操作,如下图所示:

从上图即可看出,输入R来运行,输入F来查看错误用例,输入Q来退出

这种模式在实际中是很实用的。

3)Basic Mode
在testcase.c中将其它模式的代码注释掉,放到163~164行代码,便转换成Basic模式,这种是不支持交互的,直接打印出运行结果。

可以看出,这种模式也是比较简单快捷的

另外对于这种写测试代码的重复工作,可以想办法减小重复,还好,我用的是Emacs写代码,可以借助强大的msf-abbrev 来定义一个testcase的模板,每次写测试时,可以直接引入,既简单又快捷,使自己将有限的精力集中到更核心的部分。(假如你不知我在说什么,就当没看到这部分,直接闪过)

小结:
以后写代码过程中,若需要测试函数的功能,就可以采用如下步骤:
1)创建一个专门的测试类,用msf-abbrev模板快捷创建内容,
2) 添加测试函数
3) 将测试函数按组放到CU_TestInfo数组中,并指定给一个Suite
4)根据自己需要,定义CU_SuiteInfo数组。
5)在RunTest()中定义运行模式。
6)在main函数中调用RunTest(),默认生成的testcase中有一个main函数,若不与其它冲突,直接在这里调用即可。(若main冲突,则砍掉不需要的那个)

代码附件:此处缺少文件,参见原文链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值