第1部分 又一次认识C语言

C语言中的协议及单元測试演示样例

【文章摘要】

在实际的软件开发项目中。常常要实现多个模块之间的通信。这就须要大家约定好相互之间的通信协议,各自依照协议来收发和解析消息。

本文以实际的程序代码为例,详细介绍了如何用C语言来实现通信协议,并基于对协议字段的推断,说明了程序单元測试的过程,为相关的开发工作提供了故意的參考。

【关键词】

软件开发  协议  单元測试  C语言  字段

一、软件模块之间的协议

什么是软件模块之间的协议?不同的软件模块之间要实现相互通信,就必须遵循共同的消息规范。大家依照约定好的规范来收发消息。软件模块之间的协议就是不同模块间消息交互的规范。

在通信协议中,一条完整的消息由消息头和消息体构成,如图1所看到的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhvdXpoYW94aW9uZzEyMjc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

图1 一条完整的消息示意图

在C语言中。用结构体来表示协议。在进行消息解析的时候,一般仅仅关注消息体的内容。

消息头仅仅是用于标识一条消息。让其他模块能够识别该类消息。

二、单元測试

在提交程序版本号之前,开发者须要对代码进行单元測试和集成測试。

那么什么是单元測试呢?单元測试就是对程序中的一个函数进行測试。看对于某个输入,是否有预期的输出。

单元測试的示意图如图2所看到的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhvdXpoYW94aW9uZzEyMjc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

图2 单元測试的示意图

能够把函数看成一个灰色的盒子,測试的时候仅仅关心输入和输出,要设计多组单元測试数据来对函数的功能进行測试。

此外,在測试中。另一个叫做“測试用例”的概念。測试用例就是一次測试的整个过程,包含:測试目的、预置条件、測试步骤、预期结果、通过准则、測试工具等。

三、本程序中的协议

本程序中的协议包含了消息头和消息体。当中。消息头有四个字段。消息体有五个字段。

例如以下代码所看到的。

// 消息头结构

typedef struct

{

    UINT16  iReserve1;

    UINT16  iReserve2;

    UINT16  iReserve3;

    UINT16  iReserve4;

}MsgHead_T;

 

// 消息结构体(包含消息头和消息体)

typedef struct

{

    MsgHead_T    MsgHead;                       // 消息头

    UINT32       iOperType;                         // 操作类型

    UINT8        szUserNumber[30];            // 用户号码

    UINT8        szOperTime[20];                // 操作时间, 格式为: yyyymmdd

    UINT32       iReserve1;                          // 保留字段1

    UINT8        szReserve2[50];                 // 保留字段2

}UserReqMsg_T;

在消息体的五个字段中。操作类型、用户号码和操作时间是本次要进行推断处理的字段。另外两个字段是保留字段,能够先不用赋详细的值。

在协议中,为什么要留有保留字段呢?这是方便以后对协议进行扩展。

也就是说,假设以后除了操作类型、用户号码和操作时间之外,还须要添加新的字段定义。能够直接利用扩展字段。这在实际的软件开发项目中是非常重要的。

四、程序代码

基于以上协议。本文中的程序代码例如以下所看到的:

/**********************************************************************

* 版权全部 (C)2014, Zhou Zhaoxiong。

*

* 文件名: UnitTest.c

* 文件标识:无

* 内容摘要:协议及单元測试演示样例代码

* 其他说明:无

* 当前版本号: V1.0

* 作    者: Zhou Zhaoxiong

* 完毕日期: 20140507

*

* 改动记录1:// 改动历史记录, 包含改动日期、版本号号、改动人及改动内容

* 改动日期:

* 版本号号:

* 改动人:

* 改动内容:

*

**********************************************************************/

#include <stdio.h>

#include <string.h>

 

// 重定义数据类型

typedef unsigned char       UINT8;

typedef unsigned short int    UINT16;

typedef unsigned int        UINT32;

typedef signed   int        INT32;

 

// 消息头结构

typedef struct

{

    UINT16  iReserve1;

    UINT16  iReserve2;

    UINT16  iReserve3;

    UINT16  iReserve4;

}MsgHead_T;

 

// 消息结构体(包含消息头和消息体)

typedef struct

