让你提前认识软件开发(19):C语言中的协议及单元測试演示样例
第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语言中的协议及单元測试演示样例的更多相关文章
- C#开发Unity游戏教程循环遍历做出推断及Unity游戏演示样例
C#开发Unity游戏教程循环遍历做出推断及Unity游戏演示样例 Unity中循环遍历每一个数据,并做出推断 非常多时候.游戏在玩家做出推断以后.游戏程序会遍历玩家身上大量的所需数据,然后做出推断. ...
- 让你提前认识软件开发(31):数据库脚本中的begin与end
版权声明:本文为博主原创文章.对文章内容有不论什么意见或建议,欢迎与作者单独交流.作者QQ(微信):245924426. https://blog.csdn.net/zhouzxi/article/d ...
- 构造Scala开发环境并创建ApiDemos演示样例项目
从2011年開始写Android ApiDemos 以来.Android的版本号也更新了非常多,眼下的版本号已经是4.04. ApiDemos中的样例也添加了不少,有必要更新Android ApiDe ...
- JavaScript 中对变量和函数声明提前的演示样例
如题所看到的,看以下的演示样例(能够使用Chrome浏览器,然后F12/或者右键,审查元素.调出开发人员工具,进入控制台console输入)(使用技巧: 控制台输入时Shift+Enter能够中途代码 ...
- 【COCOS2D-HTML5 开发之三】演示样例项目附源代码及执行的GIF效果图
本站文章均为李华明Himi原创,转载务必在明显处注明:(作者新浪微博:@李华明Himi) 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/cocos2d- ...
- SNF快速开发平台MVC-各种级联绑定方式,演示样例程序(包含表单和表格控件)
做了这么多项目,经常会使用到级联.联动的情况. 如:省.市.县.区.一级分类.二级分类.三级分类.仓库.货位. 方式:有表单需要做级联的,还是表格行上需要做级联操作的. 实现:实现方法也有很多种方式. ...
- 移动端报表JS开发演示样例
近期对移动端的报表开发颇有研究,细磨精算了好久,尽管到如今还是"囊中羞涩",但决定还是先抛砖引玉,拿点小干货出来和大家分享. 研究的工具是比較有代表性的FineReport. 1. ...
- AppCan移动应用开发平台新增9个超有用插件(内含演示样例代码)
使用AppCan平台进行移动开发.你所须要具备的是Html5+CSS +JS前端语言基础.此外.Hybrid混合模式应用还需结合原生语言对功能模块进行封装,对于没有原生基础的开发人员,怎样实现App里 ...
- 【Unity 3D 游戏开发】Unity3D 入门 - 工作区域介绍 与 入门演示样例
一. 工作区域具体解释 1. Scence视图 (场景设计面板) scence视图简单介绍 : 展示创建的游戏对象, 能够对全部的游戏对象进行 移动, 操作 和 放置; -- 演示样例 : 创建一个球 ...
随机推荐
- xhprof
#官网下载 http://pecl.php.net/package/xhprof tar zxf xhprof-0.9.2.tgz cd xhprof-0.9.2/extension/ sud ...
- spring 入门篇
spring 入门篇 相对于Hibernate(冬眠),Spring(春天),具有更多的诗意与希望的感觉,是为了解决传统J2EE开发效率过低.开发商之间不统一.没有真正实现“写一次到处 ...
- java——String的那边破事
经典的先看下面一段代码,请问最终创建几个对象,分别在哪里? String s0 = new String("luoliang.me"); String s1 = "luo ...
- PHP无法获取Referer问题排查
测试结果: 同一个页面,2次打开,第一次能获取到Referer第二次获取不到,很好奇原因所在. test1.php代码是: <?php echo '测试来源:直接载入页面<br/>' ...
- Ubuntu 12.04 LTS下logomaker的安装
学校嵌入式课程实验,本地装的时候遇到了一系列问题,因为基本不会linux所以到处搜解决方法,中间还走了不少弯路,作个笔记. 1.解压安装倒是没什么问题,运行时提示找不到共享库 logomaker: e ...
- Oracle 数据库导入、导出
第一步:新建一个txt文件: exp.exe jeamsluu@test file=d:\daochu.dmp log=1.log 另存为.bat格式的文件 第二步:双击运行:此时会弹出输入口令的对话 ...
- MFC 动态创建控件
动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件: 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立一个容器,一 ...
- JAVA GUI学习 - JPopupMenu鼠标右键菜单组件学习
public class JPopmenuKnow { public void test() { //为表格添加鼠标右键菜单 JMenuItem jMenuItemFileInfo = new JMe ...
- Apache OFbiz entity engine源代码解读
简单介绍 近期一直在看Apache OFbiz entity engine的源代码.为了能够更透彻得理解,也由于之前没有看人别人写过分析它的文章,所以决定自己来写一篇. 首先,我提出一个问题,假设你有 ...
- Android应用开发基础篇(4)-----TabHost(选项卡)
一.概述 TabHost是一种用来显示标签的组件,不清楚?看一下来电通这个应用就知道了.这个组件用起来与其他组件不太一样,它需要继承TabActivity这个类,还有它的布局文件与我们平时用的也有些不 ...