在單元測試中存根和模擬對象處于一個非常重要的地位以下我就來說說我對兩者的理解。
工作單元最終的三種結果類型:
重點說一下交互測試:如果一個特定的工作單元最終的結果就是調用另一個對象那么就要進行交互測試。簡單來說就是你無法判斷你是否調用了這個方法,因為他的返回值是void,那么你只能通過其他方式來判斷你確實調用了這個方法。這整個過程就就是交互測試。
什么是模擬對象:模擬對象也是偽對象,它可以驗證被測試對象是否按照預期的方式調用了這個偽對象,因此來判斷單元測試的成功或者失敗。
舉一個例子:小明由于作業沒有做完,老師就讓小明放學之后晚回家一個小時來寫作業,那么今天老師有事就提前回家了,就讓班長小亮來查看小明是否留下來一個小時在寫作業。此時小亮就是我們說的偽對象,他就檢測了小明是否晚回家一個小時。
1:存根上一篇已經說了現在我們看一下圖
很明顯我們斷言的對象是被測試類下面是模擬對象
這個是對模擬對象的斷言。
其實他們區別很小,他們的根本區別就是存根不會導致測試的失敗而模擬對象卻可以(存根由于斷言是對被測試類所以不會導致測試失敗,而模擬對象恰恰相反)
模擬對象就是來檢測你的測試是否會失敗。下面看例子
現在我引用一個外部的LogService專門記錄錯誤日志,但是這個日志是void類型無法返回這個時候模擬對象就派上用場。我們先定義個一個日志服務接口
public interface ILogService { void ErrorLog(string message); }
被測試代碼
PRivate readonly ILogService _logService; public UserManager(ILogService logService)//模擬對象注入 { this._logService = logService; } public void RecordLog(string userName) { if (userName.Length<=4)//名稱長度小于4就記錄日志異常 { _logService.ErrorLog(userName); } }
模擬對象
public class FakeLogService:ILogService { public string Message;//記錄錯誤信息 public void ErrorLog(string message) { this.Message = message; } }
測試代碼
[Category("模擬對象")] [Test] public void RecordLog_UserNameTooShort_CallLogService() { var mockService=new FakeLogService(); var userManager=new UserManager(mockService);//注入模擬對象 userManager.RecordLog("lp");//記錄錯誤日志 Assert.AreEqual("lp", mockService.Message);//如果錯誤信息和模擬對象的相同說明我已經調用了這個方法并正確的傳遞了值 }
我們看一下測試效果:
我們發現測試過去了說明我們已經的方法正確的調用和傳遞值給日志服務。這個測試保證的是我們調用日志方法沒有錯誤。
存根和模擬對象的同時使用
有時候一個方法體有2個未能返回值的方法,那么這個時候你可能就要確定一下哪個是存根哪個是模擬對象了。
比喻現在我們又加入一個需求,如果出現錯誤日志異常就要給系統管理員發一份郵件,這個時候我們就發現自己有2個沒有返回值的函數,不建議寫2個模擬對象,那樣就會造成混亂你不知道到底是哪個方法出現錯誤(因為斷言是針對于模擬對象的)
先定義一個郵件發送接口
public interface IEmailService { void SendEmail(string user, string subject, string content); }
模擬對象實現這個接口
public class FackEmailService : IEmailService { public string User { get; set; } public string Subject { get; set; } public string Content { get; set; } public void SendEmail(string user, string subject, string content) { this.User = user; this.Subject = subject; this.Content = content; } }
我們在看看被測試的代碼
public class UserManager { public void RecordLog(string userName) { try { if (userName.Length <= 4) //名稱長度小于4就記錄日志異常 { LogManager().ErrorLog(userName); } } catch (Exception ex) { EmailService().SendEmail("lp", "subject", ex.Message); } } protected virtual ILogService LogManager()//底層可替換 { return new LogService(); }
protectedvirtual IEmailService EmailService()
{
returnnew EmailService();
} }
創建新類繼承被測試類來完成底層替換
public class TestUserManager : UserManager { private readonly ILogService _logService; private readonly IEmailService _emailService; public TestUserManager(ILogService logService, IEmailService emailService) { _logService = logService; _emailService = emailService; } public override IEmailService EmailService() { return _emailService; } public override ILogService LogManager() { return _logService; } }
測試代碼
[Test] public void RecordLog_EmailServiceThrows_SendsEmail() { var stubLogService = new FakeLogService() {Exception = new Exception("fack exception")};//日志的模擬對象拋出異常(這個是存根) var mokeEmailService = new FackEmailService(); var testUser = new TestUserManager(stubLogService, mokeEmailService);//注入存根和模擬對象 testUser.RecordLog("lp"); StringAssert.Contains("lp",mokeEmailService.User); StringAssert.Contains("subject",mokeEmailService.Subject); StringAssert.Contains("fack exception",mokeEmailService.Content); }
我們看看測試結果
你也可以把三個屬性封裝成一個實體對實體進行斷言。
什么是對象鏈:就是一個對象的屬性是另一個對象然后這個對象的屬性又是一個對象。比喻我們經??吹降腃onfigurationManager.ConnectionStrings[0].ConnectionString這個就是一個對象鏈。
如果我們在測試的時候就發現需要偽造2個對象如果很多的話就可能偽造的更多,所以我們在重構代碼的時候就要考慮可測的代碼如下這樣就測試就可以直接替代
protected virtual string GetConnectionString() { return ConfigurationManager.ConnectionStrings[0].ConnectionString; }
新聞熱點
疑難解答