{

    MsgHead_T   MsgHead;                // 消息头

    UINT32      iOperType;      // 操作类型, 操作类型仅仅能为1或2

    UINT8       szUserNumber[30];         // 用户号码

    UINT8       szOperTime[20];      // 操作时间, 格式为: yyyymmdd

    UINT32       iReserve1;                // 保留字段1

    UINT8        szReserve2[50];           // 保留字段2

}UserReqMsg_T;

 

// 函数声明

INT32 ProcUserReqMsg(UserReqMsg_T *ptUserReqMsg);

INT32 main();

 

/**********************************************************************

* 功能描写叙述:主函数

* 输入參数:无

* 输出參数:无

* 返回值: 0-运行完毕

* 其他说明:无

* 改动日期        版本号号              改动人         改动内容

* --------------------------------------------------------------------------------------------------

* 20140507         V1.0                zzx            创建

***********************************************************************/

INT32 main()

{

    UINT8  iRetVal          = 0;

    UINT32 iOperType        = 0;        // 操作类型

    UINT8  szUserNumber[30] = {0};      // 用户号码

    UINT8  szOperTime[10]  = {0};    // 操作时间, 格式为: yyyymmdd

 

    UserReqMsg_T tUserReqMsg = {0};     // 请求消息

 

    // 对消息头部进行赋值

    tUserReqMsg.MsgHead.iReserve1 = 1;

    tUserReqMsg.MsgHead.iReserve2 = 2;

    tUserReqMsg.MsgHead.iReserve3 = 3;

    tUserReqMsg.MsgHead.iReserve4 = 4;

 

    // 读入详细消息字段的值

    printf("操作类型: \n");

    scanf("%d", &iOperType);

    printf("用户号码: \n");

    scanf("%s", szUserNumber);

    printf("操作时间: \n");

    scanf("%s", szOperTime);

 

    // 对详细消息字段进行赋值(保留字段可不赋值)

    tUserReqMsg.iOperType = iOperType;

    strncpy(tUserReqMsg.szUserNumber, szUserNumber, strlen(szUserNumber));// 获取号码, 用strncpy取代strcpy

    strncpy(tUserReqMsg.szOperTime,   szOperTime,   strlen(szOperTime));     // 获取时间, 用strncpy取代strcpy

 

    // 对消息体的字段进行异常推断

    iRetVal = ProcUserReqMsg(&tUserReqMsg);  // 注意: 传递參数的时候要加上&

    if (iRetVal == 0)      // 函数运行正确

    {

        // 打印消息字段内容

        printf("The user request message is: iOperType=%d, szUserNumber=%s, szOperTime=%s.\n", tUserReqMsg.iOperType, tUserReqMsg.szUserNumber, tUserReqMsg.szOperTime);

        return 0;

    }

    else         // 打印异常消息

    {

        printf("Some content of the user request message is wrong, please check!\n");

        return -1;

    }

}

 

 

/**********************************************************************

* 功能描写叙述:对消息体的字段进行异常推断

* 输入參数: ptUserReqMsg-用户请求消息

* 输出參数:无

* 返回值: 0-成功   其他-失败

* 其他说明:无

* 改动日期        版本号号              改动人         改动内容

* --------------------------------------------------------------------------------------------------

* 20140507         V1.0                zzx            创建

***********************************************************************/

INT32 ProcUserReqMsg(UserReqMsg_T *ptUserReqMsg)

{

    INT32  iRetValue      = 0;

 

    // 对输入參数进行异常推断

    if (ptUserReqMsg == NULL)

    {

        printf("ProcUserReqMsg(...): input parameter(ptUserReqMsg) is NULL.\n");

 

        return -1;

    }

 

    // 对消息体字段进行异常推断

    if ((ptUserReqMsg->iOperType != 1) && (ptUserReqMsg->iOperType != 2))    // 操作类型仅仅能为1或2, 其他为数据异常

    {

        printf("ProcUserReqMsg(...): the iOperType is wrong, iOperType=%d.\n", ptUserReqMsg->iOperType);

 

        return -2;

    }

   

    if (strlen(ptUserReqMsg->szUserNumber) != 8) // 用户号码异常, 长度8位才正确

    {

        printf("ProcUserReqMsg(...): the szUserNumber is wrong.\n");

 

        return -3;

    }

 

    if (strlen(ptUserReqMsg->szOperTime) != 8)  // 操作时间异常, 长度8位才正确

    {

        printf("ProcUserReqMsg(...): the szOperTime is wrong.\n");

       

        return -4;

    }

 

    return 0;

}

