测试为先/测试驱动案例分析

发表于:2007-8-30 14:37  作者:Richard Sun   来源:Richard Sun的博客

字体: | 上一篇 | 下一篇 |我要投稿 | 推荐标签:

  还有什么可以测试?首先,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"));
      }
   }
}

  再次运行上面的测试用例,我不再看见原有的错误消息。你可以看出我到现在,一直在使用我的测试和我对我要设计的代码的结构的熟悉来指导我的设计。白盒测试不仅能提供我所需要的程序质量检测,同时也指引我的设计方向。再试试几个其他的案例,这就是一个典型的案例,如果我用NULL作为原缓冲,那会出现什么问题:

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;
   }
}

  运行以上的单元测试,我马上看见原有的异常被抛出,表明我的测试失败了。我的意图是如果图标柄的设置是NULL,那么,我就让系统找到一个默认的图标,并用它作为程序在SystemTray里的图标。我对代码进行以下修改:

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;
}

  运行我的单元测试,结果就没有出错,但是我的第二个单元测试原来设计为,如果图标柄的设置是NULL,就抛出异常,现在这个使用案例已经没有意义了,所以第二个单元测试就可以被删掉了。

总结
  我的这篇文章完整(并非完美)地展示了一个简单的测试为先,测试驱动的开发案例。设计过程中,我用测试案例来主导我的设计,只有最简单的设计来实现我的需求。我只在更改错误的情况下增加功能,而不是随便凭着自己的想像来增加我不需要的功能。我在测试中找出了不少我问题,而且都是在开发过程中发现的问题,也就是说在开法的最基本阶段测试就开始进行了,而且很多问题在开发初期就被检测出来并修改好,尽早测试,可以为后面的开发减少很多不必要的困难。

  我这个案例不是一个完美的案例。现实中,能够完美展示单元测试的好处的完美案例是不存在的,所有我见过的完美案例都是在教科书里出现的。这些案例有时给人的感觉是不真实,也展示出这些案例的局限性。我的案例在很大程度上依赖手动化测试,这有时是违反敏捷开发的用意的。在敏捷开发中,自动化单元测试和接受性测试是非常重要的。我敢说很多进行敏捷开发的专家都会说我的案例算不上敏捷开发。我对这一观点只同意到一定的程度,软件开发是个人与人互动的社会活动,开发者在手动测试上所花时间过多的话,就要将这样的任务推给QA,有能力的QA应该可以和开发者一起考虑什么样的接受性测试和单元测试能够帮助整个团队提交更好的产品。

  我这个案例同时也说明,用户界面的设计也能通过单元测试来进行。这样的测试不仅仅是开发者自己进行,有能力的QA可以和开发者一起进行接受性测试,QA可以享受一下开发的乐趣。同时可以和开发者一起合作进行质量监控,这样双方不会因为竞争而感受双方的相互威胁,QA帮助开发者及早进行测试,从而建立友好的合作关系。


33/3<123

评 论

论坛新帖



建议使用IE 6.0以上浏览器,800×600以上分辨率,法律顾问:上海漕溪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2022, 沪ICP备05003035号
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪公网安备 31010102002173号

51Testing官方微信

51Testing官方微博

扫一扫 测试知识全知道