软件开发中的自测及C代码示例

简介: 在软件开发中,程序自测是一个永远都绕不开的话题。很多开发人员以写出有难度的代码为荣,但却不重视对自己编写的代码进行测试,这导致了最终到达客户手中的产品质量不高,bug频发,损害了公司的形象。

在软件开发中,程序自测是一个永远都绕不开的话题。很多开发人员以写出有难度的代码为荣,但却不重视对自己编写的代码进行测试,这导致了最终到达客户手中的产品质量不高,bug频发,损害了公司的形象。对于一个开发人员来说,我们应该将开发和自测置于同等重要的地位,我们花在自测上的时间要不比开发少。能否对自己编写的代码进行充分的自测也是检验一个开发人员水平高低的标准之一。

自测方法
根据所编写的程序的特点,自测方法大致有如下几种:

第一种,利用模拟工具进行自测。这种方法适用于需要其他模块(尚不具备)发过来的消息才能触发程序流程的情况。模拟工具要严格按照协议的要求发消息,并处理相应的应答消息。这种方法的优点是可模拟真实的系统来测试代码,功能覆盖比较完全;其缺点是模拟工具的编写比较复杂(相当于要实现一个完整的功能模块)、花费的时间较多。

第二种,利用对端模块进行自测。这种方法适用于两个软件模块功能的耦合性比较强且在同时开发的情况。例如,正在开发的模块1的功能A需要正在开发的模块2的功能B才能触发,而模块1和模块2的开发进度差不多,此时,就可以通过模块2向模块1发消息的方法来对功能A和功能B进行自测。

第三种,手动插入数据或执行命令进行自测。这种方法适用于某个软件模块的功能比较独立的情况。此时,没有其他模块与该模块进行消息的交互,也没有编写单独的测试模块。利用手工的方法的优点是可以根据程序的特点设置测试用例,代码覆盖率比较高;其缺点是难以对大数据量的消息进行测试,无法保证程序性能。

第四种,在程序中添加测试代码进行自测。也就是说,在编写软件功能代码的同时,将测试代码一并加入其中。这种方法的优点比较明显,那就是无需与其他程序模块进行消息交互、无需手动插入数据或发消息,当整个软件模块运行起来之后,测试代码即可发挥其功效。当然,该方法也有缺点,那就是编写这些测试代码需要花费一定的时间,可能导致代码过于臃肿。

本文接下来的部分,将用实际的C代码来示例第四种自测方法的使用。

示例程序
本文用到的示例程序比较简单,其功能是每个一段时间(1分钟)将源目录中满足前缀要求的文件备份(移动)到备份目录中。
程序(FilesBackup.c)如下:

/**********************************************************************
* 版权所有(C)2016, Zhou Zhaoxiong。
*
* 文件名称:FilesBackup.c
* 文件标识:无
* 内容摘要:将某个目录中的文件进行备份
* 其它说明:无
* 当前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20160701
*
**********************************************************************/
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <ftw.h>
#include <time.h>

// 重定义数据类型
typedef signed   int       INT32;
typedef unsigned int       UINT32;
typedef unsigned char      UINT8;

// 全局变量定义
UINT8 g_szSourceDir[256] = {0};     // 源文件的目录
UINT8 g_szBackupDir[256] = {0};     // 备份文件的目录

// 函数声明
INT32 SelectFlies(struct dirent *pDir);
void ScanDirAndBackup(void);
void Sleep(UINT32 iCountMs);


/****************************************************************
* 功能描述: 主函数
* 输入参数: 无
* 输出参数: 无
* 返回值: 0-执行完成
* 其他说明: 无
* 修改日期       版本号        修改人        修改内容
*-------------------------------------------------------------
* 20160701        V1.0    Zhou Zhaoxiong     创建
****************************************************************/
INT32 main(void)
{
    INT32 iRetValue = 0;

   // 获取源文件的目录
   snprintf(g_szSourceDir, sizeof(g_szSourceDir)-1,"%s/zhouzx/TestDir/SourceDir", getenv("HOME"));

    // 获取备份文件的目录
   snprintf(g_szBackupDir, sizeof(g_szBackupDir)-1,"%s/zhouzx/TestDir/BackupDir", getenv("HOME"));

   // 调用函数执行文件的备份
   while (1)
   {
       ScanDirAndBackup();

       Sleep(60 * 1000);    // 每一分钟执行一次文件的备份
   }

   return 0;
}


