2014-12-30 21 views
3

我正在試驗一些C++遺留代碼。特別是,我有一個類層次結構,例如A < B < C(即,AB的子類,並且BC的子類),並且存在全局引用到類型爲C的對象,該對象從全局系統的代碼(單例模式)。目標是用一些僞造的對象替換那個對象(實際上,C用於訪問數據庫)。如何僞造包含非虛函數的C++類?

我的第一次嘗試是引入接口IA, IB, and IC(其中包含相應類的功能的純虛擬版本),讓每個類實現其接口,並將全局參考的類型更改爲IC。在我測試的設置功能中,我將用我自己實現的IC替換全局引用的C對象,從而使整個系統使用我的假實現。

但是,類A, BC每個都包含相當多的非虛函數。現在,如果我要讓類從我的接口繼承,我會將這些函數的語義從非虛擬變爲虛擬(Feathers在「使用遺留代碼高效工作」,第367頁討論了這個問題。換句話說:我必須檢查每個對我的全局對象的調用,而且我必須確保在更改之後,仍然會調用相同的函數。這聽起來像是很多錯誤PRONE工作給我。

我也想過讓非虛函數「最終」,即告訴的A, BC的功能不能在子類中隱藏(編譯這將使編譯器告訴我的B所有潛在危險的功能和C - 如果一個函數沒有隱藏在基類中,上述效果根本就不會發生),但似乎並沒有被C++支持(我們還沒有使用C++ 11,但即使是最終的關鍵字似乎只適用於虛擬功能)。

爲了使情況更加困難,類A, BC還包含公共屬性,虛函數以及一些模板函數。

所以我的問題是:如何應對我上面描述的情況?有沒有我錯過的C++功能,哪些可以幫助我的方案?任何設計模式?甚至是任何重構工具?我的主要要求是變化必須儘可能安全,因爲我想假冒的課程對系統來說是相當重要的......我也會對一個「醜陋」的解決方案感到高興,這個解決方案可以讓我進行測試(如果系統適當覆蓋測試,可以在後面重新構建)。

編輯:我搞砸了我的繼承層次結構(它顛倒了) - 現在已經得到糾正。

編輯2:我們最終結束如下:我們只做了我們當前測試用例實際需要的虛擬函數。然後,我們檢查每個調用的方法(這是可管理的)。這讓我們可以用Google Mocks來模擬我們的課程。越來越多的測試案例到位,我們希望我們的變化能夠隨時間節省。請注意,當問我的問題時,我認爲Google Mocks只能模擬純粹的接口;這是而不是的情況,因此允許如上所述的增量方法。

+0

通常情況下,測試的頭號問題是全局對象,所以我建議在做其他事情之前擺脫它。 – Puppy

+0

是'A'和/或'B'具體類嗎?如果是這樣,他們*需要*成爲? –

+0

@Puppy:我們的系統有很多全局對象,我認爲這是一個典型的雞與雞蛋問題:爲了安全地擺脫全局變量,我需要測試,並且爲了編寫測試,我需要擺脫全局。因此,我現在試圖通過儘可能少的和儘可能安全的重構來實現測試,並且我計劃在稍後處理完整的全局問題。 – csoltenborn

回答

1

編輯: Per DietmarKühl的評論如下,這種方法不會僞造成員函數模板。

我不確定這是否可行,但嘗試在單獨的.cpp文件中製作假類(非虛擬)。然後,將測試與假類聯繫起來,但不是真正的類。從理論上講,兩個應用程序具有相同的ABI(應用程序二進制接口),所以兩者應該兼容。

+0

如果不是這個類中的成員函數模板,那可以工作。使用成員函數模板,幾乎肯定有必要,至少用測試版本進行編譯。 –

+0

啊,我錯過了。是的,使用成員模板函數,這種方法不能用來僞造這些函數。 – Maz

+0

它可以工作,如果你換出標題以及圖書館。您可以定義一個宏,然後使該宏包含條件。 – Puppy

2

我打算建議將C更改爲模板,其中模板類型是實際的數據庫訪問/實現代碼。然後在您的直播節目中,您使用C<LiveDatabase>無處不在您目前使用C,並在測試時使用C<MockDatabase>。然後AB保持不變,並且所有C的內部工作都保持不變,只有特定的數據庫調用不同(在調用委託實現時)。

接下來我會介入並研究公共屬性,因爲它們只會導致您頭痛和難以發現的錯誤。簡單地說,讓它們全部保密,讓你的編譯器告訴你他們正在被訪問。對於只讀使用,只需添加訪問器就非常容易。在他們正在發生變異的地方,您需要確定適合您班級的界面以實現所需的變異。不要只是添加一個增變器方法,除非這絕對是你的最後一種選擇。

+0

是否有理由更喜歡模板來簡單地將數據庫層提取到實例化C中提供的單獨對象? –

+0

@Dave Bacher通過允許編譯器在編譯時知道類型,模板可以提供更好的性能。 –

+0

「模板想法」是一個有趣的建議,我肯定會在明年進行評估 - 感謝您的建議! – csoltenborn

相關問題