进行测试为先测试驱动的程序设计是确保敏捷开发顺进行的有效措施。这篇案例将为读者提供详细的开发历程,来分析测试为先测试驱动的程序设计的过程。本文的重点:
*简要重复叙述一下测试为先/测试驱动得好处。
*简要介绍一下案例中的项目。
*没有利用测试为先/测试驱动设计的单元代码是什么样的?
*没有利用测试为先/测试驱动设计的单元代码里有什么样的问题?
*针对单元代码设计的手动单元测试。
*查找出毛病后的改进代码。
测试为先/测试驱动的好处
传统的瀑布型软件开发是先从客户那里获得需求,然后进行纸上谈兵的设计,接着是程序源码写作构建,最后才是测试者对质量进行检评。从需求的分析到最后的测试,两者的相隔往往有好几个月。等到测试发现结构性问题时,重新设计已经成为一个无法完成的任务。设计者程序员已经无法回到几个月前推翻纸上谈兵的错误设计,重新用新的方式进行代码编写组合。测试为先/测试驱动和瀑布型软件开发不同是:
测试模拟用户的使用组件的应用方式,为开发者提供解决方案;
测试驱使开发者开发可以测试的部件;
及早测试,尽快排除设计中的各种微小问题;
测试为开发者提供质保底线,每次的部件更改都能利用测试来检测修改后质量。
以上这些都是我以前重复过的。这些说的容易但是想像起来是比较难一点。我下面所要谈到的案例并不是教科书里的完美案例,所谓的完美案例是完全可以自动化,完全可以进行单元测试的程序组件。举个例子说,想像你要设计一个类来代表“复数”(imaginary
number),这样一个类是可以完全进行自动化单元测试。这种情况只能算得上百分之五十的现实情况,在其他百分之五十的状况下,一些手动测试和一些自动化测试都是必要的。还有很多情况下,手动测试是唯一的选择。半自动和手动测试并不代表整个开发不算作测试驱动开发。测试驱动的多数人都会说手动测试和半自动化测试并不能代表团队在进行测试驱动的开法。我觉得这种说法是偏见,只要测试组和开发组能够配合,尽可能地在最早时间将用户需求确定后,让测试组开始针对用户需求,设计思路进行测试用例设计,开发和测试能同时进行,开发出的部件能够迅速进行测试,测试用例能够经常地运行确保开发的质量不受变化的影响。这就是测试为先/测试驱动的开发。
本文的案例简介
用来演示测试为先/测试驱动的开发,我将使用我最近设计的一个将应用程序图标加入System Tray里的类。然后在应用程序退出后,自动将图标从System
Tray里删除。这样的类,你如果知道Windows系统对System Tray里的图标管理,就知道设计这么一个类的自动化测试并不简单。我觉得这种和图形界面打交道的类,也没有必要100%地进行自动化测试。所以我对这个类的测试驱动采取手工测试为主的测试,以测试者甚至开法者本身用用户的需求,先用例程作为基础,来设计图标管理类的单元测试。
案例的用户需求
我是这个类的唯一用户,对于我要设计的程序,我的使用是很简单的。下面的列表就是我的需求:
System Tray里的应用图标的数据管理和图标的加入删除都由类对象来进行;
类对象能够设定视窗柄;
类对象能够设定图标的独特ID;
类对象能够设定图标对系统信息处理的消息ID;
类对象能够设定图标在鼠标指向后能够显示有关程序的信息(程序的名称,设计公司和其他信息);
类对象必须在调用者的指示下将图标放入System Tray。
类对象必须处理让调试者能够删除System Tray里的图标。
类对象在自我摧毁的时候自动删除System Tray里的图标。
这些用户需求就是我要设计使用案例,在敏捷中,这些案例就是一个个故事。我在设计每一个故事的编码之前就先设计一个测试案例。每个测试案例都在设计完成之前会运行失败。设计完成后,这些测试案例才能顺利运行。
为了调试图标的加入和删除都能正确运行,我决定使用一个简单的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;
}
} |
这是一个很简单的函数,我假设我有一个全局变量叫gSysTrayIcon。它是一个类对象;它有至少六个函数;它的五个函数是数据设定函数;它的最后一个函数是让调用者告诉它把图像加入System
Tray。根据我自己设计的单元测试案例,我设计了以下的类:
#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();
} |
这个案例其实很简单。假设我建立了一个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);
}
}
还有什么可以测试?首先,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;
}
} |
最后是鼠标滑到System Tray的图标时要显示的字符串。NOTIFYICONDATA::szTip最大容量应该是64个字节。我用以下的测试案例来测试我的设计:
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;
}
} |
让我吃惊的是,程序并没有出现任何异常。只是64字节以后的字节都被忽略了。这种现象并不代表我的设计没有问题,我的原有设计是这样的:
void SysTrayIcon::SetTrayIconTip(LPCTSTR
szMsg)
{
_tcscpy(niData.szTip, szMsg);
} |
_tcscpy是个很不安全的函数,它不检测接受缓冲的容量,所以,原缓冲的容量可以比接受缓冲的容量大,这样的代码很容易出现buffer
overflow。所以,我们必须在字符串拷贝的时候检测两个缓冲的容量大小,接受缓冲的容量必须比原缓冲的容量要大。但是在我们现在所面对的情况下,这种情况我们采用另一种解决方式比较容易,就是保证字符串的拷贝不超过一定的数量。而且我们采用一个比较安全的拷贝函数。首先我改变了原有的测试用例:
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"));
}
} |
使用更新的代码,再运行我的测试用例,让我意外的是,测试用例竟然报道程序出错。说明我的更改是正确的。我的希望是如果原缓冲的容量太大,程序只拷贝接受缓冲容量所能承受的字符串量。这样我的测试用例应该不会报错。是什么造成这个问题?仔细查询一下有关StringCchCopyN()的说明,就发现我的问题在哪里了。如果原缓冲的容量太大,程序只拷贝接受缓冲容量所能承受的字符串量,StringCchCopyN()的返回值是STRSAFE_E_INSUFFICIENT_BUFFER,而不是S_OK。所以我的源代码必须进行一定的变化:
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"));
}
}
} |
附录C:单元测试调试程序
// 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;
}
|