让你提前认识软件开发(19):C语言中的协议及单元测试示例

简介: 第1部分 重新认识C语言C语言中的协议及单元测试示例 【文章摘要】        在实际的软件开发项目中,经常要实现多个模块之间的通信,这就需要大家约定好相互之间的通信协议,各自按照协议来收发和解析消息。

第1部分 重新认识C语言

C语言中的协议及单元测试示例

 

【文章摘要】

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

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

【关键词】

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

 

一、软件模块之间的协议

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

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

1 一条完整的消息示意图

        在C语言中,用结构体来表示协议。在进行消息解析的时候,一般只关注消息体的内容。消息头只是用于标识一条消息,让其它模块能够识别该类消息。

 

二、单元测试

       在提交程序版本之前,开发人员需要对代码进行单元测试和集成测试。那么什么是单元测试呢?单元测试就是对程序中的一个函数进行测试,看对于某个输入,是否有预期的输出。

       单元测试的示意图如图2所示。

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;      // 操作类型, 操作类型只能为12

    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))    // 操作类型只能为12, 其它为数据异常

    {

        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所示。

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

 

2. 异常测试用例

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

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

        (1) “操作类型”不为12

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

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

 

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

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

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

 

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

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

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

        正常和异常测试的情况都有很多种,这里就不一一列举了。为了确保程序的正确性,一定要对程序(或者函数)进行充分的单元测试。

 

七、总结

        对于协议,这是不同模块之间通信的桥梁。因此,在开始编码之前,一定要将协议定义清楚,这样也可以减少后续修改带来的不便。

        对于单元测试,这是每个软件开发工程师都必须要认真对待的。单元测试进行得是否彻底,会直接影响到软件产品的质量。

        本文以实际的程序代码为例子,对用C语言表示协议和对代码进行单元测试作了详细的介绍。文中涉及到的协议表示方法和单元测试方法可供相关的软件开发工程师参考。

 

 

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

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

 

目录
相关文章
|
1月前
|
网络协议 安全 测试技术
性能工具之emqtt-bench BenchMark 测试示例
【4月更文挑战第19天】在前面两篇文章中介绍了emqtt-bench工具和MQTT的入门压测,本文示例 emqtt_bench 对 MQTT Broker 做 Beachmark 测试,让大家对 MQTT消息中间 BenchMark 测试有个整体了解,方便平常在压测工作查阅。
250 7
性能工具之emqtt-bench BenchMark 测试示例
|
1月前
|
C语言
对链表使用插入排序的C语言实现示例
对链表使用插入排序的C语言实现示例
|
1月前
|
C语言
最简单的C语言程序示例
最简单的C语言程序示例
27 0
|
1月前
|
C语言
在C语言中数组作为函数参数的应用与示例
在C语言中数组作为函数参数的应用与示例
25 0
|
1月前
|
算法 C语言
在C语言中函数的递归调用及应用示例
在C语言中函数的递归调用及应用示例
21 1
|
1月前
|
C语言
在C语言中多维数组名作为函数参数的应用与示例
在C语言中多维数组名作为函数参数的应用与示例
20 0
|
8月前
|
测试技术 API C#
C# 软件开发之单元测试
在日常开发中,一般通过启动调试或运行程序来查看功能是否符合预期,如果不符合预期,则需要优化程序,再次运行,如此反复,直到程序的输出符合预期需求为止。随着程序的不断复杂化,某些功能的测试也变得越来越复杂,可能为了验证一个很小的改动项,就需要操作很多步骤,才能验证成功,如果验证不成功,则需要多次重复验证,这对于开发者来说,将大大的拖延了开发进度。如何才能将复杂的功能进行拆分,每一个都可以单独进行验证呢?如果其他的功能没有问题,则只需要验证修改的那部分内容即可,这就是本篇文章需要介绍的单元测试。通过创建和运行单元测试,检查代码是否按预期工作。
305 1
|
1月前
|
JSON 数据可视化 测试技术
性能测试之Artillery(示例及指标)
性能测试之Artillery(示例及指标)
36 2
|
14天前
|
测试技术 C语言
数据结构学习记录——树习题—Tree Traversals Again(题目描述、输入输出示例、解题思路、解题方法C语言、解析)
数据结构学习记录——树习题—Tree Traversals Again(题目描述、输入输出示例、解题思路、解题方法C语言、解析)
13 1
|
14天前
|
编译器 C语言
C语言尾递归知识及代码示例
C语言尾递归知识及代码示例
11 0