您的位置:軟件測(cè)試 > 軟件項(xiàng)目管理 > 項(xiàng)目案例分析 >
測(cè)試為先/測(cè)試驅(qū)動(dòng)案例分析
作者:網(wǎng)絡(luò)轉(zhuǎn)載 發(fā)布時(shí)間:[ 2013/9/29 15:45:10 ] 推薦標(biāo)簽:

  進(jìn)行測(cè)試為先測(cè)試驅(qū)動(dòng)的程序設(shè)計(jì)是確保敏捷開發(fā)順進(jìn)行的有效措施。這篇案例將為讀者提供詳細(xì)的開發(fā)歷程,來分析測(cè)試為先測(cè)試驅(qū)動(dòng)的程序設(shè)計(jì)的過程。本文的重點(diǎn):

簡(jiǎn)要重復(fù)敘述一下測(cè)試為先/測(cè)試驅(qū)動(dòng)得好處。
簡(jiǎn)要介紹一下案例中的項(xiàng)目。
沒有利用測(cè)試為先/測(cè)試驅(qū)動(dòng)設(shè)計(jì)的單元代碼是什么樣的?
沒有利用測(cè)試為先/測(cè)試驅(qū)動(dòng)設(shè)計(jì)的單元代碼里有什么樣的問題?
針對(duì)單元代碼設(shè)計(jì)的手動(dòng)單元測(cè)試。
查找出毛病后的改進(jìn)代碼。

測(cè)試為先/測(cè)試驅(qū)動(dòng)得好處

  傳統(tǒng)的瀑布型軟件開發(fā)是先從客戶那里獲得需求,然后進(jìn)行紙上談兵的設(shè)計(jì),接著是程序源碼寫作構(gòu)建,后才是測(cè)試者對(duì)質(zhì)量進(jìn)行檢評(píng)。從需求的分析到后的測(cè)試,兩者的相隔往往有好幾個(gè)月。等到測(cè)試發(fā)現(xiàn)結(jié)構(gòu)性問題時(shí),重新設(shè)計(jì)已經(jīng)成為一個(gè)無法完成的任務(wù)。設(shè)計(jì)者程序員已經(jīng)無法回到幾個(gè)月前推翻紙上談兵的錯(cuò)誤設(shè)計(jì),重新用新的方式進(jìn)行代碼編寫組合。測(cè)試為先/測(cè)試驅(qū)動(dòng)和瀑布型軟件開發(fā)不同是:


測(cè)試模擬用戶的使用組件的應(yīng)用方式,為開發(fā)者提供解決方案;
測(cè)試驅(qū)使開發(fā)者開發(fā)可以測(cè)試的部件;
及早測(cè)試,盡快排除設(shè)計(jì)中的各種微小問題;
測(cè)試為開發(fā)者提供質(zhì)保底線,每次的部件更改都能利用測(cè)試來檢測(cè)修改后質(zhì)量。

  以上這些都是我以前重復(fù)過的。這些說的容易但是想像起來是比較難一點(diǎn)。我下面所要談到的案例并不是教科書里的完美案例,所謂的完美案例是完全可以自動(dòng)化,完全可以進(jìn)行單元測(cè)試的程序組件。舉個(gè)例子說,想像你要設(shè)計(jì)一個(gè)類來代表“復(fù)數(shù)”(imaginary number),這樣一個(gè)類是可以完全進(jìn)行自動(dòng)化單元測(cè)試。這種情況只能算得上百分之五十的現(xiàn)實(shí)情況,在其他百分之五十的狀況下,一些手動(dòng)測(cè)試和一些自動(dòng)化測(cè)試都是必要的。還有很多情況下,手動(dòng)測(cè)試是的選擇。半自動(dòng)和手動(dòng)測(cè)試并不代表整個(gè)開發(fā)不算作測(cè)試驅(qū)動(dòng)開發(fā)。測(cè)試驅(qū)動(dòng)的多數(shù)人都會(huì)說手動(dòng)測(cè)試和半自動(dòng)化測(cè)試并不能代表團(tuán)隊(duì)在進(jìn)行測(cè)試驅(qū)動(dòng)的開法。我覺得這種說法是偏見,只要測(cè)試組和開發(fā)組能夠配合,盡可能地在早時(shí)間將用戶需求確定后,讓測(cè)試組開始針對(duì)用戶需求,設(shè)計(jì)思路進(jìn)行測(cè)試用例設(shè)計(jì),開發(fā)和測(cè)試能同時(shí)進(jìn)行,開發(fā)出的部件能夠迅速進(jìn)行測(cè)試,測(cè)試用例能夠經(jīng)常地運(yùn)行確保開發(fā)的質(zhì)量不受變化的影響。這是測(cè)試為先/測(cè)試驅(qū)動(dòng)的開發(fā)。