/**********************************************************************
* 功能描述:根据前缀和后缀选择文件
* 输入参数:dir-目录
* 输出参数:无
* 返回值:0-失败   1-成功
* 其它说明:无
* 修改日期         版本号      修改人          修改内容
*--------------------------------------------------------------------
* 20160701         V1.0   ZhouZhaoxiong        创建
***********************************************************************/
INT32 SelectFlies(struct dirent *pDir)
{
   INT32 iSelectResult = 0;

   UINT8 szFilePrefix[10] = {0};     // 源文件的前缀

   if (pDir == NULL)
   {
       printf("SelectFlies:input parameter is NULL!\n");
       return 0;
   }

   // 匹配文件前缀和后缀
   strncpy(szFilePrefix, "File_", strlen("File_"));
   iSelectResult = (strncmp(pDir->d_name, szFilePrefix,strlen(szFilePrefix)) == 0);

   if (iSelectResult == 1)           // 找到了匹配前缀的文件
   {
       return 1;
   }
   else
   {
       return 0;
   }
}


/**********************************************************************
* 功能描述:扫描目录并备份文件
* 输入参数:无
* 输出参数:无
* 返回值:无
* 其它说明:无
* 修改日期         版本号      修改人          修改内容
*--------------------------------------------------------------------
* 20160701         V1.0     ZhouZhaoxiong        创建
***********************************************************************/
void ScanDirAndBackup(void)
{
   INT32  iScanDirRet       = 0;
   UINT32 iFileIdx          = 0;
   UINT8  szScanedFile[512] = {0};
   UINT8  szCmdBuf[256]     = {0};
   struct dirent **ppDirEnt = NULL;

   iScanDirRet = scandir(g_szSourceDir, &ppDirEnt, SelectFlies,alphasort);
   if (iScanDirRet < 0)   // 扫描目录出错
   {
       printf("ScanDirAndBackup:exec scandir failed, path=%s\n",g_szSourceDir);
       return;
   }
   else if (iScanDirRet == 0)   // 目录下无文件
   {
       printf("ScanDirAndBackup:no satisfied file in directory %s\n",g_szSourceDir);
       return;
   }
   else          // 将满足条件的文件移动到备份目录中
   {
       for (iFileIdx = 0; iFileIdx < iScanDirRet; iFileIdx ++)
       {
           memset(szScanedFile, 0x00, sizeof(szScanedFile));
           snprintf(szScanedFile, sizeof(szScanedFile) - 1, "%s/%s",g_szSourceDir, ppDirEnt[iFileIdx]->d_name);

           memset(szCmdBuf, 0x00, sizeof(szCmdBuf));
           snprintf(szCmdBuf, sizeof(szCmdBuf) - 1, "mv %s %s",szScanedFile, g_szBackupDir);
           system(szCmdBuf);

           printf("ScanDirAndBackup:now, %s\n", szCmdBuf);
       }
   }

   printf("ScanDirAndBackup:this time,totally moved %d file(s) to%s\n", iScanDirRet, g_szBackupDir);

   return;
}


/**********************************************************************
* 功能描述:程序休眠
* 输入参数:iCountMs-休眠时间(单位:ms)
* 输出参数:无
* 返回值:无
* 其它说明:无
* 修改日期      版本号       修改人        修改内容
*------------------------------------------------------------------
* 20160701       V1.0    Zhou Zhaoxiong     创建
********************************************************************/
void Sleep(UINT32 iCountMs)
{
   struct timeval t_timeout = {0};

   if (iCountMs < 1000)
   {
       t_timeout.tv_sec  = 0;
       t_timeout.tv_usec = iCountMs * 1000;
   }
   else
   {
       t_timeout.tv_sec  = iCountMs /1000;
       t_timeout.tv_usec = (iCountMs % 1000) * 1000;
   }
   select(0, NULL, NULL, NULL, &t_timeout);    // 调用select函数阻塞程序
}

