2011-04-03 50 views
11

假設我已經決定用C或任何其他過程編程語言編寫一個大型應用程序。它有呼叫的依賴性看起來像這樣的功能:程序編程的依賴注入

A 
| 
+-------------+ 
|    | 
B1   B2 
|    | 
+------+  +------+ 
|  |  |  | 
C11 C12 C21 C22 

顯然,單元測試葉功能,C11,C12,C21和C22非常簡單:設置輸入,調用功能,斷言輸出。

但是,爲B1,B2和A啓用良好單元測試的正確策略是什麼?

Dependency Injection建議B1(和B2也)被宣佈爲跟隨?

// Declare B1 with dependency injection for invoking C11 and C12. 
int B1(int input, int (*c11)(int), int(*c12)(int)); 

但是,如果我有很多層次的調用,該策略看起來不可擴展。試想一下A的聲明是什麼樣的:

int A(int input, int (*b1)(int, int (*)(int), int(*)(int)), 
       int(*b2)(int, int (*)(int), int(*)(int)), 
       int (*c11)(int), 
       int (*c12)(int), 
       int (*c21)(int), 
       int (*c22)(int)); 

Yuck!一定有更好的方法。

有時候,我覺得DI和其他旨在提升模塊性和維護簡便性的類似模式實際上會妨礙代碼的清晰性,並且使應將簡單的編碼變爲無意義的抽象和複雜的間接代碼變得複雜。

C中的大型軟件項目(如Perl和Ruby)如何處理單元測試?

回答

3

如果您只需要DI進行單元測試,則可以使用鏈接器來完成。

我的意思是,函數B1 & B2在頭中聲明並被函數A使用,所以B函數的實現由鏈接器提供。你只需要爲單元測試提供一個不同的C-File。這應該不是一個大問題,因爲無論如何你可能都有自己的makefile用於單元測試。

如果在運行時需要動態依賴關係解析,則​​應使用函數指針的工廠模式(返回函數指針的函數),並在需要時將其從工廠拉出。工廠可以根據全球環境來決定返回什麼功能。

+0

這是一個有趣的想法。我將不得不嘗試。 – kirakun 2011-04-14 17:32:58

+0

你也可以用C編寫面向對象程序,而不僅僅是程序。只需聲明一個帶有數據成員和函數的結構,將結構作爲第一個參數(僞指針)。在c文件中實現,你已經創建了一個僞類。多態性和這樣的事情是可能的,只是複雜的;) – sanosdole 2011-04-14 21:00:03

0

我喜歡這個問題。它在程序語言中有點棘手......但我認爲你可以從OO世界借鑑一個想法,人們經常使用構造函數重載來處理一些DI工作。因此,例如,您將使用像往常一樣設置所有依賴項的默認構造函數......但是另外還有另一個允許注入依賴項的構造函數。

既然你是程序性的......我想你可以使用函數重載來爲你處理。此外,當您正在測試時,您只需在調用A ...時嘲笑B1 & B2,以便您可以簡化DI用於此目的。換句話說,如果你真的只使用DI的單元測試,那麼你就不必僅注入dependcies第一級依賴條件的整個樹...

所以從你可能有...

int A(int input){ 
// create function point to b1 & b2 and call "return A(input, {pointer to b1},{pointer to b2})" 
} 

原諒我的僞代碼,它一直以來我所做C.

2

你可以把依賴於C-結構,這將成爲函數調用一個參數很長一段時間。在c中,這將類似於文件api,其中第一個參數始終是文件句柄

+0

我們在其他語言中做了類似的事情,有一個服務提供者,它具有各種依賴關係的實現。它具有額外的好處,可以減少對函數簽名的更改並使重構更容易。 – Enno 2011-04-15 03:26:03

3

A只需要調用B1B2。它不需要知道關於C級別的任何事情。

爲了測試A,您可以將B1B2的不同虛擬版本注入A

這將A從需要整體結構中分離出來,意味着您可以單獨測試每個功能。

0

您可以在沒有DI的情況下對B1,B2和A進行適當的單元測試。就像葉子函數一樣,B1和B2有有效的輸入和輸出,並且你測試那些,和A一樣.B1可以在內部使用C11和C12來幫助你完成它的單元測試,並不意味着它們必須被注入你不需要這種靈活性。

+0

但它如何規模?在規範中,'A'將覆蓋'B1'和'B2'的功能,它們都涵蓋了'C11','C12','C21'和'C22'的功能。 因此,所有葉函數'C11'等的測試都必須爲它們上面的所有節點函數重複。現在,想象一個深層次的代碼集。你可能會發現編寫測試非常費力。 – kirakun 2011-04-14 17:32:20

+0

@Kirakun在實際情況下,A和B1和B2不一樣,它增加了一些額外的功能。如果你有B1和B2的測試,你可以對代碼的那部分有信心。對於你而言,你只需要測試你需要確信如何將B1和B2連接在一起。 – tddtrying 2011-04-14 22:20:35

+0

@tddtrying恕我直言*單元*測試(在上面的場景中)意味着測試'A' *而不在B級測試(即調用)函數,更不用說C級。 B級可能是有爭議的,但想象一下'C11'連接到一個數據庫。你不需要'A'(或'B1')的單元測試來調用實際的'C11'。相反應該調用某種模擬'C11'。 – TobiMcNamobi 2015-02-18 10:19:00