[UWP] 如何作 簡單的UnitTest with Stub(基於微軟原生的MSTest + SimpleStub)

  近期應項目須要,筆者瘋狂學習UWP項目測試的知識,確實學習到了很多關於測試方面的知識,等有時間會好好地記錄下測試的學習成果。OK,回到主題,在筆者作項目測試的過程當中把本身遇到的坑都簡單的記錄了下來,而且寫了個關於測試的Demo,傳送門。下面簡單地筆者的初入測試門檻的經驗!ios

  首先上環境git

  • Windows 10 OS
  • Windows 10 SDK 10.0.15860
  • Visual Studio 2017
  • MSTest Framework 1.2.0
  • Etg.SimpleStubs 2.4.6

  使用到的測試框架:MSTest 提供的 UnitTestFramework + 微軟 Microsoft BigPark Studios團隊開發的 SimpleStub Mock框架。github

  處於筆者的水平限制,筆者前後嘗試了 NUnitXUnit 等多款測試框架可是效果並非太好,其中 NUnit 彷佛對 UnitTestProject(單元測試應用)並不太友好(多半是筆者水平的問題,若是有Dalao能教下我NUnit與UWP的集成請私信),網上的dalao們使用 XUnit 多數是基於 ClassLibrary 的項目,而筆者嘗試的時候發現系統沒法直接調用 ClassLibrary 所書寫的測試 ,須要加載 Runner ,有些小麻煩,不過應該也是能用的。最終,多番嘗試後筆者敲定了使用 MSTest + SimpleStub這兩款框架(都是來自微軟,相信與UWP兼容性不錯)。框架

  MSTest比較簡單,上述的MSDN傳送門對應的網頁有詳細的指導,而 SampleStub 則須要注意挺多的,筆者將本身在使用過程當中踩的一些可能比較常見的坑記錄了下來,下面上筆者記錄的小坑:函數

摘自筆者Demo項目的README:單元測試

  做爲一個剛入測試的小菜雞,做者在結合使用這兩個框架的時候也遇到了不少坑,下面簡單列舉一些萌新們可能會踩的坑:學習

  1. UWP 新建一個UnitTest項目?

答:UWP 的單元測試項目優選 單元測試應用 類型的項目,即新建一個單元測試應用的項目,而且在其中書寫本身的測試測試

  1. 爲何在新建的UnitTestProject內新建測試文件不會在測試列表中顯示?

答:測試文件目前使用的是註解機制,其中對做爲TestFixture的測試類以及測試方法都是有訪問權限要求的,即必須都爲 public 的 ,而且測試方法 Method 必須爲 public void 類型的。spa

  1. 測試經常使用的東西: Assert 類的諸多方法,Such as: AreEqual AreSame IsTruecode

  2. 如何在目前的Unit測試引入Mock框架?

答:目前的狀況,做者嘗試過許多框架,出於做者水平,這些框架(NUnit Template 不支持、XUnit 換成Library沒法識別測試)在做者的UWP上表現都不太好。目前發現表現最好的是微軟去年年底推出的 SimpleStub 框架,而後做者也推薦使用這個,由於做者我的認爲這個是目前與 MSTest 兼容最好的Mock框架。

  1. SimpleStub 如何使用?