添加测试代码之后的程序
我们添加测试代码的基本思路是在扫描源文件目录之前,先在该目录下生成文件,这样就相当于手动将文件放到源目录中了。

添加之后的程序代码如下:

/**********************************************************************
* 版权所有(C)2016, Zhou Zhaoxiong。
*
* 文件名称:FilesBackup.c
* 文件标识:无
* 内容摘要:将某个目录中的文件进行备份
* 其它说明:无
* 当前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20160701
*
**********************************************************************/
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <ftw.h>
#include <time.h>

// 重定义数据类型
typedef signed   int       INT32;
typedef unsigned int       UINT32;
typedef unsigned char      UINT8;

// 全局变量定义
UINT8 g_szSourceDir[256] = {0};     // 源文件的目录
UINT8 g_szBackupDir[256] = {0};     // 备份文件的目录

// 函数声明
INT32 SelectFlies(struct dirent *pDir);
void ScanDirAndBackup(void);
void Sleep(UINT32 iCountMs);
void CreateTestFile(void);


/****************************************************************
* 功能描述: 主函数
* 输入参数: 无
* 输出参数: 无
* 返回值: 0-执行完成
* 其他说明: 无
* 修改日期       版本号        修改人        修改内容
*-------------------------------------------------------------
* 20160701        V1.0    Zhou Zhaoxiong     创建
****************************************************************/
INT32 main(void)
{
    INT32 iRetValue = 0;

    // 获取源文件的目录
    snprintf(g_szSourceDir, sizeof(g_szSourceDir)-1,"%s/zhouzx/TestDir/SourceDir", getenv("HOME"));

   // 获取备份文件的目录    snprintf(g_szBackupDir,sizeof(g_szBackupDir)-1, "%s/zhouzx/TestDir/BackupDir",getenv("HOME"));

   // 调用函数执行文件的备份
   while (1)
   {
       // -------------
       // 先在源目录中创建测试文件
       CreateTestFile();
       // -------------

        ScanDirAndBackup();

       Sleep(60 * 1000);    // 每一分钟执行一次文件的备份
   }

   return 0;
}


/**********************************************************************
* 功能描述:根据前缀和后缀选择文件
* 输入参数:dir-目录
* 输出参数:无
* 返回值:0-失败   1-成功
* 其它说明:无
* 修改日期         版本号      修改人          修改内容
*--------------------------------------------------------------------
* 20160701         V1.0   ZhouZhaoxiong        创建
***********************************************************************/
INT32 SelectFlies(struct dirent *pDir)
{
   INT32 iSelectResult = 0;

   UINT8 szFilePrefix[10] = {0};     // 源文件的前缀

   if (pDir == NULL)
   {
       printf("SelectFlies:input parameter is NULL!\n");
       return 0;
   }

   // 匹配文件前缀和后缀
   strncpy(szFilePrefix, "File_", strlen("File_"));
   iSelectResult = (strncmp(pDir->d_name, szFilePrefix,strlen(szFilePrefix)) == 0);

   if (iSelectResult == 1)           // 找到了匹配前缀的文件
   {
       return 1;
   }
   else
   {
       return 0;
   }
}


