[英]Why and how implementing initial unit tests in legacy application code
我正在將單元測試集成到現有的遺留應用程序中。 在“使用遺留應用程序”一書和我閱讀的許多其他書籍中,有人寫道,在開始重構現有代碼或集成新功能,糾正錯誤等過程之前,您應始終編寫單元測試...
在我閱讀的大量樣本中,重構方法的簽名從未或很少中斷,舊的單元測試在經過大量更改后仍然有效。 原因是作者代碼不是那么遺留,以至於當我使用我認為的“遺留代碼”時,我每天查看的代碼。
實際上,當你有一個遺留應用程序時,代碼是如此糟糕,你必須打破方法的簽名。 如果您嘗試使用原始方法編寫單元測試,只需更改5分鍾后,您將打破整個簽名,第一次測試將很好地發送到垃圾箱。
舉個例子,看看下面的代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyCompany.Accouting
{
public class DataCreator
{
public static System.Data.DataSet CreateInvoice(
System.Data.DataSet customer,
System.Data.DataSet order,
string mails,
ref bool isValid)
{
System.Data.DataSet invoice = new System.Data.DataSet();
int taxGroupId =
ApplicationException.ShareConnection.ExecuteScalar(
"SELECT Id FROM TaxGroup WHERE TaxGroup.IsDefault");
Application.ShareConnection.ExecuteNonQuery(
"INSERT INTO Invoice (CustomerId, EffectiveDate) VALUES(?,?)",
customer.Tables[0].Rows[0]["Id"], System.DateTime.Now);
int invoiceId;
invoiceId = Application.SharedConnection.ExecuteScalar("SELECT @@IDENTITY");
Application.SharedConnection.ExecuteNonQuery(
"INSERT INTO InvoiceLine (ProductId, Quantity, InvoiceId) VALUES(?,?,?)", ,
order.Tables[0].Rows[0]["ProductId"], order.Tables[0].Rows[0]["Quantity"], invoiceId);
foreach(string mail in mails.Split(';'))
{
Application.MailSender.Send(mail);
}
isValid = true;
System.Data.DataRow row = invoice.Tables[0].NewRow();
row["Id"] = invoiceId;
invoice.Tables[0].Rows.Add(row);
return invoice;
}
}
}
正如您所看到的,這里有很多不良代碼。
重構后,方法不會是靜態的,ref參數將被刪除,DataSet將被轉換為POCO對象,對“Application”這樣的全局對象的訪問將被動態注入的屬性所取代,並且將進行許多其他更改實現接口,查看類的名稱,命名空間和許多其他東西。 事實上,這段代碼完全是一個廢話,應該扔掉並從頭開始重寫。
如果我為原始靜態方法創建單元測試,則在刪除static關鍵字以便以更面向對象的方式使用該類時,測試將立即中斷。 將DataSet更改為Poco等也是如此...
為什么在5分鍾內創建一個單元測試,我會丟掉這個測試? 這項測試有用嗎?
在這種情況下你會使用哪種策略?
非常感謝你。
這里的關鍵項目是選擇您實際要進行單元測試的點。 在您的情況下,對您要替換的確切方法進行測試沒有意義。 相反,需要為應用程序中調用方法的每個點創建一個測試,以確保特定功能仍然相同。
原因是,一旦完成了對DataCreator類的重構,您將不得不回到調用它的所有區域並更改它們。 在進行更改之前對這些區域進行測試將確保您的功能相同。
見下文:
public class SomeClass {
public Boolean DoSomething() {
OtherClass oc = new OtherClass();
return oc.DoSomethingElse("param1", "param2") == "true";
}
}
public class OtherClass {
public String DoSomethingElse(String param1, String param2) {
// horrible code here which never uses the second parameter
return "true";
}
}
在上面的示例中,您可能非常希望重構DoSomethingElse
以將返回類型更改為布爾值並消除第二個參數。
所以你首先在SomeClass.DoSomething
方法上進行單元測試。 然后將OtherClass
重構為您的內容,確保DoSomething
的最終結果是相同的。
當然,在這種情況下,您需要確保對每個調用“DoSomethingElse”的東西進行單元測試 。
您的單元測試將始終隨着簽名更改而改變。 解決此問題的最佳方法是設置測試一般行為的單元測試,並首先進行簡單的優化。
例如,從優化函數代碼本身開始(例如,修復數據訪問並將函數拆分為一對。)
然后你可以進入簽名重構,但是在你做之前,確保使用這個類的組件有基本的期望結果測試,這樣你就知道在刪除out參數的過程中,你忽略了一個取決於其中一個類的東西。就此而言。
在進行重大重構時,您的測試會發生很大變化。 有時候只需要進行概念性測試就可以了,所以你可以確保使用重構可用性是相似的,或者你會知道什么樣的測試被棄用,需要在許多其他依賴類中更新。
按照您希望的方式寫出接口,然后針對該接口編寫單元測試。
然后從接口調用遺留代碼,直到測試通過。
然后根據需要重構。
對?
在您開始更改之前,單元測試將作為該方法所需/執行的功能的活動/生活記錄。
在重構方法之后考慮它們就像檢查清單一樣,以確保它在重構之前仍然涵蓋了它所覆蓋的內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.