重構乙個UT測試 二 翻譯

2021-10-22 23:31:49 字數 4797 閱讀 7524

構建的系統架構能讓大多數測試用例不依賴於資料庫就能執行,這樣最好了,但是我們還是經常遇到許多測試用例需要資料庫的。在這種情況下,我們可以擴充套件測試自動化框架(test automation framework)去完成大部份的工作。可以增加一種使用框架來進行建立物件註冊的辦法,這樣,框架就能為進行刪除操作。

首先,當建立物件時,我們需要註冊它。

// set up fixture

billingaddress = new address("1222 1st st sw", "calgary",

"alberta", "t2n 2v2", "canada");

registertestobject(billingaddress);

shippingaddress = new address("1333 1st st sw", "calgary",

"alberta","t2n 2v2", "canada");

registertestobject(shippingaddress);

customer = new customer(99, "john", "doe",

new bigdecimal("30"),

billingaddress,

shippingaddress);

registertestobject(shippingaddress);

product = new product(88, "somewidget",

new bigdecimal("19.99"));

registertestobject(shippingaddress);

invoice = new invoice(customer);

registertestobject(shippingaddress);

註冊過程由將物件加入到測試物件集合的操作構成:

list testobjects;

protected void setup() throws exception

protected void registertestobject(object testobject)

在teardown方法中,我們遍歷測試物件集合,並將每個物件刪除:

public void teardown() catch (runtimeexception e) }}

現在測試用例變成這樣子:

public void testadditemquantity_severalquantity_v8()

我們已經能夠將try/finally語句塊給移除。除了呼叫registertestobject外,我們變得**簡單多了。但我們仍然可以進一步精簡**。為什麼這樣說?我們需要將宣告變數,然後將它們初始化為null,再稍後對其重新初始化嗎?之前的測試用例這樣做是因為這樣變數必須在finally語句塊中可訪問;現在我們將finally塊移除了,所以我們可以將變數定義與初始化操作合併。

public void testadditemquantity_severalquantity_v9()

3) 清理夾具建立

我們已經清理好了斷言及夾具解除安裝,現在來看看夾具建立。乙個明顯的「快速修復」就是,對建構函式和registertestobject的呼叫,利用「方法抽取重構」來定義「生成方法」(creation method)。這樣可以使得測試用例更易於讀寫。「生成方法」有另外乙個好處:它們封裝了sut的api,使得當物件的建構函式發生改變時,我們不必每個測試用例都去更改,只需要去修改乙個地方,減少了測試用例的維護成本。

public void testadditemquantity_severalquantity_v10()

這個夾具建立邏輯還是有其他問題。第乙個問題是,很難明確這個夾具與測試預期輸出之前的聯絡。customer物件的細節會以某種方式影響到輸出嗎?customer的address域會影響到輸出?這個用例真正想驗證的是什麼?

另乙個問題是:這個測試用例展示了「硬編碼測試資料」(hard-coded test data)(見模糊測試)。如果sut將我們建立的所有物件持久化到資料庫中,那硬編碼資料就會導致:當customer,product或者invoice的某些域要求必須唯一時,會出現「不可重覆式測試」、「互動式測試」或者「測試執行衝突test run war」(見不穩定測試)。

我們可以通過為每乙個測試用例生成唯一的值並用這個值做為種子(seed)去產生用例中使用到的物件。這個辦法能保證每次用例執行時,都會得到不同的物件。因為我們已經將物件產生邏輯移到了生成方法中,這一步修改相對容易。我們只要將上述邏輯放到生成方法中並去掉相應的引數就行了。抽取方法式重構還有另外乙個用處,我們可以生成乙個新的、無引數的新版生成方法。

public void testadditemquantity_severalquantity_v11()

private product createaproduct(bigdecimal unitprice)

我們將這個模式稱為「匿名生成方法」(anonymous creation method),因為這個模式表明我們並不關心物件本身的特性。如果sut的預期行為依賴於特定的值,我們要麼可以將這個值做為引數傳到生成函式,要麼可以在生成函式的函式名中暗示。

這個測試用例看上會好一些了,但是仍然沒有做完。預期結果真的以某種方式依賴於customer物件的address?如果不依賴,我們可以通過抽取方法式重構(再一次)將它們的建立過程完全隱藏,這裡用createacustomer方法來達到這個目的:

public void testadditemquantity_severalquantity_v12()

將建立address物件的呼叫移到customer的建立方法中,我們更加清楚:這個測試用例中,address物件並不會影響我們需要驗證的邏輯。然而,結果依賴於customer的discount,所以將discount做為customer的生成函式的引數。