/**********************************************************************
* 功能描述:扫描目录并备份文件
* 输入参数:无
* 输出参数:无
* 返回值:无
* 其它说明:无
* 修改日期         版本号      修改人          修改内容
*--------------------------------------------------------------------
* 20160701         V1.0    ZhouZhaoxiong        创建
***********************************************************************/
void ScanDirAndBackup(void)
{
   INT32  iScanDirRet       = 0;
   UINT32 iFileIdx          = 0;
   UINT8  szScanedFile[512] = {0};
   UINT8  szCmdBuf[256]     = {0};
   struct dirent **ppDirEnt = NULL;

   iScanDirRet = scandir(g_szSourceDir, &ppDirEnt, SelectFlies,alphasort);
   if (iScanDirRet < 0)   // 扫描目录出错
   {
       printf("ScanDirAndBackup:exec scandir failed, path=%s\n",g_szSourceDir);
       return;
   }
   else if (iScanDirRet == 0)   // 目录下无文件
   {
       printf("ScanDirAndBackup:no satisfied file in directory %s\n",g_szSourceDir);
       return;
   }
   else          // 将满足条件的文件移动到备份目录中
   {
       for (iFileIdx = 0; iFileIdx < iScanDirRet; iFileIdx ++)
       {
           memset(szScanedFile, 0x00, sizeof(szScanedFile));
           snprintf(szScanedFile, sizeof(szScanedFile) - 1, "%s/%s", g_szSourceDir,ppDirEnt[iFileIdx]->d_name);

           memset(szCmdBuf, 0x00, sizeof(szCmdBuf));
           snprintf(szCmdBuf, sizeof(szCmdBuf) - 1, "mv %s %s",szScanedFile, g_szBackupDir);
           system(szCmdBuf);

           printf("ScanDirAndBackup:now, %s\n", szCmdBuf);
       }
   }

   printf("ScanDirAndBackup:this time,totally moved %d file(s) to%s\n", iScanDirRet, g_szBackupDir);

   return;
}


/**********************************************************************
* 功能描述:程序休眠
* 输入参数:iCountMs-休眠时间(单位:ms)
* 输出参数:无
* 返回值:无
* 其它说明:无
* 修改日期      版本号       修改人        修改内容
*------------------------------------------------------------------
* 20160701       V1.0    Zhou Zhaoxiong     创建
********************************************************************/
void Sleep(UINT32 iCountMs)
{
   struct timeval t_timeout = {0};

   if (iCountMs < 1000)
   {
       t_timeout.tv_sec  = 0;
       t_timeout.tv_usec = iCountMs * 1000;
   }
   else
   {
       t_timeout.tv_sec  = iCountMs /1000;
       t_timeout.tv_usec = (iCountMs % 1000) * 1000;
   }
   select(0, NULL, NULL, NULL, &t_timeout);    // 调用select函数阻塞程序
}


/**********************************************************************
 * 功能描述:创建本地测试文件
 * 输入参数:无
 * 输出参数:无
 * 返回值:无
 * 其它说明:每一轮创建的测试文件数目加1,到达1000个之后又从1开始
 * 修改日期            版本号            修改人           修改内容
 *--------------------------------------------------------------------
 *20160701             V1.0          Zhou Zhaoxiong        创建
 **********************************************************************/
void CreateTestFile(void)
{
   UINT32  iFileIdx        = 0;
   UINT8   szFileName[500] = {0};
   FILE   *fp              = NULL;

   static UINT32 s_iFileNo = 0;

   s_iFileNo ++;
   if (s_iFileNo > 1000)
   {
       s_iFileNo = 0;
   }

   for (iFileIdx = 0; iFileIdx < s_iFileNo; iFileIdx ++)
   {
       // 获取带路径的文件名
       memset(szFileName, 0x00, sizeof(szFileName));
       snprintf(szFileName, sizeof(szFileName)-1, "%s/File_%d.txt",g_szSourceDir, iFileIdx);

       fp = fopen(szFileName, "a+");
       if (fp == NULL)
       {
            printf("CreateTestFile: open file %s failed!\n", szFileName);
            return;
       }

       fputs("Hello,world!", fp);
       fflush(fp);

       fclose(fp);
       fp = NULL;
   }

   if (s_iFileNo % 10 == 0)   // 每生成10批满足前缀要求的文件之后, 生成1个不满足前缀要求的文件
   {
       memset(szFileName, 0x00, sizeof(szFileName));
       snprintf(szFileName, sizeof(szFileName)-1, "%s/Test_%d.txt",g_szSourceDir, s_iFileNo);

       fp = fopen(szFileName, "a+");
       if (fp == NULL)
       {
            printf("CreateTestFile: open file %s failed!\n", szFileName);
            return;
       }

       fputs("Hello,world!", fp);
       fflush(fp);

       fclose(fp);
       fp = NULL;
   }
}

