深入淺出裸測之道 單元測試的單元化

2022-03-06 02:04:13 字數 4058 閱讀 2296

本文所涉及使用的工具, 見前文: 我的.net**庫 ------ 新.net架構必備工具列表

依賴注入di很大程度的幫助測試單元化。這對層與層之間的依賴關係,幾乎是真理。

如對資料讀寫的依賴關係,用irepository替換之後,所有用到irepository的類,如serivce這一層的examservice,在測試時,只需要傳入乙個mock的irepository類,就不需要使用真實的資料庫對它測試了.

我們的另外一層controller也用到service這一層,同樣我為service這一層的實現也提出乙個介面iexamservice,在controller的構造器中傳入iexamservice的mock類。因此,很容易的讓測試關注於controller本身的行為和功能。甚至可以在examservice類實現之前,我們就可以測試和實現controller類。這是依賴注入的優勢。

這一整套分層,解耦和測試我們已經實現了,並形成乙個規範的過程和成形的框架。現在已經簡單到按部就班,就能輕鬆完成,甚至後期都可以考慮自動生成這部分**。但這部分現在不是本文的重點。

當我們的注意力轉移到業務域時,情景有了悄悄的改變。業務域中,類與類之間有更多更複雜的依賴關係。相比之下,三層之間反而簡單。

這裡,把我正在做的考試(exam)類做乙個簡單的背景介紹。考試,對於身經百戰的我們應該不陌生了,讓我們好好分析,看看熟悉身影的陌生之面。另外,我這裡考試更多是拿社會化考試作分析目標。

乙個考試有三個很重要的要素:考試**(考試定義);考區(北京考區,湖南考區);考試日期。這三個要素,唯一標識乙個考試,也就是說,同乙個考區,同乙個考試定義在同日期,我就認為是同乙個考試。很簡單的邏輯,為了體現這個邏輯,我把這三個要素,放在考試類的構造器中。為什麼?任何乙個要素的缺失,考試物件的存在都沒有任何含義,所以一開始構造的時候,就要傳入。從另乙個角度,考區+考試定義+日期是考試的業務id,是唯一標識,必須貫穿於業務物件的始終。

看**:

public class exam

}

通過構造器,從外部傳入三個物件後,把它們賦給考試的三不屬性,而這三個屬性是唯讀, private是為了給nhibernate和構造器使用的。為什麼?如前所說他們是業務動,在建立之後,再修改沒有任何含義。

看**:

public class exam

public virtual examdef examdef

public virtual district district

public virtual date date

}

好了,背景已經足夠了。讓我們來針對這部分功能進行測試。喂,等等,我們……現在有功能嗎?有!我測試的描述就是,

當從構造器鏈構造考試類時,三個屬性應該要賦相應的值。

是的,足夠簡單使我們一目了然,也足夠複雜,我們需要用測試來保障它的功能。 1. 保證它被執行---覆蓋測試;2. 保證它是按我的設計進行的---行為測試。

看**:

[testfixture]

public class when_create_an_exam

}

引入三個中間變數和另外三個類的定義我就不在這羅嗦了。我的命名方式也曾為人病詬,也不在這辯解。只看實質內容:分別建立三個類的例項,用於測試,至於這三個類的具體內容,我其實並不關心。所以用個詞stub來表示我的不關心。ddd的核心理念之一:名符其實。最後,我的斷言只判斷屬性的值是否與構造器傳入值相符。ok,完成!

過一段時,間。我們再回頭看看這段測試,會有些小小的不舒服。特別,我們還有更多的類有類似的構造器賦值功能,還有更多更複雜的功能等著我們去測試,我們在做商業軟體,不是嗎?隨著類似的測試更得越多。這些小小的不舒服會越積越大。

這面的測試有什麼問題?

1. 測試有三部分:建立測試環境;呼叫被測功能,(測試的本體);斷言。上面的**,我甚至都已經刻意用注釋分離出了這麼三塊,但仍不是語法級別的分離。

2. 對第三方的類依賴較為嚴重,這是本文的重點---單元測試單元化。對exam類來說examdef, district都是插足的第三者。

3. 測試**太多,被測的實際上只有三行,雖然這不是原則性的問題,但是本著更好,更快,更強的精神,這個問題也是值得解決的。

好了,你提出的問題已經太多了,我沒辦法一下子解決。3個還多?是的,我們的口號是「只要乙個好」。

言歸正傳,讓我們本著選代和重構的原則來把這些問題乙個乙個解決。是的,測試也需要重構,測試**還有bug呢?一點不奇怪。你沒碰到過?噢,因為你根本不寫測試**。

關於測試的三段式,我曾經看過有人確實在nunit的框架下一步一步重構,形成良好了測試框架。這裡我就不這麼麻煩了,直接上工具mspec!測試的三段式,有個說法,叫aaa語法,分別是arrange,action,assert。3a級語法,多酷!

而mspec用了自己的名詞,分別是establish, because, it。看看下面改造之後的測試**就清楚什麼意思了。

看**:

public class when_create_an_exam_by

;private because of =

() => subject = new exam(stub_district, stub_exam_def, stub_date);

private it should_assign_to_properties =

() =>

;private static examdef stub_exam_def;

private static district stub_district;

private static date stub_date;

private static exam subject;

}

再看一看測試執行的結果,就明了**即文件的含義了。

看截圖:

從nunit公升級到mspec,給人一種耳目一新的感覺。開始也許會有些不習慣。但是,一旦習慣之後再也不想回頭了。

好了,看看第二個問題。一開始,我們依乎不覺得這是個大問題,不就是直接建立乙個依賴美嗎,建立就完了唄,一行**而已。仍然,需要提醒注意,我們是在做商業軟體。一旦展開了,乙個類不可能只是

一、兩個類,特別是間接關聯的,會更多,拔出蘿蔔帶出泥。就拿這個考試類來說,在我們的實際專案中,它還有考試科目列表屬性,還通過報考類與考生有間接聯絡。而報考類又與訂單類,事務類有互動有關係。考慮所有這些級聯關係,難道我為了測試這個構造賦值功能把所有的類全部建立出來?

再進一步思考,我們會給出乙個自然的解決方案,把考區類,考試定義類抽象出兩個介面來,構造器傳入介面定義,而不是類本身。這其實是對層與層之間依賴注入的乙個模仿。但是,相信我,這個方向是另乙個夢魘的入口。業務域和多層之間完全是不同的環境。不想太深入討論,可能獨立一篇文章都打不住。

幸好,我們有另乙個工具rhino mock,能幫助我們解決類的模擬的問題。改造之後的測試**如下。唯一的影響是,你需要為被模擬的類,加入乙個至少是protected的無引數構造器。這其實不是個大問題,如果你同時在專案中使用nhibernate的話,也會有類似的要求。

看**:

public class when_create_an_exam

;//...此處省略的沒有修改的**

}

可以看到,這一次的重構,把考試**、考區**等,其實你根本不關心的資訊已經省略掉了。

到這還不夠,最後乙個問題是填飽我們肚子的最有一塊燒餅。

隆重介紹automocking,自動模擬。當你的測試類從automock的specification類繼承時,它會自動為你建立乙個被測試物件subject,並且根據被測試物件構建器的引數定義,全自動的建立模擬物件。而引用這些模擬物件的方式,

很簡單dependency,就是依賴注入的依賴這個詞。已經不需要太多的解釋---名如其實。

再看**:

public class when_create_an_exam:specification;

}

三行實現**,對應三行測試**。簡潔的不能再簡潔了。

Nginx 的深入淺出

1.什麼是nginx nginx是一款高效能的http 伺服器 反向 伺服器及電子郵件 imap pop3 伺服器。由俄羅斯的程式設計師igor sysoev所開發,官方測試nginx能夠支支撐5萬併發鏈結,並且cpu 記憶體等資源消耗卻非常低,執行非常穩定。2.nginx的應用場景 3.nginx...

深入淺出Google ProtoBuf中的編碼規則

在開始本部分的內容之前,首先有必要介紹兩個基本概念,乙個是序列化,乙個是反序列化。這兩個概念的定義在網上搜一下都很多的,但大多都講得比較晦澀,不太好理解,在這裡我會用比較通俗的文本來解釋,盡可能讓讀都朋友們一讀就明白是怎麼回事 序列化 是指將結構化的資料按一定的編碼規範轉成指定格式的過程 反序列化 ...

深入淺出Google ProtoBuf中的編碼規則

在開始本部分的內容之前,首先有必要介紹兩個基本概念,乙個是序列化,乙個是反序列化。這兩個概念的定義在網上搜一下都很多的,但大多都講得比較晦澀,不太好理解,在這裡我會用比較通俗的文本來解釋,盡可能讓讀都朋友們一讀就明白是怎麼回事 序列化 是指將結構化的資料按一定的編碼規範轉成指定格式的過程 反序列化 ...