本文的案例簡(jiǎn)介

  用來演示測(cè)試為先/測(cè)試驅(qū)動(dòng)的開發(fā),我將使用我近設(shè)計(jì)的一個(gè)將應(yīng)用程序圖標(biāo)加入System Tray里的類。然后在應(yīng)用程序退出后,自動(dòng)將圖標(biāo)從System Tray里刪除。這樣的類,你如果知道Windows系統(tǒng)對(duì)System Tray里的圖標(biāo)管理,知道設(shè)計(jì)這么一個(gè)類的自動(dòng)化測(cè)試并不簡(jiǎn)單。我覺得這種和圖形界面打交道的類,也沒有必要地進(jìn)行自動(dòng)化測(cè)試。所以我對(duì)這個(gè)類的測(cè)試驅(qū)動(dòng)采取手工測(cè)試為主的測(cè)試,以測(cè)試者甚至開法者本身用用戶的需求,先用例程作為基礎(chǔ),來設(shè)計(jì)圖標(biāo)管理類的單元測(cè)試。

案例的用戶需求

  我是這個(gè)類的用戶,對(duì)于我要設(shè)計(jì)的程序,我的使用是很簡(jiǎn)單的。下面的列表是我的需求:

System Tray里的應(yīng)用圖標(biāo)的數(shù)據(jù)管理和圖標(biāo)的加入刪除都由類對(duì)象來進(jìn)行;
類對(duì)象能夠設(shè)定視窗柄;
類對(duì)象能夠設(shè)定圖標(biāo)的獨(dú)特ID;
類對(duì)象能夠設(shè)定圖標(biāo)對(duì)系統(tǒng)信息處理的消息ID;
類對(duì)象能夠設(shè)定圖標(biāo)在鼠標(biāo)指向后能夠顯示有關(guān)程序的信息(程序的名稱,設(shè)計(jì)公司和其他信息);

類對(duì)象必須在調(diào)用者的指示下將圖標(biāo)放入System Tray。
類對(duì)象必須處理讓調(diào)試者能夠刪除System Tray里的圖標(biāo)。
類對(duì)象在自我摧毀的時(shí)候自動(dòng)刪除System Tray里的圖標(biāo)。

  這些用戶需求是我要設(shè)計(jì)使用案例,在敏捷中,這些案例是一個(gè)個(gè)故事。我在設(shè)計(jì)每一個(gè)故事的編碼之前先設(shè)計(jì)一個(gè)測(cè)試案例。每個(gè)測(cè)試案例都在設(shè)計(jì)完成之前會(huì)運(yùn)行失敗。設(shè)計(jì)完成后,這些測(cè)試案例才能順利運(yùn)行。

  為了調(diào)試圖標(biāo)的加入和刪除都能正確運(yùn)行,我決定使用一個(gè)簡(jiǎn)單的Win32視窗程序來作為我的單元測(cè)試溫床,我的測(cè)試是手動(dòng)測(cè)試。我的目標(biāo)是用單元測(cè)試來盡可能地覆蓋我設(shè)計(jì)的代碼面積。第一步我設(shè)計(jì)了以下的單元測(cè)試:

void UnitTestCase0(HWND hWnd, HICON handleIcon)
{
// a normal core functionality test.
gSysTrayIcon.SetTrayIconID(11200);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);

gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  這是一個(gè)很簡(jiǎn)單的函數(shù),我假設(shè)我有一個(gè)全局變量叫g(shù)SysTrayIcon。它是一個(gè)類對(duì)象;它有至少六個(gè)函數(shù);它的五個(gè)函數(shù)是數(shù)據(jù)設(shè)定函數(shù);它的后一個(gè)函數(shù)是讓調(diào)用者告訴它把圖像加入System Tray。根據(jù)我自己設(shè)計(jì)的單元測(cè)試案例,我設(shè)計(jì)了以下的類:

#ifndef SYS_TRAY_ICON_H_
#define SYS_TRAY_ICON_H_

#include "shellapi.h"

class SysTrayIcon
{
private:
NOTIFYICONDATA niData;

public:
SysTrayIcon();
~SysTrayIcon();

void SetTrayIconID(UINT iconID);
void SetNotifyWindow(HWND hWnd);
void SetTrayIcon(HICON iconHandle);
void SetTrayIconTip(LPCTSTR szMsg);
void SetTrayIconWmMsg(UINT wmMsg);

BOOL AddIconToSysTray();
BOOL DeleteIconFromSysTray();

};

#endif

  我的類成員設(shè)計(jì)如下,這里面有很多我無意中犯下的錯(cuò)誤,也有我故意設(shè)置的錯(cuò)誤,后面我用單元測(cè)試一點(diǎn)點(diǎn)地查找出一些常見的問題。為了順利通過我上面的單元測(cè)試,首先看看我的設(shè)計(jì)初稿: #include "StdAfx.h"

#include "SysTrayIcon.h"
#include <string.h>


SysTrayIcon::SysTrayIcon()
{
ZeroMemory(&niData, sizeof(NOTIFYICONDATA));
niData.cbSize = (DWORD)sizeof(NOTIFYICONDATA);
niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
}

SysTrayIcon::~SysTrayIcon()
{
DeleteIconFromSysTray();
}

void SysTrayIcon::SetTrayIconID(UINT iconID)
{
niData.uID = iconID;
}

void SysTrayIcon::SetNotifyWindow(HWND hWnd)
{
niData.hWnd = hWnd;
}

void SysTrayIcon::SetTrayIcon(HICON iconHandle)
{
niData.hIcon = iconHandle;
}

void SysTrayIcon::SetTrayIconWmMsg(UINT wmMsg)
{
niData.uCallbackMessage = wmMsg;
}

void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
{
_tcscpy(niData.szTip, szMsg);
}

BOOL SysTrayIcon::AddIconToSysTray()
{
Shell_NotifyIcon(NIM_ADD, &niData);
return TRUE;
}

BOOL SysTrayIcon::DeleteIconFromSysTray()
{
return Shell_NotifyIcon(NIM_DELETE, &niData);
}

  敏捷的宗旨是,在短的時(shí)間內(nèi)為客戶提供完整的設(shè)計(jì),讓客戶能夠看到期待的價(jià)值,讓客戶能迅速反饋,并把反饋意見轉(zhuǎn)變?yōu)樵O(shè)計(jì)改進(jìn)。我以上的代碼給我自己提供一個(gè)可以測(cè)試的機(jī)會(huì)。我用我的測(cè)試案例來實(shí)踐我的設(shè)計(jì),測(cè)試程序是一個(gè)SDI視窗程序。程序運(yùn)行開始先把一個(gè)圖標(biāo)放入System Tray,然后,用戶可以按在程序的縮小按鈕上,程序會(huì)消失,但是System Tray里的程序圖標(biāo)。用戶用鼠標(biāo)左鍵雙擊System Tray里的程序圖標(biāo),程序視窗會(huì)重新出現(xiàn)在桌面上。用戶把鼠標(biāo)光標(biāo)移到System Tray里的程序圖標(biāo)上,一秒鐘后會(huì)一個(gè)提示標(biāo)題出現(xiàn),顯示程序的名稱。當(dāng)我關(guān)閉程序視窗,視窗消失,System Tray里的程序圖標(biāo)也一并消失。這是我的第一個(gè)測(cè)試。這個(gè)測(cè)試案例運(yùn)行,不會(huì)出現(xiàn)任何問題。

  我寫的第一個(gè)案例是開發(fā)者通常會(huì)做的測(cè)試,一個(gè)簡(jiǎn)單的案例保證設(shè)計(jì)到達(dá)基本的用戶需求。作為認(rèn)真的開發(fā)者,和有專業(yè)意識(shí)的QA,這樣簡(jiǎn)單的測(cè)試根本不夠。各種各樣的邊界問題會(huì)通過設(shè)計(jì)的空隙造成程序運(yùn)行異常。我設(shè)計(jì)了另一個(gè)測(cè)試邊際問題的測(cè)試,代碼如下:

void UnitTestCase1(HWND hWnd, HICON handleIcon)

{
gSysTrayIcon.AddIconToSysTray();
}

 

  這個(gè)案例其實(shí)很簡(jiǎn)單。假設(shè)我建立了一個(gè)gSysTrayIcon,但是我不對(duì)其做任何初始化設(shè)定。那會(huì)出現(xiàn)什么問題?我運(yùn)行一下這個(gè)案例,結(jié)果我馬上發(fā)現(xiàn)了兩個(gè)問題,一是ystem Tray里的程序圖標(biāo)是一個(gè)空格。接著我把標(biāo)光標(biāo)移到System Tray里的程序圖標(biāo)的位置上,馬上那個(gè)位置被其他圖標(biāo)給占據(jù)了。這些行為都是不對(duì)的。

  仔細(xì)看看我的設(shè)計(jì),我在調(diào)用AddIconToSysTray()之前,沒有調(diào)用一些重要的對(duì)象處理,這三個(gè):SetNotifyWindow(HWND hWnd),SetTrayIcon(HICON iconHandle),和SetTrayIconWmMsg(UINT wmMsg)。所以我的案例會(huì)出現(xiàn)異常。在現(xiàn)實(shí)中,測(cè)試或者開發(fā)者自己都能運(yùn)用自己的經(jīng)驗(yàn)和知識(shí)來判斷這些邊界的問題,然后用單元測(cè)試來鑒別設(shè)計(jì)在處理這些問題的能力。現(xiàn)在我已經(jīng)了解到我的設(shè)計(jì)有毛病,要想辦法解決。首先看看SetNotifyWindow(HWND hWnd),這個(gè)函數(shù)的邊界是HWND參數(shù)不能是NULL(或是0)。如果這種情況出現(xiàn),我應(yīng)該如何處理?我的解決是用扔出異常。我要為以上的單元測(cè)試進(jìn)行一點(diǎn)改變。下面是我的修改:

void UnitTestCase1(HWND hWnd, HICON handleIcon)