答:Nuget -> download SimpleStub nuget package 。而後環境就已經搭建好了,選擇你的測試項目進行生成,此時框架會自動在編譯期間在項目obj目錄下生成一個 SimpleStub.generate.cs 的文件。利用這個文件生成一個 StubXxxx 的Mock對象進而實現測試,詳細用法見樣例代碼( UnitTestProject1 內的 TestUserMagr.cs

Tips:

  • 這裏補充說明下,若是項目生成後在obj目錄下沒找到 SimpleStub.generate.cs 文件,說明生成 SimpleStub.generate.cs 文件失敗了,此時應該去查看Console(輸出控制檯)的輸出內容,挖掘出錯誤的緣由,解決異常後從新生成下便可!
  • 同時須要多留意 SimpleStub 框架爲咱們生成的 StubXxx Mock對象的一些方法構建,由於這與構建的Mock對象的 MockBehavior 息息相關,詳細請見示例代碼

  最後,上筆者Demo中測試的部分代碼:

namespace UnitTestProject1
{
    [TestClass]
    public class TestUserMagr
    {
        // 待測試對象-> uMagr  Mock對象->uMagr.UDao(IUserDao)
        private UserMagr uMagr;        

        [TestInitialize]
        public void BeforeTestInitialize()
        {
            uMagr = new UserMagr();
        }

        
        [TestMethod]
        public void TestCallSequence()
        {

            // 新建mock對象
            var stub = new StubIUserDao(MockBehavior.Strict)
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Once)// 限定最多調用一次
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Twice)// 限定最多調用兩次
                .CreateUser((p2) => { return null == p2 ? 0 : 1; }, Times.Forever);// 解除調用次數上限設置
                                                                                   // 若是不執行上述最後一行,那麼stub對象只能執行CreateUser 兩次(取決於最後一條的限定),執行超出次數會拋異常

            // 將Mock對象賦值給依賴對象,從而解除依賴對象對測試的干預
            uMagr.UDao = stub;
            // 從這開始執行測試 相似 EasyMock->replayAll(); 
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
            Assert.AreEqual(1, uMagr.CreateUser(new User() { Username = "123", Password = "123" }));
        }

        [TestMethod]
        public void TestMethod_WithReturnType_WithParameters()// 測試Mock對象的函數帶參數的調用
        {
            // 設置一些初始變量
            int Expect = 1;
            string Username = null;
            string Password = null;
            // 用於判斷是否修改的對象
            User user = new User() { Username = Username, Password = Password };
            // 開始構建Mock
            var stub = new StubIUserDao()
                .CreateUser((u) => { user = u; return Expect; });
            uMagr.UDao = stub;
            // replayAll()
            Assert.AreEqual(Expect, uMagr.CreateUser(new User() { Username = "test" }));
            Assert.AreEqual("test", user.Username);
        }

        [TestMethod]
        public void TestThatMethodStubCanBeOverwritten()// 測試Mock對象的方法是否支持重寫
        {
            // 構建一個Mock對象
            var stub = new StubIUserDao()
                .CreateUser(u => 1);
            // 重寫其調用的方法,若是須要重寫則須要加上overwrite:true,不然將不會重寫
            stub.CreateUser(u => 2, overwrite: true);
            uMagr.UDao = stub;
            // replayAll()
            Assert.AreEqual(2, uMagr.CreateUser(new User()));
        }

        [TestMethod]
        [ExpectedException(typeof(SimpleStubsException))]
        public void TestMethod_WithReturnType_WithParameters_DefaultBehavior_Strict()// 展現 MockBehavior爲Strict下的不一樣之處,於下面的method一塊兒比對觀察
        {
            // 構建一個不帶函數定義的Mock對象(存根)
            var stub = new StubIUserDao(MockBehavior.Strict);
            uMagr.UDao = stub;
            // 調用方法的時候,Strict模式下會拋出異常,由於沒有定義調用函數
            Assert.AreEqual(0, uMagr.CreateUser(null));
        }

        [TestMethod]
        public void TestMethod_Void_WithNoParameters_DefaultBehavior_Loose()
        {
            // 構建一個不帶函數定義的Mock對象(存根),默認下MockBehavior爲Loose
            var stub = new StubIUserDao();
            uMagr.UDao = stub;
            // 調用方法的時候,Loose模式下不會拋出異常,而且返回函數返回值的默認值 null | 默認基本類型值 | 
            Assert.AreEqual(0, uMagr.CreateUser(null));
        }       
    }
}

OK,就先記錄到這!

附:感謝多位dalao超棒的回答:

  • In Github:

What should I do if I want to mock an object ? Order of Execution ?

  • In StackOverFlow:

What is the difference between MockBehavior.Loose and MockBehavior.Strict in SimpleStub?

相關文章
相關標籤/搜索