本程序要对ProcUserReqMsg函数进行单元測试。看该函数是否能对消息体的字段进行异常推断。

五、单元測试用例

1. 正常測试用例

正常測试用例是指满足程序输入条件的測试用例。即观察程序在正确的输入情况下,是否能产生正确的输出。

什么是正常測试?包含了两种情况:
        1) 输入正确的值,程序产生正确的输出。
        2) 输入错误的值。程序产生错误的输出。

(1) “操作类型”为1

设定“操作类型”为1,“用户号码”和“操作时间”字段均符合协议要求。程序的运行情况如图3所看到的。

图3 “操作类型”为1的正常运行情况

(2) “操作类型”为2

设定“操作类型”为2,“用户号码”和“操作时间”字段均符合协议要求。程序的运行情况如图4所看到的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhvdXpoYW94aW9uZzEyMjc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

图4 “操作类型”为2的正常运行情况

2. 异常測试用例

异常測试用例是指不满足程序输入条件的測试用例,即观察程序在错误的输入情况下。产生的结果是如何的。

什么是异常測试?包含了两种情况:
        1) 输入正确的值,程序产生错误的输出。

2) 输入错误的值,程序产生正确的输出。

(1) “操作类型”不为1或2

设定“操作类型”为3(不为1或2的正整数),“用户号码”和“操作时间”字段均符合协议要求。程序的运行情况如图5所看到的。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhvdXpoYW94aW9uZzEyMjc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

图5 “操作类型”为3的异常运行情况

(2) “用户号码”不是8位

设定“操作类型”为1,“用户号码”字段为9位。“操作时间”字段符合协议要求。程序的运行情况如图6所看到的。

图6 “用户号码”为9位的异常运行情况

(3) “操作时间”不是8位

设定“操作类型”为1,“用户号码”符合协议要求,“操作时间”字段为9位。程序的运行情况如图7所看到的。

图7 “操作时间”为9位的异常运行情况

正常和异常測试的情况都有非常多种,这里就不一一列举了。

为了确保程序的正确性,一定要对程序(或者函数)进行充分的单元測试。

七、总结

对于协议。这是不同模块之间通信的桥梁。因此,在開始编码之前,一定要将协议定义清楚,这样也能够降低兴许改动带来的不便。

对于单元測试,这是每一个软件开发project师都必须要认真对待的。

单元測试进行得是否彻底。会直接影响到软件产品的质量。

本文以实际的程序代码为样例。对用C语言表示协议和对代码进行单元測试作了详细的介绍。

文中涉及到的协议表示方法和单元測试方法可供相关的软件开发project师參考。

