测试为先/测试驱动案例分析
作者:Richard Sun (版权所有,严禁未经许可的转载与复制)
进行测试为先测试驱动的程序设计是确保敏捷开发顺进行的有效措施。这篇案例将为读者提供详细的开发历程,来分析测试为先测试驱动的程序设计的过程。本文的重点:
测试为先/测试驱动得好处
传统的瀑布型软件开发是先从客户那里获得需求,然后进行纸上谈兵的设计,接着是程序源码写作构建,最后才是测试者对质量进行检评。从需求的分析到最后的测试,两者的相隔往往有好几个月。等到测试发现结构性问题时,重新设计已经成为一个无法完成的任务。设计者程序员已经无法回到几个月前推翻纸上谈兵的错误设计,重新用新的方式进行代码编写组合。测试为先/测试驱动和瀑布型软件开发不同是:
以上这些都是我以前重复过的。这些说的容易但是想像起来是比较难一点。我下面所要谈到的案例并不是教科书里的完美案例,所谓的完美案例是完全可以自动化,完全可以进行单元测试的程序组件。举个例子说,想像你要设计一个类来代表“复数”(imaginary number),这样一个类是可以完全进行自动化单元测试。这种情况只能算得上百分之五十的现实情况,在其他百分之五十的状况下,一些手动测试和一些自动化测试都是必要的。还有很多情况下,手动测试是唯一的选择。半自动和手动测试并不代表整个开发不算作测试驱动开发。测试驱动的多数人都会说手动测试和半自动化测试并不能代表团队在进行测试驱动的开法。我觉得这种说法是偏见,只要测试组和开发组能够配合,尽可能地在最早时间将用户需求确定后,让测试组开始针对用户需求,设计思路进行测试用例设计,开发和测试能同时进行,开发出的部件能够迅速进行测试,测试用例能够经常地运行确保开发的质量不受变化的影响。这就是测试为先/测试驱动的开发。
本文的案例简介
用来演示测试为先/测试驱动的开发,我将使用我最近设计的一个将应用程序图标加入System Tray里的类。然后在应用程序退出后,自动将图标从System Tray里删除。这样的类,你如果知道Windows系统对System Tray里的图标管理,就知道设计这么一个类的自动化测试并不简单。我觉得这种和图形界面打交道的类,也没有必要100%地进行自动化测试。所以我对这个类的测试驱动采取手工测试为主的测试,以测试者甚至开法者本身用用户的需求,先用例程作为基础,来设计图标管理类的单元测试。
案例的用户需求
我是这个类的唯一用户,对于我要设计的程序,我的使用是很简单的。下面的列表就是我的需求:
为了调试图标的加入和删除都能正确运行,我决定使用一个简单的Win32视窗程序来作为我的单元测试温床,我的测试是手动测试。我的目标是用单元测试来尽可能地覆盖我设计的代码面积。第一步我设计了以下的单元测试:
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; } } |
#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 |
#include "StdAfx.h" #include "SysTrayIcon.h" #include 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(); } |
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; } |
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; } |
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); } } |
还有什么可以测试?首先,NOTIFYICONDATA::uID的值有没有限度?我们可以试试0和-1(-1应该是32位正值整数的最大值),对程序的影响也不大:
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; } } |
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; } } |
void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg) { _tcscpy(niData.szTip, szMsg); } |
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; } } |
void SysTrayIcon::SetTrayIconTip(LPCTSTR szMsg) { HRESULT hr = StringCchCopyN(niData.szTip, 63, szMsg, 63); if (FAILED(hr)) { // throw exception throw AppException(_T("Invalid tip string")); } } |
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")); } } } |
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; } } |
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; } } |
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")); } } } |
最后在总结之前,说说图标柄输入如果是NULL的情况,我们可以进行一些改进,如果用户在调用SysTrayIcon::SetTrayIcon(HICON iconHandle)输入非法值NULL,解决方法不一定是要直接抛出异常。我们可以让系统帮助设定一个默认图标柄。首先我们再Copy & paste制作一个新的单元测试:
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; } } |
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; } |
总结
我的这篇文章完整(并非完美)地展示了一个简单的测试为先,测试驱动的开发案例。设计过程中,我用测试案例来主导我的设计,只有最简单的设计来实现我的需求。我只在更改错误的情况下增加功能,而不是随便凭着自己的想像来增加我不需要的功能。我在测试中找出了不少我问题,而且都是在开发过程中发现的问题,也就是说在开法的最基本阶段测试就开始进行了,而且很多问题在开发初期就被检测出来并修改好,尽早测试,可以为后面的开发减少很多不必要的困难。
我这个案例不是一个完美的案例。现实中,能够完美展示单元测试的好处的完美案例是不存在的,所有我见过的完美案例都是在教科书里出现的。这些案例有时给人的感觉是不真实,也展示出这些案例的局限性。我的案例在很大程度上依赖手动化测试,这有时是违反敏捷开发的用意的。在敏捷开发中,自动化单元测试和接受性测试是非常重要的。我敢说很多进行敏捷开发的专家都会说我的案例算不上敏捷开发。我对这一观点只同意到一定的程度,软件开发是个人与人互动的社会活动,开发者在手动测试上所花时间过多的话,就要将这样的任务推给QA,有能力的QA应该可以和开发者一起考虑什么样的接受性测试和单元测试能够帮助整个团队提交更好的产品。
我这个案例同时也说明,用户界面的设计也能通过单元测试来进行。这样的测试不仅仅是开发者自己进行,有能力的QA可以和开发者一起进行接受性测试,QA可以享受一下开发的乐趣。同时可以和开发者一起合作进行质量监控,这样双方不会因为竞争而感受双方的相互威胁,QA帮助开发者及早进行测试,从而建立友好的合作关系。
附录A:测试的类头文件
#pragma once #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(); }; |
#include "StdAfx.h" #include "SysTrayIcon.h" #include "ExceptionBase.h" #include 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) { 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")); } } } 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; } BOOL SysTrayIcon::DeleteIconFromSysTray() { return Shell_NotifyIcon(NIM_DELETE, &niData); } |
// SysTrayIcon.cpp : Defines the entry point for the application. // #include "stdafx.h" #include #include "SysTrayIconTest.h" #include "..//SysTrayIcon.h" #include "..//ExceptionBase.h" #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name SysTrayIcon gSysTrayIcon; int gUnitTestIdx = -1; #define WM_TRAYICON_MSGS 10025 // Forward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); 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; } } 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); } } // No longer vallid. //void UnitTestCase2(HWND hWnd, HICON handleIcon) //{ // // without any initailization. // 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. 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); } } 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; } } 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; } } void UnitTestCase7(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("")); gSysTrayIcon.SetTrayIconWmMsg(WM_TRAYICON_MSGS); if (!gSysTrayIcon.AddIconToSysTray()) { ::MessageBox(hWnd, _T("Unable to add Icon to System Tray."), _T("Error:"), MB_OK); return; } } 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; } } 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; } } int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // TODO: Place code here. MSG msg; HACCEL hAccelTable; // Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_SYSTRAYICON, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); gUnitTestIdx = _tstoi(lpCmdLine); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_SYSTRAYICON); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // This function and its usage are only necessary if you want this code // to be compatible with Win32 systems prior to the 'RegisterClassEx' // function that was added to Windows 95. It is important to call this function // so that the application will get 'well formed' small icons associated // with it. // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_SYSTRAYICON); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = (LPCTSTR)IDC_SYSTRAYICON; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); return RegisterClassEx(&wcex); } // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); HICON iconHdl = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SYSTRAYICON)); if (iconHdl == NULL) { _tprintf(_T("Unable to load test icon.")); return FALSE; } if (gUnitTestIdx >= 0) { switch(gUnitTestIdx) { case 1: UnitTestCase1(NULL, NULL); break; // case #2 is nolonger valid. /*case 2: UnitTestCase2(hWnd, iconHdl); break;*/ case 3: UnitTestCase3(hWnd, iconHdl); break; case 4: UnitTestCase4(hWnd, iconHdl); break; case 5: UnitTestCase5(hWnd, iconHdl); break; case 6: UnitTestCase6(hWnd, iconHdl); break; case 7: UnitTestCase7(hWnd, iconHdl); break; case 8: UnitTestCase8(hWnd, iconHdl); break; case 9: UnitTestCase9(hWnd, iconHdl); break; default: UnitTestCase0(hWnd, iconHdl); break; } } // Functional Testing is done here return TRUE; } // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_TRAYICON_MSGS: switch(lParam) { case WM_LBUTTONDBLCLK: ShowWindow(hWnd, SW_RESTORE); break; } break; case WM_SYSCOMMAND: { WPARAM uCmdType = wParam; if (uCmdType == SC_MINIMIZE) { ShowWindow(hWnd, SW_HIDE); return 0; } } return DefWindowProc(hWnd, message, wParam, lParam); case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code here... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Message handler for about box. LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; } |