天天看點

《測試驅動的嵌入式C語言開發》——3.1節具有可測性的C子產品的那些元素

3.1 具有可測性的c子產品的那些元素

本書中的示例将采用子產品這個概念。在我們的目的之下,子產品就是系統中一個完備的部分,它有明确定義的接口。這個定義并沒有講一個子產品有多大。在本書中,我們隻會用很小的子產品。這些示例中的子產品會恰好與編譯單元相同,然而在現實世界中,并不是所有的子產品都與某個單一的編譯單元相對應。你會發現可測試的代碼需要子產品化。你還會發現tdd會自然而然地産生子產品化設計。

可測試性對于測試有相當大的正面影響。為了建立子產品化的c程式,我們需要抽象資料類型這個概念。barbara liskov在她所著的《programming with abstract data types》[lis74]一書中這樣定義抽象資料類型(abstract data type,adt):“抽象資料類型隻用可能對它進行的那些操作來間接定義,并且這些操作的效果(也可能是代價)有數學上的限制。”

在抽象資料類型(adt)中,一個子產品的資料被當做私有成員來對待。它被封裝起來。我們可以使用幾種子產品化方法來封裝子產品的資料。第一個選擇是用靜态的隻有在.c檔案中可見的變量,這樣隻有同一編譯單元中的函數才能通路它們。資料隻能間接地通過在.h檔案中有原型定義的那些子產品的公共接口來通路。這個方式适用于隻有一套資料需要處理的子產品,有時稱為單一執行個體子產品(single-instance module)。

當一個子產品要為不同的客戶管理不同的資料時,可以使用多執行個體子產品(multiple-instance module)。在多執行個體子產品中,必須要初始化資料結構并把它傳回給客戶以保持其上下文。這裡就是抽象資料類型發揮作用的地方。可以用typedef在頭檔案中提前聲明結構體,像這樣:

《測試驅動的嵌入式C語言開發》——3.1節具有可測性的C子產品的那些元素

隻要沒有代碼去解引用它,編譯器會很樂意讓我們把定義不完全的指針傳來傳去。可以在實作circularbuffer的.c檔案中定義結構體的成員,這樣就有效地隐藏了資料,進而使得隻有那些以該資料結構的完備性為責任的子產品才能操作它。如果你熟悉posix接口(portable operating system interface of unix)中的pthread庫,就會明白它使用的就是這種技術。unix中的file(檔案)是另一個抽象資料類型的例子。

當用tdd來建立c子產品時,我們會用到以下檔案及慣例:

用頭檔案來定義子產品的接口。對于單一執行個體子產品,頭檔案由函數原型構成。對于抽象資料類型,除了函數原型,還會有一個用typedef來指向提前聲明的資料結構的指針。正如多次提到的,隐藏資料結構是為了隐藏子產品的資料細節。

源檔案用來包含對接口的實作。它同時會包含任何所需的私有輔助函數和隐藏的資料。子產品的實作會管理子產品資料的完備性。對于抽象資料類型,提前聲明的資料結構成員會在源檔案中定義。

測試檔案用來包含測試用例,以保持測試代碼和産品代碼分離。每個子產品都至少有一個測試檔案,一個測試檔案通常僅包含一個,但有時會是幾個測試組。測試組圍繞組中所有測試通用的資料來組織。當一些測試需求的建立與其他測試顯著不同時,我們就需要有多個測試組,甚至多個測試檔案。

子產品初始化及清理函數。每個管理着隐藏資料的子產品都應該有初始化及清理函數。抽象資料類型完全隐藏的内部結構必然需要它們。c++把這個想法内置到建構和析構函數中。按照慣例,本書會為每個子產品建立create(建立)和destroy(銷毀)函數。對于由獨立函數組成的子產品,如strlen()和sprintf()這樣的沒有内部狀态的子產品将不會需要初始化與清理。

遵循這些實踐及習慣,代碼将變得更容易測試并容易閱讀和擴充。并不是完全不能測試那些可以随意通路資料結構/函數的代碼,隻是那會更難一點。在第一個例子中,本書将會用單一執行個體子產品來測試驅動開發一個led(發光二極管)驅動程式。我們以後再使用抽象資料類型。

繼續閱讀