#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); } |
敏捷的宗旨是,在最短的时间内为客户提供完整的设计,让客户能够看到期待的价值,让客户能迅速反馈,并把反馈意见转变为设计改进。我以上的代码给我自己提供一个可以测试的机会。我用我的测试案例来实践我的设计,测试程序是一个SDI视窗程序。程序运行开始先把一个图标放入System Tray,然后,用户可以按在程序的缩小按钮上,程序会消失,但是System Tray里的程序图标。用户用鼠标左键双击System Tray里的程序图标,程序视窗会重新出现在桌面上。用户把鼠标光标移到System Tray里的程序图标上,一秒钟后就会一个提示标题出现,显示程序的名称。当我关闭程序视窗,视窗消失,System Tray里的程序图标也一并消失。这就是我的第一个测试。这个测试案例运行,不会出现任何问题。
我写的第一个案例是开发者通常会做的测试,一个简单的案例保证设计到达最基本的用户需求。作为认真的开发者,和有专业意识的QA,这样简单的测试根本不够。各种各样的边界问题会通过设计的空隙造成程序运行异常。我就设计了另一个测试边际问题的测试,代码如下:
void UnitTestCase1(HWND hWnd, HICON handleIcon) { gSysTrayIcon.AddIconToSysTray(); } |
这个案例其实很简单。假设我建立了一个gSysTrayIcon,但是我不对其做任何初始化设定。那会出现什么问题?我运行一下这个案例,结果我马上发现了两个问题,一是ystem Tray里的程序图标是一个空格。接着我把标光标移到System Tray里的程序图标的位置上,马上那个位置就被其他图标给占据了。这些行为都是不对的。
仔细看看我的设计,我在调用AddIconToSysTray()之前,没有调用一些重要的对象处理,这三个:SetNotifyWindow(HWND hWnd),SetTrayIcon(HICON iconHandle),和SetTrayIconWmMsg(UINT wmMsg)。所以我的案例会出现异常。在现实中,测试或者开发者自己都能运用自己的经验和知识来判断这些边界的问题,然后用单元测试来鉴别设计在处理这些问题的能力。现在我已经了解到我的设计有毛病,就要想办法解决。首先看看SetNotifyWindow(HWND hWnd),这个函数的边界是HWND参数不能是NULL(或是0)。如果这种情况出现,我应该如何处理?我的解决是用扔出异常。我就要为以上的单元测试进行一点改变。下面是我的修改:
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 SysTrayIcon::SetNotifyWindow(HWND hWnd) { if (hWnd == NULL) { // throw excepttion throw AppException(_T("The handle of the window is invalid.")); } niData.hWnd = hWnd; } |
我再运行一下我的案例,结果还是不行,原来的毛病一点都没有改变。我再看看我的测试案例,结果发现我的修改并没有解除我所面对的问题。在我调用AddIconToSysTray()之前,我根本没有调用SetNotifyWindow,所以我的测试案例根本没有解决我的问题。我要修改的是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; } |
修改后运行一下,我的程序输出了异常信息提示,当我选择提示的“OK”按钮后,程序没有在System Tray里添加程序图标。我为了测试剩下两个判断分支,设计了两个案例里,加上上一个案例我有三个:
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); } } |