汪年第一贴:单元测试

为啥是单元测试。

最近手上的东西已经都定了第一个版本了,最近几周一直在做测试的工作。我闲暇时间,放弃了其他的事情,专心在为我的项目做单元测试。以前都是断断续续的弄了点单元测试,这次就结合自己的实际项目,再次实施了一次单元测试。并且有了很深刻的体会。

1.什么是单元测试

单元测试这个概念的话,在网上,一拉一大堆。这里我就不描述了。我只是说说我自己的理解。谈到单元测试,基本就要说到敏捷软件开发,为啥?我个人理解的敏捷软件开发的精髓在于快速迭代和重构,特别是重构。然而在迭代和重构的过程,如何保证之前代码流程和结构不被破坏?这个就需要测试,特别是白盒测试,因为重构过程中,除了要保证流程和逻辑无误之外,有时候,还需要保证中间变量一致。这个基本就是单元测试了。这个后面会有实例说明。

2.单元测试的要求

单元测试其实对团队要求很高的,特别是配合度上面。之前了解过,有的说单元测试是测试来做,有的说的是开发来做。其实无论谁来做,对团队的要求都很高的,这些不仅仅是技能上面要求的,更多的是思想上的。我清楚的记得,在第一次听单元测试的培训的时候,老师就说过,其实单元测试的要求还是比较高的,特别是需要开发有测试的意识,也就是说,开发的代码需要具有可测试性,之前不理解,现在做了以后才明白确实如此。为啥需要具有可测试性,后面讲解。

如果开发自己来做单元测试,那么问题来了,单元测试的代码其实工作量很大的我统计过被测代码和测试的代码的比例至少是1:1.5,而且还不包括打桩的基础代码,那么这个对开发的工作量来说是致命的。如果单元测试由测试来做,那么恭喜你们,对测试和研发的要求很高很高,特别是头的,因为这样就需要测试理解开发的流程,还需要研发配合测试来做,想想都觉得恐怖。

3.单元测试怎么做

我自己东西是用的C来做的,那么我单元测试的工具选用的CPPUTEST-3.8。这里我只是简单阐述下,我自己对这块的理解,和我的做法。每个人对于单元测试理解不同,做法也不同。并且测试,不可能保证测出所有的问题。

简单说来,就是通过编译和链接的手法,将被测代码和测试代码通过不同的编译,链接到一块,将测试和实际的产品工程给分开。举个简单的例子,我有个C/S模式的软件,客户端Clinet 启动以后,会通过TCP连接向服务器端Server发送一个字符串”hello”,然后服务器回应一个”world”。

第一步就是工程布局,这里就是第一个考研需要研发具有其代码可测试性的地方,cpputest具有自己的main函数,也就是说这里需要有3个可执行文件,这里以windows为例,只调用socket接口则可以无缝切换到在linux下,Clinet.exe、Service.exe、unit_test.exe。这里就需要把client和server的main函数给单独分开,才能进行单元测试。

如下图所示,Clinet.o和Client_main.o链接libwinsocket32.a和libwin32.a以后,则可以生成实际的产品Client.exe。而通过Client.o和测试代码中.o文件链接libwin32.a以后,则为测试的unit_test.exe。这里Clinet.exe相比unit_test.exe多链接了libwinsocket.a。那么socket的函数如何处理,我这里是在socket_mock.cpp中实现的。socket_mock.cpp中实现了对又有socket调用的“桩”。在什么地方打桩和如何打桩,在我看来,是整个单元测试的灵魂,换句话说,需要对整个工程有全面的掌握,因为桩很关键,如果桩打不好,不仅测试恼火,后面的维护更恼火。这块后面在说明。


下面是client.c被测代码的实际代码,功能很简单,连接服务器,发送字符串"hello",并且接受服务器的回应。

Clinet.c代码:

#include <winsock2.h>

#include <stdio.h>


static int g_socket_fd = 0;


int init_client()

{

   SOCKADDR_IN addrServer;//服务端地址


   g_socket_fd = socket( AF_INET,SOCK_STREAM,0 );

   addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//目标IP(127.0.0.1是回送地址)

   addrServer.sin_family = AF_INET;

   addrServer.sin_port = htons(6000);//连接端口6000


   connect(g_socket_fd,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));


   return 0;

}


#define BUFFSIZE 1024

int process_client()