(欢迎訪问南邮BBS:http://bbs.njupt.edu.cn/)
(欢迎訪问重邮BBS:http://bbs.cqupt.edu.cn/nForum/index)

(本系列文章每周更新两篇,敬请期待!本人微博:http://weibo.com/zhouzxi?topnav=1&wvr=5。微信号:245924426,欢迎关注!)

让你提前认识软件开发(19):C语言中的协议及单元測试演示样例的更多相关文章

  1. C#开发Unity游戏教程循环遍历做出推断及Unity游戏演示样例

    C#开发Unity游戏教程循环遍历做出推断及Unity游戏演示样例 Unity中循环遍历每一个数据,并做出推断 非常多时候.游戏在玩家做出推断以后.游戏程序会遍历玩家身上大量的所需数据,然后做出推断. ...

  2. 让你提前认识软件开发(31):数据库脚本中的begin与end

    版权声明:本文为博主原创文章.对文章内容有不论什么意见或建议,欢迎与作者单独交流.作者QQ(微信):245924426. https://blog.csdn.net/zhouzxi/article/d ...

  3. 构造Scala开发环境并创建ApiDemos演示样例项目

    从2011年開始写Android ApiDemos 以来.Android的版本号也更新了非常多,眼下的版本号已经是4.04. ApiDemos中的样例也添加了不少,有必要更新Android ApiDe ...

  4. JavaScript 中对变量和函数声明提前的演示样例

    如题所看到的,看以下的演示样例(能够使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发人员工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter能够中途代码 ...

  5. 【COCOS2D-HTML5 开发之三】演示样例项目附源代码及执行的GIF效果图

    本站文章均为李华明Himi原创,转载务必在明显处注明:(作者新浪微博:@李华明Himi) 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/cocos2d- ...

  6. SNF快速开发平台MVC-各种级联绑定方式,演示样例程序(包含表单和表格控件)

    做了这么多项目,经常会使用到级联.联动的情况. 如:省.市.县.区.一级分类.二级分类.三级分类.仓库.货位. 方式:有表单需要做级联的,还是表格行上需要做级联操作的. 实现:实现方法也有很多种方式. ...

  7. 移动端报表JS开发演示样例

    近期对移动端的报表开发颇有研究,细磨精算了好久,尽管到如今还是"囊中羞涩",但决定还是先抛砖引玉,拿点小干货出来和大家分享. 研究的工具是比較有代表性的FineReport. 1. ...

  8. AppCan移动应用开发平台新增9个超有用插件(内含演示样例代码)

    使用AppCan平台进行移动开发.你所须要具备的是Html5+CSS +JS前端语言基础.此外.Hybrid混合模式应用还需结合原生语言对功能模块进行封装,对于没有原生基础的开发人员,怎样实现App里 ...

  9. 【Unity 3D 游戏开发】Unity3D 入门 - 工作区域介绍 与 入门演示样例

    一. 工作区域具体解释 1. Scence视图 (场景设计面板) scence视图简单介绍 : 展示创建的游戏对象, 能够对全部的游戏对象进行 移动, 操作 和 放置; -- 演示样例 : 创建一个球 ...

随机推荐

  1. Nginx +iis反向代理

    一:简介      Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.由俄罗斯的程序设计师Igor Sysoev所 ...

  2. mysql的触发器

    删除触发器 drop TRIGGER 触发器名字; 查找库里面的所有触发器 SELECT * FROM information_schema.`TRIGGERS`;show triggers 触发器语 ...

  3. R与数据分析旧笔记(十)非线性模型

    非线性模型 非线性模型 例子:销售额x与流通费率y > x=c(1.5,2.8,4.5,7.5,10.5,13.5,15.1,16.5,19.5,22.5,24.5,26.5)> y=c( ...

  4. R与数据分析旧笔记(⑨)广义线性回归模型

    广义线性回归模型 广义线性回归模型 例题1 R.Norell实验 为研究高压电线对牲畜的影响,R.Norell研究小的电流对农场动物的影响.他在实验中,选择了7头,6种电击强度, 0,1,2,3,4, ...

  5. symfony2 关于是否需要建立多bundle

    http://blog.danielribeiro.org/yes-you-can-have-low-coupling-in-a-symfony-standard-edition-applicatio ...

  6. hdu 4735Little Wish~ lyrical step~ 重复覆盖

    题目链接 给出一棵树, 树上点的值为0或1, 可以交换树上两个点的权值, 给出一个距离m, 所有的0距离最近的1的距离不能超过m, 求最少的交换次数. 首先对于每一个点u,所有离u的距离不超过m的点v ...

  7. Spring起步(一)Building a RESTful Web Service

    http://spring.io/guides/gs/rest-service/ 先放链接. 这个很小很小的一个功课,我却遇到了各种各样的奇葩错误,折腾了两天才弄好. 想要开始的话,需要一些准备工具 ...

  8. perspective结合transform的3D效果

    http://css-tricks.com/almanac/properties/p/perspective/ 链接中讲了 perspective的两种用法及比较: 1.perspective:100 ...

  9. How to get the xpath by clicking an html element

    How to get the xpath by clicking an html element How to get the xpath by clicking an html element

  10. [01] Preparation - Sitecore Installment

    Sitecore CMS 是一套内容管理系统商业软件,其底层平台依托于微软.net技术.由于最近的一个项目采用了这个平台,所以有机会接触到了这个产品. 虽然接触该产品已有一段时间,但总感觉对这个产品缺 ...