【C#】【xUnit】【Moq】.NET單元測試Mock框架Moq初探!

在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的強大之處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章