測試優先是 TDD 核心,但 GUI 自動化測試向來是弱項。本文透過《測試驅動開發實用指南》中的影片清單管理範例,示範如何用 CppUnit 對 MFC GUI 進行單元測試,並引入 Mock Objects 隔離外部資源依賴。

圖 1:GUI 草圖
class TestWidgets : public CppUnit::TestCase
{
CPPUNIT_TEST_SUITE(TestWidgets);
CPPUNIT_TEST(testList);
CPPUNIT_TEST(testField);
CPPUNIT_TEST(testAddButton);
CPPUNIT_TEST(testDeleteButton);
CPPUNIT_TEST_SUITE_END();
MovieListWindow* m_pWindow;
// ...
void testAddButton()
{
CButton* pBtn = m_pWindow->GetAddButton();
CPPUNIT_ASSERT(pBtn->m_hWnd);
CPPUNIT_ASSERT_EQUAL(TRUE, ::IsWindowVisible(pBtn->m_hWnd));
CString str; pBtn->GetWindowText(str);
CPPUNIT_ASSERT_EQUAL(CString("Add"), str);
}
};
測試點擊 Add 按鈕後 ListBox 是否新增項目:
void TestOperation::testAdd()
{
// 準備預期資料
CStringArray expected = m_MovieNames; expected.Add(LOST_IN_SPACE);
MovieListWindow* pWin = new MovieListWindow(m_pEditor);
pWin->Init();
pWin->GetMovieField()->SetWindowText(LOST_IN_SPACE);
::SendMessage(pWin->GetAddButton()->m_hWnd, BM_CLICK, 0, 0);
CPPUNIT_ASSERT_EQUAL(expected.GetSize(), pWin->GetMovieListBox()->GetCount());
CString str; pWin->GetMovieListBox()->GetText(pWin->GetMovieListBox()->GetCount()-1, str);
CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, str);
pWin->DestroyWindow(); delete pWin;
}
對應產品代碼 MovieListWindow::OnClickAddButton:
void MovieListWindow::OnClickAddButton()
{
UpdateData();
CString str; m_MovieField.GetWindowText(str);
if (!str.IsEmpty()) { m_pEditor->Add(str); m_MovieListBox.AddString(str); }
}
問題:測試不應依賴真實檔案/資料庫。解決:定義介面 MovieListEditor,實作 mockEditor 內建測試資料。
// 介面
class MovieListEditor {
public:
virtual CStringArray* GetMovies() = 0;
virtual void Add(CString) = 0;
virtual void Delete(int) = 0;
};
// Mock 實作
class mockEditor : public MovieListEditor {
CStringArray m_ar;
public:
mockEditor() { m_ar.Add("Star Wars"); m_ar.Add("Star Trek"); m_ar.Add("Stargate"); }
virtual CStringArray* GetMovies() { return &m_ar; }
virtual void Add(CString s) { m_ar.Add(s); }
virtual void Delete(int i) { m_ar.RemoveAt(i); }
};
測試中注入 Mock:
m_pEditor = new mockEditor();
MovieListWindow* pWin = new MovieListWindow(m_pEditor);
// ... 操作後驗證 Mock 內部狀態
CPPUNIT_ASSERT_EQUAL(expected.GetSize(), m_pEditor->GetMovies()->GetSize());
CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, m_pEditor->GetMovies()->GetAt(nIndex-1));

圖 2:所有測試通過
Movie:產品代碼GuiTestFirst:測試代碼AppMovieList:應用程式範例,繼承介面實作 MyEditor(可擴充 XML/Access 等)
圖 3:應用程式執行畫面