首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

測試驅動開發(TDD)實戰:GUI 單元測試與 Mock Objects

     編輯:關於VC++

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

環境與需求

  • CppUnit 1.9.0, VC6, Windows 2000
  • 功能:影片清單的新增、刪除、顯示
  • 介面:ListBox 顯示清單、Edit 輸入名稱、Add/Delete 按鈕

圖 1:GUI 草圖

階段一:UI 元件測試

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

階段三:引入 Mock Objects 隔離資料層

問題:測試不應依賴真實檔案/資料庫。解決:定義介面 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:應用程式執行畫面

總結

  1. GUI 測試優先非強制,視專案情況採用
  2. Mock Objects 隔離外部依賴,同時倒逼介面抽象化,提升代碼品質

參考資料

  • 《測試驅動開發實用指南》David Astels
  • 《測試驅動開發》Kent Beck
  • 《Endo-Testing: Unit Testing with Mock Objects》Mackinnon/Freeman/Craig
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved