談開發(fā)者測試,為什么又要談設(shè)計(jì)呢?這是一個(gè)有意思的問題。在切入這個(gè)主題之前,先看一個(gè)“完美架構(gòu)圖”的問題。
架構(gòu)是完美的,實(shí)現(xiàn)是骨干的
我相信你肯定看過無數(shù)多個(gè)類似這樣的架構(gòu)圖,每個(gè)框框都排布整齊,而且顯得特別高大上。

但是,實(shí)際的系統(tǒng)實(shí)現(xiàn)真的是這樣嗎?未必。系統(tǒng)往往充滿了各種變化、約束、限制和條件,這些隱式的概念往往是不能“公諸于世”的。設(shè)計(jì)存在就是為了控制住系統(tǒng)實(shí)現(xiàn)的復(fù)雜度,應(yīng)對(duì)軟件的變化,并將這些隱式的概念顯式化。
但是,我們也不能否認(rèn)架構(gòu)存在的意義,至少它利于個(gè)體之間的交流,利于與客戶的溝通,并在更高維度的角度看待問題,并指導(dǎo)進(jìn)一步的系統(tǒng)設(shè)計(jì)和實(shí)現(xiàn)。
分層架構(gòu)
理論上,任何復(fù)雜的系統(tǒng)都可以用一個(gè)main函數(shù)實(shí)現(xiàn),但事實(shí)上沒人那么干,分層架構(gòu)便是一種最樸素的系統(tǒng)分解和組合的架構(gòu)思維,并符合大部分人的心智模型。

分層架構(gòu)最大的好處在于提供了層間抽象和隔離的機(jī)制,并非常容易在工程上保證層間的契約不被破壞。例如,分層架構(gòu)遵守單向依賴原則,當(dāng)有人違背架構(gòu)原則而引入循環(huán)依賴,采用一些架構(gòu)看護(hù)的工具,使能自動(dòng)檢查這些行為的。
其次,分層架構(gòu)有利于開展模塊間并行開發(fā)和協(xié)作。每個(gè)模塊只要邊界清晰,便可以獨(dú)立開發(fā)了。但是,任何的軟件工程方法的實(shí)踐,必然存在它的邊際效應(yīng)。
同層模塊間依賴混亂、模塊內(nèi)實(shí)現(xiàn)一團(tuán)亂麻,
這是很多系統(tǒng)實(shí)現(xiàn)的真實(shí)寫照。雖然,層間調(diào)用和約定都得到了很好的約束和保證,但同層內(nèi)的模塊間的耦合程度極高,相互引用和依賴,缺失架構(gòu)原則的約束和檢查。
更有甚者,模塊內(nèi)實(shí)現(xiàn)基本都是基于全局變量的過程化設(shè)計(jì),缺失最基本的封裝。在面臨變化的時(shí)候,要么就是堆砌if-else,要么大面積拷貝代碼,修修補(bǔ)補(bǔ),導(dǎo)致系統(tǒng)實(shí)現(xiàn)的耦合度急劇攀升,最終將系統(tǒng)成功演進(jìn)為典型的“腐化系統(tǒng)”。

設(shè)計(jì)邊界
當(dāng)我們確定了設(shè)計(jì)的邊界和范圍,便自然地決定了軟件工程效率提升的邊界、獨(dú)立測試的邊界、獨(dú)立構(gòu)建的邊界,及其獨(dú)立可替換的設(shè)計(jì)邊界。

邊際效應(yīng)
任何流程、工具和機(jī)器提升效率的邊界,約束在設(shè)計(jì)邊界的最大效應(yīng)內(nèi),無法跨代提升。唯有改進(jìn)設(shè)計(jì),將設(shè)計(jì)的邊界往內(nèi)遷移,相應(yīng)的工程效率才能得到質(zhì)的提升和飛越。
邊界等同
設(shè)計(jì)邊界即獨(dú)立裁剪的邊界,獨(dú)立測試和獨(dú)立交付的構(gòu)建邊界。如果層間之間的依賴是抽象的,那么層測試所依賴的其他層實(shí)現(xiàn)便可以替換為測試替身;如果層內(nèi)模塊之間是低耦合的,那么模塊測試所依賴的其他模塊也可以替換為測試替身;如果模塊內(nèi)類或函數(shù)之間是低耦合的,那么類或函數(shù)所依賴的其他類或函數(shù)也可以替換為測試替身。
設(shè)計(jì)優(yōu)先
因此,可替換性(裁剪性)、可測試性是提前設(shè)計(jì)出來的,是由設(shè)計(jì)的邊界決定的,這便是"Design for Testability"的原始初衷。
測試金字塔
因?yàn)樵O(shè)計(jì)的邊界決定了測試的邊界。正如上述的系統(tǒng)實(shí)現(xiàn),如果將系統(tǒng)看成整體,其外部的依賴和可觀察的行為是可以控制的,所以設(shè)計(jì)相應(yīng)的系統(tǒng)測試,雖然復(fù)雜,而且極難構(gòu)造,但它是一種較為可行的測試方案。所以,這也是遺留系統(tǒng)普遍采用的一種測試方案,產(chǎn)生了大量的ST用例。
層間接口也較為穩(wěn)定,所以也可以構(gòu)造出大量的IT用例;而層內(nèi)模塊之間耦合程度逐漸增大,所以CT用例越來越少;隨著設(shè)計(jì)邊界的邊際效應(yīng)的消失,模塊內(nèi)的單元測試構(gòu)造成本很高,或測試用例很脆弱,大部分系統(tǒng)實(shí)踐單元測試最終走向了不歸路。
最終,實(shí)際的測試金字塔是倒立的,或呈現(xiàn)菱形。與理想的測試金字塔剛好相反或背道而馳。

理論上,UT是容易構(gòu)造的,而且反饋是急快的,而且應(yīng)該擁有絕對(duì)數(shù)量的UT用例。但是,現(xiàn)實(shí)并非如此,構(gòu)造的UT并非易事;實(shí)現(xiàn)可能依賴龐大的全局變量,函數(shù)入?yún)⒌慕Y(jié)構(gòu)體包含了繁雜的字段,而且函數(shù)實(shí)現(xiàn)朝夕令改,導(dǎo)致用例極為脆弱。
就其本質(zhì),這都是設(shè)計(jì)的問題。如果將每個(gè)全局變量進(jìn)行合理的封裝,那么內(nèi)部邏輯變得更加穩(wěn)定,測試用例的前置條件也變得容易構(gòu)造;如果將不必要的依賴剝離,將實(shí)現(xiàn)最小化依賴,測試用例也會(huì)變得更加健壯;如果將依賴設(shè)計(jì)為抽象的,那么在測試中便可以輕松地替換為測試替身,提升可測試性。
總而言之,可測試性是設(shè)計(jì)出來的(Design for Testability)。