{

   unsigned char p_buff[BUFFSIZE];


   snprintf( p_buff, BUFFSIZE, "hello" );

   send( g_socket_fd, p_buff, BUFFSIZE, 0 );


   recv( g_socket_fd, p_buff, BUFFSIZE, 0 );

   return ;

}


void uninit_clinet()

{

   close( g_socket_fd );

   g_socket_fd = -1;

   return ;

}

         被测试代码通过调用posix标准的socket函数发送字符串到服务器端。现在就针对client代码进行测试。第一就需要打桩,将socket的函数封装一次,其代码在socket_mock.cpp 中。对于单元测试,我个人理解桩,实际就是类似进出口的钩子,合理的设置好桩了以后,就可以任意构造自己需要的数据,和返回的流程。在这个例子中通过socket的桩,可以获取每一个自己想要获取的发送数据,也可以构造自己所需要的任意的接收数据。从而可以达到对被测代码逻辑和流程的测试。

#include <string.h>

#include <stdio.h>

#include <assert.h>


int (*socket_mock)(int domain, int type,int protocol) = NULL;

int (*connect_mock)( int sockfd, conststruct sockaddr_t *addr, int addrlen ) = NULL;

int (*send_mock)( int sockfd, const void*buf, int len, int flags ) = NULL;

int (*recv_mock)( int sockfd, void *buff,int len, int flags ) = NULL;


#ifdef __cplusplus

extern "C" {

#endif


int socket( int domain, int type, intprotocol )

{

   if( socket_mock )

    {

       return socket_mock( domain, type, protocol );

    }


   return 0;

}


int inet_addr( char *s )

{

   return 0;

}


int htons( int short )

{

   return 24;

}


int connect( int sockfd, const structsockaddr_t *addr, int addrlen )

{

   if( connect_mock )

    {

       return connect_mock( sockfd, addr, addrlen );

    }

   return 0;

}


int send( int sockfd, const void *buf, intlen, int flags )

{

   if( send_mock )

    {

       return send_mock( sockfd, buf, len, flags );

    }

   return 0;

}


int recv( int sockfd, void *buff, int len,int flags )

{

   if( recv_mock )

    {

       return recv_mock( sockfd, buff, len, flags );

    }


   return 0;

}


#ifdef __cplusplus

}

#endif


void init_socket_mock()

{

   socket_mock = NULL;

   connect_mock = NULL;

   send_mock = NULL;

   recv_mock = NULL;

}


void unini_socket_mock()

{

   socket_mock = NULL;

   connect_mock = NULL;

   send_mock = NULL;

   recv_mock = NULL;

}


         最后我们看下client的测试代码,通过对send_mock函数指针的赋值,可以将clinet.c中的send函数定向到测试的代码,从而可以获取其发送出去的数据,通过对数据的校验达到测试的目的。在这个例子中,我只是简答的对发送出去的数据是否是hello进行判断。但是这里可以测试的case应该是比较多的。

#include <stdio.h>

#include <strings.h>


#include "socket_mock.h"

#include "CppUTest/TestHarness.h"


TEST_GROUP( test_group_clinet )

{

   void setup()

    {

       init_socket_mock();

       init_client();

    }

   void teardown()

    {

       uninit_clinet();

       unini_socket_mock();

    }

};

static const char *p_hellow_string ="hello";

static int first_send( int sockfd, constvoid *buff, int len, int flags )

{

   const char *p_tmp = (const char*)buff;

   if( strncmp( p_tmp,p_hellow_string, strlen(p_tmp) ) )

    {

       FAIL( p_tmp );

    }

   return len;

}

TEST( test_group_clinet, first_test )

{

   send_mock = first_send;

   process_client();


   return ;

}


其运行结果如图。


假如某次,研发在重构时候,将字符串“hello”写错为“hallo”,单元测试运行结果如下图。


这里只是我对单元测试的简单的理解,后面会结合点实际的项目更多的说明。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    VD2012阅读 2,270评论 0 5
  • 1. 单元测试入门——优秀基因 单元测试最初兴起于敏捷社区。1997年,设计模式四巨头之一Erich Gamma和...
    厲铆兄阅读 2,642评论 3 16
  • 浓雾渐渐把你我隔开 路旁昏暗的灯仍旧闪烁 自行车的铃声也越来越远 当我回过神时 已经望不到你了 浓雾渐渐把你我隔开...
    路sha阅读 277评论 2 3
  • 这个月的基础安排基本已经排满了,感觉好像都没有自己的时间,但又感觉又有很多时间,很多事情都还没去做。 ...
    幽兰依依阅读 102评论 0 0