我們還是有一兩個地方要處理的。比如,表示單價(unit price),數量(quantity),折扣(discount)的測試資料是硬編碼的,且在這個測試用例中重複出現了兩次。我們可以使用「用符號化常量替換魔數式重構」來給這些資料以名稱,用於說明它們的含意。另外,用於建立lineitem物件的建構函式並沒有在sut本身的其他地方使用到,因為lineitem只是在其建立時去計算extenedcost。我們可以將這個針對測試本身的**放到測試輔助中的外部方法(foreign method[fowler])。我們已經在處理cuostom及product中看到了例子:使用引數化的生成方法,只根據相關的值返回預期的的lineitem物件。

public void testadditemquantity_severalquantity_v13()

4) 清理後的測試用例

下面是清理後的最終版本的測試用例:

public void testadditemquantity_severalquantity_v14()

我們已經使用了「引入解釋變數式重構」將base_price(price*quantity)和extended_price(帶折扣的**)的計算過程更好地文件化了。修訂過的測試用例比我們開始時面對的笨重的**各小巧、各清晰。它也實現了「測試即文件」的任務。那麼,我們能發現這個測試驗證的是什麼嗎?它驗證了通過新增函式,商品(item)確實回到了發貨單(invoice),而且總成本(extended cost)是基於產品**(product price),使用者的折扣(coustomer』s discount)及預定的數量(quantity ordered)。

0.4 寫更多的測試用例

看上去我們花了很大的精力去重構這個用例以使其更加清晰,是不是每乙個用例都要花費這麼多的精力呢?

希望不是!這兒花費的努力大多數與發現用例中需要用到的測試輔助方法(test utility methods)有關。我們定義了測試我們程式的「更高階語言」(higher-level language)。一旦有了這些輔助方法,編寫其他用例就會變得簡單得多。比如,如果我們想去驗證當lineitem的數量發生變化時,總**(extended cost)會被重新計算,我們就可以重用大多數的測試輔助方法。

public void testaddlineitem_quantityone()

public void testchangequantity_severalquantity()

這個用例只要了大概兩分鐘,而且沒有去新增任何新的測試輔助方法。比較一下,如果按照之前的風格來寫乙個全新的測試用例,會要多長的時間。另外,在測試用例編寫上面節省的精力只是等式的一部份,還應該考慮到,每次重訪已有測試用例時,可以節省理解的時間。在專案開發及後續的維護活動中,這種節省還會是不斷累積的。

0.5 進一步的精簡

後來新增的測試用例帶來了**重複。比如,我們總會去建立customer和invoice物件。為什麼不能將這兩行合併呢?同理,我們在測試方法中不斷定義、初始化常量quantity及customer_discount_pc。為什麼不能只做一次?pruduct物件沒有在測試中發揮作用,我們總是用同樣方式去建立它。能將這個職責提出來嗎?當然可以,我們對每組重複**使用「抽取方法式重構」,建立乙個更強大的「生成方法」。

public void testadditemquantity_severalquantity_v15()

public void testaddlineitem_quantityone_v2()

public void testchangequantity_severalquantity_v2()

我們將需要去理解的35行**減少到6行。只需要去維護原有**的1/6。可以進一步將夾具的建立放到setup方法中,不過只有將很多測試用例都需要同樣的customer/discout/invoice的配置。如果我們想讓其他測試用例類(testcase classes)復用這樣測試輔助方法,我們可以使用「抽取超類式重構」建立乙個測試用例超類(testcase superclass),再使用「上移方法式重構」將這些測試輔助函式上移,那它們就能被重用了。

如何重構乙個系統

發現乙個很有意思的情況,做系統寫 多年了,遇到的需求基本上是在已有的系統上實現,從頭來實現的系統基本上沒有。無論是從頭是實現乙個系統,還是維護乙個系統,當時實現的技術可能是最先進的 規劃的產品邏輯是合理的,隨著時間的發展 開發人員的變更 系統的 質量會逐漸腐化,加個feature太麻煩,改個bug涉...

《重構》讀書筆記(二) 第一章 第乙個重構案例

作者在第一章通過乙個影片出租的例子,試圖闡述重構的基本過程和步驟。看得出來,作者對這個案例給予厚望,花了很大的篇幅。正因如此,我沒有理由不好好學習這一章。影片出租的例子本身不難,但我足足花了一整個下午學習了這個例子。我先是老老實實的把 用c 重抄了一遍,然後跟著作者的步伐,一步步重構,以期體驗 重構...

乙個專案工程的重構小結

首次看到移交到我手上的c 工程那個7千多行 的檔案時,既佩服又擔心,前輩的構架能力讓我甘拜下風,同時擔心能不能勝任。在研究原始碼的時候,也解決幾個線上遺留的問題。愈發覺得有重構的必要 因為這將是我負責的 不完全重構 那叫重寫,一是否定了前輩的功勞,二是接觸不久業務不熟,風險大。因此總體原則是逐步重構...