程序说明如下:
第一,本程序中添加的测试函数是CreateTestFile,其作用是在源目录中创建测试文件。考虑到程序性能,我们设定最多生成1000(可根据实际情况修改)个满足前缀要求的文件,并且每一轮生成的文件数比上一轮多一个。同时,每生成10轮的满足前缀要求的文件之后,要生成一个不满足前缀要求的文件,用以测试异常情况。如此,正常和异常情况都考虑到了。

第二,示例代码中写入文件的内容是固定的“Hello,world!”,大家可以根据需要修改该内容以满足自身测试的要求。

第三,在提交正式版本的时候,大家要将测试代码注释掉或删掉,以免影响正常的程序流程。

总结
很多人所理解的软件开发人员的工作就是写代码,而不包括测试,这样的理解是片面的。实际的经验表明,很多时候,我们花在测试上的时间比写代码的时间还要多。为了保证产品质量,很多项目组也对自测提出了较高的要求。

作为一位合格的软件开发人员,自测是一个检验和提升自身能力的好方法,大家一定要对自己编写的代码进行充分的测试。通过不断地实践,大家也可以总结出更多和更好的自测方法。

目录
相关文章
|
7月前
|
存储 Rust 自然语言处理
C代码演示WebAssembly工作流程
【2月更文挑战第3天】 工作流程: * C 代码使用 Emssripten 工具编译为 wasm 后缀的二进制文件,同时可以生成访问wasm的js胶水代码和html代码 * wasm后缀的二进制格式文件的文本表示方式为后缀为wat格式的文本文件,方便在编辑器和浏览器开发者工具中查看 * 可以使用wabt工具将wat格式的文本文件直接打包成wasm的二进制文件 * 使用WebAssemblyJavascriptAPI发起对wasm的调用 * 编写胶水代码 * 使用fetch/xhr获取wasm * 借助胶水代码访问wasm中的函数
98 0
|
7月前
|
存储 Web App开发 开发者
Python 自动化指南(繁琐工作自动化)第二版:附录 C:练习题的答案
Python 自动化指南(繁琐工作自动化)第二版:附录 C:练习题的答案
69 0
|
7月前
|
jenkins Devops 测试技术
单元测试与质量保证:确保Visual Basic代码的健壮性
【4月更文挑战第27天】在VB开发中,单元测试是保证代码质量和软件健壮性的关键。本文介绍了单元测试的基础,包括其定义和好处,如提高代码质量、促进重构。接着,讨论了MSTest、NUnit和xUnit等VB单元测试工具。遵循TDD原则和最佳实践,编写独立、有针对性的测试,并注重测试速度和覆盖率。通过示例展示了如何在Visual Studio中设置和运行测试。最后,提到了持续集成和自动化测试工具,如Jenkins和静态代码分析工具,以提升软件开发效率和质量。单元测试不仅是技术手段,更是提升团队协作和软件工程水平的文化体现。
91 2
|
7月前
|
程序员 编译器 C语言
编程最重要的技术之一 — 调试(以C语言代码为例)
编程最重要的技术之一 — 调试(以C语言代码为例)
122 0
|
数据采集 搜索推荐 小程序
编程新手:看懂很多示例,却依然写不好一个程序
当然题目本身难度不高,和我们公众号【每周一坑】栏目里的题相比,这个算是小 case 了。不过如果你是一个刚刚接触编程不久,才掌握条件判断、循环、列表的新手来说,还是有点小挑战的。
|
程序员 C语言 C++
编程最重要的技术—调试(以C语言代码为例)
编程最重要的技术—调试(以C语言代码为例)
|
Web App开发 前端开发 测试技术
|
开发框架 缓存 监控
测试是否有必要看开发代码?如何能看懂?
测试是否有必要看开发代码?如何能看懂?
|
安全 测试技术 数据库
接口测试平台代码实现141: 项目大用例干扰bug解决2
接口测试平台代码实现141: 项目大用例干扰bug解决2
接口测试平台代码实现141: 项目大用例干扰bug解决2
|
前端开发 IDE 测试技术
接口测试平台代码实现40:修改bug
我们的这个系列已经进行了长达12章成品预览和40章纯开发章节,但是基本还没做过完全一点的测试修复bug章节,每次新开发的功能也仅仅停留在单元/函数层面上的自测。
接口测试平台代码实现40:修改bug