在TDD開發模型中,經常是在編碼的同時進行單元測試的編寫,由于現代軟體開發不可能是一個人完成的工作,是以在定義好接口的時候我們就可以進行自己功能的開發(接口不能經常變更),而我們調用他人的功能時隻需要使用接口即可。
但我們在編寫自己的單元測試并進行功能驗證的時候,如果接口的實作人還沒有完成代碼怎麼辦呢?一般我們可能會自己寫一個模拟實作來進行單元測試,這就是我們經常所說的單元測試中的Stub和Mock(關于單元測試的Stub和Mock,可以自己度娘一下,也可以參考
https://www.cnblogs.com/TankXiao/archive/2012/03/06/2366073.html, 本文的部分代碼來自于這篇部落格)。在.net環境中可以使用的Mock架構是Moq,目前版本4.10。
我們使用NuGet安裝依賴的庫xUnit,Moq等。
我們定義兩個接口:
public interface IWebService
{
void LogError(string msg);
}
public interface IEmailService
{
void SendEmail(string a, string b, string c, string d);
}
一個類:
public class LogAnalyzer
{
private IWebService service;
private IEmailService email;
public IWebService Service
{
get { return service; }
set { service = value; }
}
public IEmailService Email
{
get { return email; }
set { email = value; }
}
public void Analyze(string fileName)
{
if (fileName.Length < 8)
{
try
{
service.LogError("the file name is to short" + fileName);
}
catch (Exception e)
{
email.SendEmail("[email protected]", "[email protected]", "IWebServiceFailed", e.Message);
}
}
}
}
我們要進行這個類的測試,其中兩個接口的實作是别人來做。我在自己的單元測試中不想去引用他人的實作,也不想自己寫Mock,是以使用架構Moq來建立我想要的對象。
public class LogAnalyzerTest
{
[Fact(DisplayName = "使用MOQ架構")]
public void AnalyzeTest()
{
var mockWebService = new Mock<IWebService>();
mockWebService.Setup(p => p.LogError(It.Is<string>(str => str.Length > 8))).Throws(new Exception());
var mockEmailService = new Mock<IEmailService>();
var a = mockEmailService.Setup(e => e.SendEmail("[email protected]", "[email protected]", "IWebServiceFailed", It.Is<string>(x=>x != null)));
LogAnalyzer log = new LogAnalyzer();
log.Service = mockWebService.Object;
log.Email = mockEmailService.Object;
log.Analyze("xxx");
mockEmailService.Verify(p => p.SendEmail("[email protected]", "[email protected]", "IWebServiceFailed", It.Is<string>(x => x != null)));
}
}
這樣我就完成了我的單元測試,而不用去關心我的依賴的代碼的實作。保證我的功能的正确性。
對上面Mock的說明如下:
第一個模拟LogError抛出異常的代碼:
mockWebService.Setup(p => p.LogError(It.Is<string>(str => str.Length > 8))).Throws(new Exception());
第一行,當參數類型是string且長度大于8時正常執行,而長度長于等于8時則抛出異常。他的另一種寫法是範型:
mockWebService.Setup(p => p.LogError(It.Is<string>(str => str.Length > 8))).Throws<Exception>();
在我調用分析方法Analyze時傳入的字元串不長于8個,就會完成異常抛出異常的功能。
第二個是Email接口的Mock對象,建立如下:
var a = mockEmailService.Setup(e => e.SendEmail("[email protected]", "[email protected]", "IWebServiceFailed", It.Is<string>(x=>x != null)));
因為最後一個參數是異常的Message,是以我們需要動态指定。前三個參數和代碼中一緻。
最後驗證SendEmail有沒有執行。這行代碼不能放在log.Analyze調用之前。因為這個時候方法還沒有調用,單元測試不會通過。并且參數保持一緻。如果參數不一緻(特别是前三個)也會測試失敗。這就是Mock的強大之處。
你的支援是我繼續的動力啊。