簡體   English   中英

如何在沒有模擬框架的情況下對具有令人討厭的依賴性的類進行單元測試?

[英]How do I unit test a class with nasty dependencies without a mock framework?

我正在使用遺留的C ++代碼庫,我想測試一個類DependsOnUgly上的一些方法,它們具有依賴性,這種依賴性在大型類( Ugly )上很容易被破壞,在文件系統上有很多外部依賴,等等。我想要至少得到DependsOnUgly一些方法,同時盡可能少地修改現有代碼。 沒有很多代碼修改,無法通過工廠方法,方法參數或構造函數參數創建接縫; Ugly是一個直接依賴的具體類,沒有任何類型的抽象基類,並且有很多方法,很少或沒有一個標記為virtual ,完全嘲笑它會非常危險。 我沒有可用的模擬框架,但我想讓DependsOnUgly接受測試,以便我可以進行更改。 如何打破Ugly的外部依賴關系來對DependsOnUgly上的方法進行單元測試?

使用我所謂的預處理器模擬 - 通過預處理器接縫注入的模擬。

我首先在這個關於Programmers.SE的問題中發布了這個概念,並且根據我的答案我判斷這不是一個眾所周知的模式,所以我認為我應該分享它。 我發現很難相信之前沒有人做過這樣的事情,但因為我找不到記錄,我想我會與社區分享。

以下是UglyNotAsUgly名義實現。

DependsOnUgly.hpp

#ifndef _DEPENDS_ON_UGLY_HPP_
#define _DEPENDS_ON_UGLY_HPP_
#include <string>
#include "Ugly.hpp"
class DependsOnUgly {
public:
    std::string getDescription() {
        return "Depends on " + Ugly().getName();
    }
};
#endif

Ugly.hpp

#ifndef _UGLY_HPP_
#define _UGLY_HPP_
struct Ugly {
    double a, b, ..., z;
    void extraneousFunction { ... }
    std::string getName() { return "Ugly"; }
};
#endif

有兩種基本的變化。 第一個是DependsOnUgly只調用Ugly某些方法,你已經想要模擬那些方法。 第二是

技巧1:替換DependsOnUgly使用的Ugly所有行為

我將此技術稱為預處理器部分模擬,因為模擬僅實現被模擬的類的接口的必要部分。 在mock類的頭文件中使用包含與生產類相同名稱的保護,以使生產類永遠不會被定義,而是模擬。 一定要在DependsOnUgly.hpp之前包含模擬。

(請注意,我的測試文件示例不是自我驗證的;這只是為了簡單起見,並且是單元測試框架不可知的。重點是文件頂部的指令,而不是實際的測試方法本身。)

TEST.CPP

#include <iostream>
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
    std::cout << DependsOnUgly().getDescription() << std::endl;
}

NotAsUgly.hpp

#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly { // Once again, duplicate name is deliberate
    std::string getName() { return "not as ugly"; } // All that DependsOnUgly depends on
};
#endif

技巧2:替換DependsOnUgly使用的Ugly行為

我稱之為Subclassed-in-Place Mock,因為在這種情況下, Ugly是子類,並且必要的方法被覆蓋而其他方法仍可供使用 - 但是子類的名稱仍然是Ugly define指令用於將Ugly重命名為BaseUgly ; 然后使用undefine指令,並使用模擬Ugly子類BaseUgly 請注意,根據具體情況,這可能需要將Ugly某些內容標記為虛擬內容。

TEST.CPP

#include <iostream>
#define Ugly BaseUgly
#include "Ugly.hpp"
#undef Ugly
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
    std::cout << DependsOnUgly().getDescription() << std::endl;
}

NotAsUgly.hpp

#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly: public BaseUgly { // Once again, duplicate name is deliberate
    std::string getName() { return "not as ugly"; }
};
#endif

請注意,這兩種方法都有點不穩定,應謹慎使用。 隨着更多的代碼庫正在測試中,它們應該被移開,並且如果可能的話,用更多標准的破壞依賴性的方法替換它們。 請注意,如果遺留代碼庫的include偽指令足夠混亂,它們可能都會失效。 但是, 我已經成功地將它們用於實際的遺留系統 ,所以我知道它們可以工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM