軟件開發(fā)中的自測及C代碼示例
在軟件開發(fā)中,程序自測是一個永遠(yuǎn)都繞不開的話題。很多開發(fā)人員以寫出有難度的代碼為榮,但卻不重視對自己編寫的代碼進(jìn)行測試,這導(dǎo)致了最終到達(dá)客戶手中的產(chǎn)品質(zhì)量不高,bug頻發(fā),損害了公司的形象。對于一個開發(fā)人員來說,我們應(yīng)該將開發(fā)和自測置于同等重要的地位,我們花在自測上的時間要不比開發(fā)少。能否對自己編寫的代碼進(jìn)行充分的自測也是檢驗一個開發(fā)人員水平高低的標(biāo)準(zhǔn)之一。
自測方法
根據(jù)所編寫的程序的特點,自測方法大致有如下幾種:
***種,利用模擬工具進(jìn)行自測。這種方法適用于需要其他模塊(尚不具備)發(fā)過來的消息才能觸發(fā)程序流程的情況。模擬工具要嚴(yán)格按照協(xié)議的要求發(fā)消息,并處理相應(yīng)的應(yīng)答消息。這種方法的優(yōu)點是可模擬真實的系統(tǒng)來測試代碼,功能覆蓋比較完全;其缺點是模擬工具的編寫比較復(fù)雜(相當(dāng)于要實現(xiàn)一個完整的功能模塊)、花費(fèi)的時間較多。
第二種,利用對端模塊進(jìn)行自測。這種方法適用于兩個軟件模塊功能的耦合性比較強(qiáng)且在同時開發(fā)的情況。例如,正在開發(fā)的模塊1的功能A需要正在開發(fā)的模塊2的功能B才能觸發(fā),而模塊1和模塊2的開發(fā)進(jìn)度差不多,此時,就可以通過模塊2向模塊1發(fā)消息的方法來對功能A和功能B進(jìn)行自測。
第三種,手動插入數(shù)據(jù)或執(zhí)行命令進(jìn)行自測。這種方法適用于某個軟件模塊的功能比較獨立的情況。此時,沒有其他模塊與該模塊進(jìn)行消息的交互,也沒有編寫單獨的測試模塊。利用手工的方法的優(yōu)點是可以根據(jù)程序的特點設(shè)置測試用例,代碼覆蓋率比較高;其缺點是難以對大數(shù)據(jù)量的消息進(jìn)行測試,無法保證程序性能。
第四種,在程序中添加測試代碼進(jìn)行自測。也就是說,在編寫軟件功能代碼的同時,將測試代碼一并加入其中。這種方法的優(yōu)點比較明顯,那就是無需與其他程序模塊進(jìn)行消息交互、無需手動插入數(shù)據(jù)或發(fā)消息,當(dāng)整個軟件模塊運(yùn)行起來之后,測試代碼即可發(fā)揮其功效。當(dāng)然,該方法也有缺點,那就是編寫這些測試代碼需要花費(fèi)一定的時間,可能導(dǎo)致代碼過于臃腫。
本文接下來的部分,將用實際的C代碼來示例第四種自測方法的使用。
示例程序
本文用到的示例程序比較簡單,其功能是每個一段時間(1分鐘)將源目錄中滿足前綴要求的文件備份(移動)到備份目錄中。
程序(FilesBackup.c)如下:
- /**********************************************************************
- * 版權(quán)所有(C)2016, Zhou Zhaoxiong。
- *
- * 文件名稱:FilesBackup.c
- * 文件標(biāo)識:無
- * 內(nèi)容摘要:將某個目錄中的文件進(jìn)行備份
- * 其它說明:無
- * 當(dāng)前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:20160701
- *
- **********************************************************************/
- #include <stdio.h>
- #include <string.h>
- #include <dirent.h>
- #include <ftw.h>
- #include <time.h>
- // 重定義數(shù)據(jù)類型
- typedef signed int INT32;
- typedef unsigned int UINT32;
- typedef unsigned char UINT8;
- // 全局變量定義
- UINT8 g_szSourceDir[256] = {0}; // 源文件的目錄
- UINT8 g_szBackupDir[256] = {0}; // 備份文件的目錄
- // 函數(shù)聲明
- INT32 SelectFlies(struct dirent *pDir);
- void ScanDirAndBackup(void);
- void Sleep(UINT32 iCountMs);
- /****************************************************************
- * 功能描述: 主函數(shù)
- * 輸入?yún)?shù): 無
- * 輸出參數(shù): 無
- * 返回值: 0-執(zhí)行完成
- * 其他說明: 無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *-------------------------------------------------------------
- * 20160701 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ****************************************************************/
- 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"));
- // 調(diào)用函數(shù)執(zhí)行文件的備份
- while (1)
- {
- ScanDirAndBackup();
- Sleep(60 * 1000); // 每一分鐘執(zhí)行一次文件的備份
- }
- return 0;
- }
- /**********************************************************************
- * 功能描述:根據(jù)前綴和后綴選擇文件
- * 輸入?yún)?shù):dir-目錄
- * 輸出參數(shù):無
- * 返回值:0-失敗 1-成功
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *--------------------------------------------------------------------
- * 20160701 V1.0 ZhouZhaoxiong 創(chuàng)建
- ***********************************************************************/
- 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;
- }
- }
- /**********************************************************************
- * 功能描述:掃描目錄并備份文件
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返回值:無
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *--------------------------------------------------------------------
- * 20160701 V1.0 ZhouZhaoxiong 創(chuàng)建
- ***********************************************************************/
- 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;
- }
- /**********************************************************************
- * 功能描述:程序休眠
- * 輸入?yún)?shù):iCountMs-休眠時間(單位:ms)
- * 輸出參數(shù):無
- * 返回值:無
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *------------------------------------------------------------------
- * 20160701 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- 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); // 調(diào)用select函數(shù)阻塞程序
- }
添加測試代碼之后的程序
我們添加測試代碼的基本思路是在掃描源文件目錄之前,先在該目錄下生成文件,這樣就相當(dāng)于手動將文件放到源目錄中了。
添加之后的程序代碼如下:
- /**********************************************************************
- * 版權(quán)所有(C)2016, Zhou Zhaoxiong。
- *
- * 文件名稱:FilesBackup.c
- * 文件標(biāo)識:無
- * 內(nèi)容摘要:將某個目錄中的文件進(jìn)行備份
- * 其它說明:無
- * 當(dāng)前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:20160701
- *
- **********************************************************************/
- #include <stdio.h>
- #include <string.h>
- #include <dirent.h>
- #include <ftw.h>
- #include <time.h>
- // 重定義數(shù)據(jù)類型
- typedef signed int INT32;
- typedef unsigned int UINT32;
- typedef unsigned char UINT8;
- // 全局變量定義
- UINT8 g_szSourceDir[256] = {0}; // 源文件的目錄
- UINT8 g_szBackupDir[256] = {0}; // 備份文件的目錄
- // 函數(shù)聲明
- INT32 SelectFlies(struct dirent *pDir);
- void ScanDirAndBackup(void);
- void Sleep(UINT32 iCountMs);
- void CreateTestFile(void);
- /****************************************************************
- * 功能描述: 主函數(shù)
- * 輸入?yún)?shù): 無
- * 輸出參數(shù): 無
- * 返回值: 0-執(zhí)行完成
- * 其他說明: 無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *-------------------------------------------------------------
- * 20160701 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ****************************************************************/
- 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"));
- // 調(diào)用函數(shù)執(zhí)行文件的備份
- while (1)
- {
- // -------------
- // 先在源目錄中創(chuàng)建測試文件
- CreateTestFile();
- // -------------
- ScanDirAndBackup();
- Sleep(60 * 1000); // 每一分鐘執(zhí)行一次文件的備份
- }
- return 0;
- }
- /**********************************************************************
- * 功能描述:根據(jù)前綴和后綴選擇文件
- * 輸入?yún)?shù):dir-目錄
- * 輸出參數(shù):無
- * 返回值:0-失敗 1-成功
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *--------------------------------------------------------------------
- * 20160701 V1.0 ZhouZhaoxiong 創(chuàng)建
- ***********************************************************************/
- 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;
- }
- }
- /**********************************************************************
- * 功能描述:掃描目錄并備份文件
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返回值:無
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *--------------------------------------------------------------------
- * 20160701 V1.0 ZhouZhaoxiong 創(chuàng)建
- ***********************************************************************/
- 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;
- }
- /**********************************************************************
- * 功能描述:程序休眠
- * 輸入?yún)?shù):iCountMs-休眠時間(單位:ms)
- * 輸出參數(shù):無
- * 返回值:無
- * 其它說明:無
- * 修改日期 版本號 修改人 修改內(nèi)容
- *------------------------------------------------------------------
- * 20160701 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- 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); // 調(diào)用select函數(shù)阻塞程序
- }
- /**********************************************************************
- * 功能描述:創(chuàng)建本地測試文件
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返回值:無
- * 其它說明:每一輪創(chuàng)建的測試文件數(shù)目加1,到達(dá)1000個之后又從1開始
- * 修改日期 版本號 修改人 修改內(nèi)容
- *--------------------------------------------------------------------
- *20160701 V1.0 Zhou Zhaoxiong 創(chuàng)建
- **********************************************************************/
- 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;
- }
- }
程序說明如下:
***,本程序中添加的測試函數(shù)是CreateTestFile,其作用是在源目錄中創(chuàng)建測試文件??紤]到程序性能,我們設(shè)定最多生成1000(可根據(jù)實際情況修改)個滿足前綴要求的文件,并且每一輪生成的文件數(shù)比上一輪多一個。同時,每生成10輪的滿足前綴要求的文件之后,要生成一個不滿足前綴要求的文件,用以測試異常情況。如此,正常和異常情況都考慮到了。
第二,示例代碼中寫入文件的內(nèi)容是固定的“Hello,world!”,大家可以根據(jù)需要修改該內(nèi)容以滿足自身測試的要求。
第三,在提交正式版本的時候,大家要將測試代碼注釋掉或刪掉,以免影響正常的程序流程。
總結(jié)
很多人所理解的軟件開發(fā)人員的工作就是寫代碼,而不包括測試,這樣的理解是片面的。實際的經(jīng)驗表明,很多時候,我們花在測試上的時間比寫代碼的時間還要多。為了保證產(chǎn)品質(zhì)量,很多項目組也對自測提出了較高的要求。
作為一位合格的軟件開發(fā)人員,自測是一個檢驗和提升自身能力的好方法,大家一定要對自己編寫的代碼進(jìn)行充分的測試。通過不斷地實踐,大家也可以總結(jié)出更多和更好的自測方法。
【本文是51CTO專欄作者周兆熊的原創(chuàng)文章,作者微信公眾號:周氏邏輯(logiczhou)】