{
// without any initailization.
try
{
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
}

  然后我再更改我的設(shè)計(jì),促使我的設(shè)計(jì)在處理錯(cuò)誤輸入時(shí)會(huì)拋出異常:

void SysTrayIcon::SetNotifyWindow(HWND hWnd)
{
if (hWnd == NULL)
{
// throw excepttion
throw AppException(_T("The handle of the window is invalid."));
}
niData.hWnd = hWnd;
}

  我再運(yùn)行一下我的案例,結(jié)果還是不行,原來的毛病一點(diǎn)都沒有改變。我再看看我的測(cè)試案例,結(jié)果發(fā)現(xiàn)我的修改并沒有解除我所面對(duì)的問題。在我調(diào)用AddIconToSysTray()之前,我根本沒有調(diào)用SetNotifyWindow,所以我的測(cè)試案例根本沒有解決我的問題。我要修改的是AddIconToSysTray()。下面是我的修改:

BOOL SysTrayIcon::AddIconToSysTray()
{
if (niData.hWnd == NULL)
{
throw AppException(_T("The handle of the window is invalid."));

}
else if (niData.hIcon == NULL)
{
throw AppException(_T("The handle of the icon is invalid."));
}
else if (niData.uCallbackMessage == 0)
{
throw AppException(_T("The callback message ID is invalid."));
}


BOOL retVal = Shell_NotifyIcon(NIM_ADD, &niData);
return retVal;
}

  修改后運(yùn)行一下,我的程序輸出了異常信息提示,當(dāng)我選擇提示的“OK”按鈕后,程序沒有在System Tray里添加程序圖標(biāo)。我為了測(cè)試剩下兩個(gè)判斷分支,設(shè)計(jì)了兩個(gè)案例里,加上上一個(gè)案例我有三個(gè):

void UnitTestCase1(HWND hWnd, HICON handleIcon)
{
// without any initailization.
try
{
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
}

void UnitTestCase2(HWND hWnd, HICON handleIcon)
{
// without any initailization on ICON handle.
try
{
gSysTrayIcon.SetNotifyWindow(hWnd);

if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
}

void UnitTestCase3(HWND hWnd, HICON handleIcon)
{
// without any initailization for message callback ID.
try
{
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
}

  還有什么可以測(cè)試?首先,NOTIFYICONDATA::uID的值有沒有限度?我們可以試試0和-1(-1應(yīng)該是32位正值整數(shù)的大值),對(duì)程序的影響也不大:


void UnitTestCase4(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(0);

gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

void UnitTestCase5(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(-1);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  后是鼠標(biāo)滑到System Tray的圖標(biāo)時(shí)要顯示的字符串。NOTIFYICONDATA::szTip大容量應(yīng)該是64個(gè)字節(jié)。我用以下的測(cè)試案例來測(cè)試我的設(shè)計(jì):

void UnitTestCase6(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(11200);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
gSysTrayIcon.SetTrayIconTip(_T("kdhfhdfjhdsfhdsjfhdsjhfjdshfjdshfjdshjfhdsjfsjdhfjdshjs"
"hdfhdsfjhdsjfhsdjfhdshfhdsjfhdsfhsdjhfsdjhfjdshfjhdsfjhdsfhsdhfsjdhfjshdjfhdsfjhsdj"
"dfhhdsjfhdjshfjdhfjdhfdhfjhdsfjhdjhfjdsfhjsadhhdskfhadskfhdskjfhkdsjfhkdsjhfkjadshf"
"kjasdhkfhadskfhdskjhfkadsjhfkfashkfhaskdhfkadsfhkdsafhkdsjhfkdsahfkdshkjfhdaskhkads"
"hfkdshfkjdhfkjdhfkdshfiohirhu'prhpiurhf"));
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  讓我吃驚的是,程序并沒有出現(xiàn)任何異常。只是64字節(jié)以后的字節(jié)都被忽略了。這種現(xiàn)象并不代表我的設(shè)計(jì)沒有問題,我的原有設(shè)計(jì)是這樣的:

void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
{
_tcscpy(niData.szTip, szMsg);
}

  _tcscpy是個(gè)很不安全的函數(shù),它不檢測(cè)接受緩沖的容量,所以,原緩沖的容量可以比接受緩沖的容量大,這樣的代碼很容易出現(xiàn)buffer overflow。所以,我們必須在字符串拷貝的時(shí)候檢測(cè)兩個(gè)緩沖的容量大小,接受緩沖的容量必須比原緩沖的容量要大。但是在我們現(xiàn)在所面對(duì)的情況下,這種情況我們采用另一種解決方式比較容易,是保證字符串的拷貝不超過一定的數(shù)量。而且我們采用一個(gè)比較安全的拷貝函數(shù)。首先我改變了原有的測(cè)試用例:


void UnitTestCase6(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(11200);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
try
{
gSysTrayIcon.SetTrayIconTip(_T("kdhfhdfjhdsfhdsjfhdsjhfjdshfjdshfjdshjfhdsjfsjdhfjdshjs"
"hdfhdsfjhdsjfhsdjfhdshfhdsjfhdsfhsdjhfsdjhfjdshfjhdsfjhdsfhsdhfsjdhfjshdjfhdsfjhsdj"
"dfhhdsjfhdjshfjdhfjdhfdhfjhdsfjhdjhfjdsfhjsadhhdskfhadskfhdskjfhkdsjfhkdsjhfkjadshf"
"kjasdhkfhadskfhdskjhfkadsjhfkfashkfhaskdhfkadsfhkdsafhkdsjhfkdsahfkdshkjfhdaskhkads"
"hfkdshfkjdhfkjdhfkdshfiohirhu'prhpiurhf"));
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  用我原來的設(shè)計(jì)來測(cè)試以上的測(cè)試用例,什么都不會(huì)發(fā)生。說明我的原有設(shè)計(jì)要改變一些:

void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
{
HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
if (FAILED(hr))
{
// throw exception
throw AppException(_T("Invalid tip string"));
}
}

  使用更新的代碼,再運(yùn)行我的測(cè)試用例,讓我意外的是,測(cè)試用例竟然報(bào)道程序出錯(cuò)。說明我的更改是正確的。我的希望是如果原緩沖的容量太大,程序只拷貝接受緩沖容量所能承受的字符串量。這樣我的測(cè)試用例應(yīng)該不會(huì)報(bào)錯(cuò)。是什么造成這個(gè)問題?仔細(xì)查詢一下有關(guān)StringCchCopyN()的說明,發(fā)現(xiàn)我的問題在哪里了。如果原緩沖的容量太大,程序只拷貝接受緩沖容量所能承受的字符串量,StringCchCopyN()的返回值是STRSAFE_E_INSUFFICIENT_BUFFER,而不是S_OK。所以我的源代碼必須進(jìn)行一定的變化:

void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
{
HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
if (FAILED(hr))
{
if (hr != STRSAFE_E_INSUFFICIENT_BUFFER)
{
// throw exception
throw AppException(_T("Invalid tip string"));
}
}
}

  再次運(yùn)行上面的測(cè)試用例,我不再看見原有的錯(cuò)誤消息。你可以看出我到現(xiàn)在,一直在使用我的測(cè)試和我對(duì)我要設(shè)計(jì)的代碼的結(jié)構(gòu)的熟悉來指導(dǎo)我的設(shè)計(jì)。白盒測(cè)試不僅能提供我所需要的程序質(zhì)量檢測(cè),同時(shí)也指引我的設(shè)計(jì)方向。再試試幾個(gè)其他的案例,這是一個(gè)典型的案例,如果我用NULL作為原緩沖,那會(huì)出現(xiàn)什么問題:

void UnitTestCase8(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(11200);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
gSysTrayIcon.SetTrayIconTip(NULL);
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  試試之后的結(jié)果是整個(gè)測(cè)試程序垮掉。我們又發(fā)現(xiàn)了一個(gè)問題。我先更改一下我的測(cè)試用例:

void UnitTestCase8(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(11200);
gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(handleIcon);
try
{
gSysTrayIcon.SetTrayIconTip(NULL);
}
catch(const AppException& e)
{
::MessageBox(hWnd, e.ToString(), _T("Error:"), MB_OK);
}
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  然后再修改我的設(shè)計(jì):

void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg)
{
if (szMsg == NULL)
{
throw AppException(_T("Tip string pointer cannot be NULL."));
}

HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63);
if (FAILED(hr))
{
if (hr != STRSAFE_E_INSUFFICIENT_BUFFER)
{
// throw exception
throw AppException(_T("Invalid tip string"));
}
}
}

  運(yùn)行后看到我所希望看到的錯(cuò)誤信息。這樣我又防止了一個(gè)毛病漏到測(cè)試組的手上。

  后在總結(jié)之前,說說圖標(biāo)柄輸入如果是NULL的情況,我們可以進(jìn)行一些改進(jìn),如果用戶在調(diào)用SysTrayIcon::SetTrayIcon(HICON iconHandle)輸入非法值NULL,解決方法不一定是要直接拋出異常。我們可以讓系統(tǒng)幫助設(shè)定一個(gè)默認(rèn)圖標(biāo)柄。首先我們?cè)貱opy & paste制作一個(gè)新的單元測(cè)試:

void UnitTestCase9(HWND hWnd, HICON handleIcon)
{
// What happen to have Icon ID to be 0.
gSysTrayIcon.SetTrayIconID(15923);

gSysTrayIcon.SetNotifyWindow(hWnd);
gSysTrayIcon.SetTrayIcon(NULL);
gSysTrayIcon.SetTrayIconTip(_T("SysTrayIcon"));
gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS);
if (!gSysTrayIcon.AddIconToSysTray())
{
::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK);
return;
}
}

  運(yùn)行以上的單元測(cè)試,我馬上看見原有的異常被拋出,表明我的測(cè)試失敗了。我的意圖是如果圖標(biāo)柄的設(shè)置是NULL,那么,我讓系統(tǒng)找到一個(gè)默認(rèn)的圖標(biāo),并用它作為程序在SystemTray里的圖標(biāo)。我對(duì)代碼進(jìn)行以下修改:

BOOL SysTrayIcon::AddIconToSysTray()
{
if (niData.hWnd == NULL)
{
throw AppException(_T("The handle of the window is invalid."));
}
else if (niData.hIcon == NULL)
{
HICON defaultIcoHdl = ::LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
if (defaultIcoHdl != NULL)
{
niData.hIcon = defaultIcoHdl;
}
else
{
throw AppException(_T("The handle of the icon is invalid."));
}
}
else if (niData.uCallbackMessage == 0)
{
throw AppException(_T("The callback message ID is invalid."));
}

BOOL retVal = Shell_NotifyIcon(NIM_ADD, &niData);
return retVal;
}

  運(yùn)行我的單元測(cè)試,結(jié)果沒有出錯(cuò),但是我的第二個(gè)單元測(cè)試原來設(shè)計(jì)為,如果圖標(biāo)柄的設(shè)置是NULL,拋出異常,現(xiàn)在這個(gè)使用案例已經(jīng)沒有意義了,所以第二個(gè)單元測(cè)試可以被刪掉了。

總結(jié)

  我的這篇文章完整(并非完美)地展示了一個(gè)簡(jiǎn)單的測(cè)試為先,測(cè)試驅(qū)動(dòng)的開發(fā)案例。設(shè)計(jì)過程中,我用測(cè)試案例來主導(dǎo)我的設(shè)計(jì),只有簡(jiǎn)單的設(shè)計(jì)來實(shí)現(xiàn)我的需求。我只在更改錯(cuò)誤的情況下增加功能,而不是隨便憑著自己的想像來增加我不需要的功能。我在測(cè)試中找出了不少我問題,而且都是在開發(fā)過程中發(fā)現(xiàn)的問題,也是說在開法的基本階段測(cè)試開始進(jìn)行了,而且很多問題在開發(fā)初期被檢測(cè)出來并修改好,盡早測(cè)試,可以為后面的開發(fā)減少很多不必要的困難。

  我這個(gè)案例不是一個(gè)完美的案例。現(xiàn)實(shí)中,能夠完美展示單元測(cè)試的好處的完美案例是不存在的,所有我見過的完美案例都是在教科書里出現(xiàn)的。這些案例有時(shí)給人的感覺是不真實(shí),也展示出這些案例的局限性。我的案例在很大程度上依賴手動(dòng)化測(cè)試,這有時(shí)是違反敏捷開發(fā)的用意的。在敏捷開發(fā)中,自動(dòng)化單元測(cè)試和接受性測(cè)試是非常重要的。我敢說很多進(jìn)行敏捷開發(fā)的專家都會(huì)說我的案例算不上敏捷開發(fā)。我對(duì)這一觀點(diǎn)只同意到一定的程度,軟件開發(fā)是個(gè)人與人互動(dòng)的社會(huì)活動(dòng),開發(fā)者在手動(dòng)測(cè)試上所花時(shí)間過多的話,要將這樣的任務(wù)推給QA,有能力的QA應(yīng)該可以和開發(fā)者一起考慮什么樣的接受性測(cè)試和單元測(cè)試能夠幫助整個(gè)團(tuán)隊(duì)提交更好的產(chǎn)品。

  我這個(gè)案例同時(shí)也說明,用戶界面的設(shè)計(jì)也能通過單元測(cè)試來進(jìn)行。這樣的測(cè)試不僅僅是開發(fā)者自己進(jìn)行,有能力的QA可以和開發(fā)者一起進(jìn)行接受性測(cè)試,QA可以享受一下開發(fā)的樂趣。同時(shí)可以和開發(fā)者一起合作進(jìn)行質(zhì)量監(jiān)控,這樣雙方不會(huì)因?yàn)楦?jìng)爭(zhēng)而感受雙方的相互威脅,QA幫助開發(fā)者及早進(jìn)行測(cè)試,從而建立友好的合作關(guān)系。

軟件測(cè)試工具 | 聯(lián)系我們 | 投訴建議 | 誠(chéng)聘英才 | 申請(qǐng)使用列表 | 網(wǎng)站地圖
滬ICP備07036474 2003-2017 版權(quán)所有 上海澤眾軟件科技有限公司 Shanghai ZeZhong Software Co.,Ltd