<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      系列目錄 <https://www.cnblogs.com/tylerzhou/p/11204826.html>

      Moq庫(kù)簡(jiǎn)介及安裝

      Moq簡(jiǎn)介

      Moq是.net平臺(tái)下的一個(gè)非常流行的模擬庫(kù),只要有一個(gè)接口它就可以動(dòng)態(tài)生成一個(gè)對(duì)象,底層使用的是Castle的動(dòng)態(tài)代理功能.


      它的流行賴(lài)于依賴(lài)注入模式的興起,現(xiàn)在越來(lái)越多的分層架構(gòu)使用依賴(lài)注入的方式來(lái)解耦層與層之間的關(guān)系.最為常見(jiàn)的是數(shù)據(jù)層和業(yè)務(wù)邏輯層之間的依賴(lài)注入,業(yè)務(wù)邏輯層不再?gòu)?qiáng)依賴(lài)數(shù)據(jù)層對(duì)象,而是依賴(lài)數(shù)據(jù)層對(duì)象的接口,在IOC容器里完成依賴(lài)的配置.

      這種解耦給單元測(cè)試帶來(lái)了巨大的便利,使得對(duì)業(yè)務(wù)邏輯的測(cè)試可以脫離對(duì)數(shù)據(jù)層的依賴(lài),單元測(cè)試的粒度更小,更容易排查出問(wèn)題所在.


      大家可能都知道,數(shù)據(jù)層的接口往往有很多方法,少則十幾個(gè),多則幾十個(gè).我們?nèi)绻趩卧獪y(cè)試的時(shí)候把接口切換為假實(shí)現(xiàn),即使實(shí)現(xiàn)類(lèi)全是空也需要大量代碼,并且這些代碼不可重用,一旦接口層改變不但要更改真實(shí)數(shù)據(jù)層實(shí)現(xiàn)還要修改這些專(zhuān)為測(cè)試做的假實(shí)現(xiàn).這顯然是不小的工作量.

      幸好有Moq,它可以在編譯時(shí)動(dòng)態(tài)生成接口的代理對(duì)象.大大提高了代碼的可維護(hù)性,同時(shí)也極大減少工作量.

      除了動(dòng)態(tài)創(chuàng)建代理外,Moq還可以進(jìn)行行為測(cè)試,觸發(fā)事件等.

      Moq安裝

      Moq安裝非常簡(jiǎn)單,在Nuget里面搜索moq,第一個(gè)結(jié)果便是moq框架,點(diǎn)擊安裝即可.

      Moq簡(jiǎn)單使用

      本示例中要使用到的代碼如下
      public class MyDto { public string Name { get; set; } public int Age { get;
      set; } } public interface IDataBaseContext<out T> where T:new() { T
      GetElementById(string id); IEnumerable<T> GetAll(); IEnumerable<T>
      GetElementsByName(string name); IEnumerable<T> GetPageElementsByName(string
      name, int startPage = 0, int pageSize = 20); IEnumerable<T>
      GetElementsByDate(DateTime? startDate, DateTime? endDate); } public class MyBll
      { private readonly IDataBaseContext<MyDto> _dataBaseContext; public
      MyBll(IDataBaseContext<MyDto> dataBaseContext) { _dataBaseContext =
      dataBaseContext; } public MyDto GetADto(string id) { if
      (string.IsNullOrWhiteSpace(id)) return null; return
      _dataBaseContext.GetElementById(id); } }
      MyDto為業(yè)務(wù)層和數(shù)據(jù)層交互的對(duì)象,IDataBaseContext為數(shù)據(jù)層接口,MyBll為我們的業(yè)務(wù)邏輯層


      我們要測(cè)試的是業(yè)務(wù)邏輯層的代碼.這里業(yè)務(wù)邏輯類(lèi)并沒(méi)有無(wú)參構(gòu)造函數(shù),如果手動(dòng)創(chuàng)建起來(lái)非常麻煩,里面的坑前面說(shuō)過(guò).下面看如何使用Moq來(lái)模擬一個(gè)IDataBaseContext對(duì)象

      我們編寫(xiě)以下測(cè)試類(lèi)
      [Test] public void SimpleTest() { var moq = new
      Mock<IDataBaseContext<MyDto>>(); MyBll bll = new MyBll(moq.Object); var result
      = bll.GetADto(null); Assert.Null(result); }
      由于bll的GetADto如果傳的參數(shù)是null或者空就會(huì)返回一個(gè)null對(duì)象,因些返回的結(jié)果是Null,以上測(cè)試會(huì)通過(guò).

      這里我們首先創(chuàng)建了一個(gè)moq對(duì)象,它的Object屬性就是我們要模擬的IDataBaseContext對(duì)象,我們?cè)趧?chuàng)建MyBll對(duì)象時(shí)把它作為參數(shù)傳入.

      Moq基本配置

      我們?cè)贋镸yBll添加以下方法
      public IEnumerable<MyDto> GetDtos(string name) { if
      (string.IsNullOrWhiteSpace(name)) return null; var dtos =
      _dataBaseContext.GetElementsByName(name); return dtos; }
      我們編寫(xiě)如下測(cè)試方法
      [Test] public void ShouldReturn_A_Collection_Of_Dtos() { var moq = new
      Mock<IDataBaseContext<MyDto>>(); MyBll bll = new MyBll(moq.Object); var dtos =
      bll.GetDtos("sto"); }

      以上測(cè)試方法調(diào)用了bll的GetDtos方法,我們知道GetDtos內(nèi)部調(diào)用了數(shù)據(jù)訪問(wèn)接口的GetElementsByName方法,我們?cè)谡{(diào)試模式下看看返回的結(jié)果是什么.



      它返回了一個(gè)空集合,實(shí)際上不管我們提供的是什么樣的字符串,它都返回一個(gè)空集合,這是默認(rèn)行為,因?yàn)?br>_dataBaseContext.GetElementsByName并不知道我們的真實(shí)邏輯是什么.

      這樣很顯然并不是總能滿(mǎn)足我們的要求,很多時(shí)候我們?cè)跍y(cè)試業(yè)務(wù)邏輯層的時(shí)候需要具體的數(shù)據(jù),然后才能繼續(xù)往下走.

      比如以下方法,我們獲取數(shù)據(jù)庫(kù)里的所有數(shù)據(jù),然而通過(guò)一系列邏輯進(jìn)行過(guò)濾,最終返回過(guò)濾后的結(jié)果.
      public IEnumerable<MyDto> GetAllDtos() { var all =
      _dataBaseContext.GetAll().ToList(); if (!all.Any()) return
      Enumerable.Empty<MyDto>(); //一系列邏輯... var filteredDtos = all.Where(a => a.Age >
      20); var orderDtos = filteredDtos.OrderBy(a => a.Name); return orderDtos; }

      如果是默認(rèn)行為(調(diào)用模擬的接口方法,引用對(duì)象返回null,集合返回空,簡(jiǎn)單對(duì)象返回默認(rèn)值),則代碼很快就返回了,if下面的業(yè)務(wù)邏輯測(cè)不到了.下面我們看下如何配置接口方法的返回值

      這里其實(shí)主要用到了 新建moq對(duì)象的setup方法,我們可以在setup里設(shè)置方法,屬性的值.
      [Test] public void ShouldReturn_A_Collection_Of_Dtos() { var moq = new
      Mock<IDataBaseContext<MyDto>>(); moq.Setup(a => a.GetAll()).Returns(new
      List<MyDto> { new MyDto{Name="baidu",Age=15}, new MyDto{Name="sto",Age=32}, new
      MyDto{Name="zto",Age=24}, new MyDto{Name="yto",Age=12} }); MyBll bll = new
      MyBll(moq.Object); var dtos = bll.GetAllDtos().ToList();
      dtos.Should().HaveCount(2); dtos.Select(a =>
      a.Name).Should().BeInAscendingOrder(); }

      我們看以上代碼,我們我們讓數(shù)據(jù)訪問(wèn)接口的代理對(duì)象返回一個(gè)MyDto類(lèi)型集合,一共四個(gè)元素,由我們的業(yè)務(wù)可知,我們只要年齡大于20的元素,并且名字按正序排列.因此以上測(cè)試應(yīng)該返回成功,實(shí)際上也是測(cè)試通過(guò)了.

      帶參數(shù)的方法設(shè)置


      以上的GetAll是不帶參數(shù)的,帶參數(shù)的方法我們可以顯式的指定一個(gè)參數(shù),我們也可以使用Moq框架提供的方法來(lái)模糊指定參數(shù),比如我們可以指定方法是任意字符,任意數(shù)字,任意范圍的數(shù)字等.

      我們?cè)倏辞懊娴囊粋€(gè)方法
      public MyDto GetADto(string id) { if (string.IsNullOrWhiteSpace(id)) return
      null; return _dataBaseContext.GetElementById(id); }
      這個(gè)方法接收一個(gè)類(lèi)型為字符串的id,只要字符串不是空字符串或者null時(shí)我們都返回一個(gè)MyDto對(duì)象.

      測(cè)試方法如下
      [Test] public void ShouldReturn_A_Dto_If_QueryBy_Id_With_Valid_Parameter() {
      var moq = new Mock<IDataBaseContext<MyDto>>(); moq.Setup(a =>
      a.GetElementById(It.IsAny<string>())).Returns(new MyDto()); MyBll bll = new
      MyBll(moq.Object); var dto = bll.GetADto("afakeid"); dto.Should().NotBeNull(); }

      這里我們使用到了Moq里的It.Is方法,這個(gè)方法接受一個(gè)Func<T,bool>類(lèi)型的委托,我們的條件是不管它是一個(gè)什么樣的string,總是返回一個(gè)new
      MyDto();

      [warning]
      注意這里配置的是Moq對(duì)象(即moq.Object)的方法返回值,而不是bll對(duì)象的方法的返回值,如果我們傳入的字符串是空字符串,則GetADto直接返回了null,數(shù)據(jù)訪問(wèn)對(duì)象就沒(méi)機(jī)會(huì)執(zhí)行了.

      It里面還有很多靜態(tài)方法,用于指定數(shù)字是否是否在某一范圍,對(duì)象是否是列表中的對(duì)象,字符串是否滿(mǎn)足正則等.語(yǔ)義都非常明確,大家可以自己研究一下.

      指定參數(shù)的配置

      以上使用到了It.IsAny方法.It里面還有一個(gè)Is方法,接受一個(gè)Func<T,bool>類(lèi)型委托,用于指定對(duì)象為滿(mǎn)足特定條件的對(duì)象,而不是任意對(duì)象.

      Bll層新增以下方法
      public bool IsVip(string id) { if (string.IsNullOrWhiteSpace(id)) return
      false; var dto = _dataBaseContext.GetElementById(id); if
      (dto?.Name?.Contains("sto")) return true; return false; }
      我們判斷一個(gè)dto是否是vip,如果傳入id為null返回false,如果不是則獲取一個(gè)對(duì)象,如果對(duì)象的名字包含sto關(guān)鍵字則返回true

      比如我們知道id為9527的對(duì)象為sto,因此它是個(gè)vip,我們的測(cè)試方法如下
      [Test] public void ShouldReturn_True_If_Id_Is_9527() { var moq = new
      Mock<IDataBaseContext<MyDto>>(); moq.Setup(a =>
      a.GetElementById(It.Is<string>(t => t.Trim() == "9527"))).Returns(new MyDto {
      Name = "sto", Age = 24 }); MyBll bll = new MyBll(moq.Object); bool isVip =
      bll.IsVip("9527"); Assert.True(isVip); }
      以上測(cè)試通過(guò).

      MOCk.Of


      我們以上僅配置了接口代表的一個(gè)方法,有時(shí)候需要配置多個(gè),這樣需要多個(gè)Setup,這時(shí)候我們可以使用Mock.Of,注意Mock.Of創(chuàng)建出來(lái)的是一個(gè)代理對(duì)象,而不是一個(gè)mock對(duì)象.
      [Test] public void MockOf_Test() { var obj =
      Mock.Of<IDataBaseContext<MyDto>>(a =>a.GetAll()==new List<MyDto>(){new MyDto()}
      &&a.GetElementById(It.IsAny<string>())==new MyDto()
      &&a.GetElementsByName(It.IsAny<string>())==new MyDto[3]); var all =
      obj.GetAll(); var one = obj.GetElementById("s"); var some =
      obj.GetElementsByName("somename"); Assert.Multiple(() => { Assert.AreEqual(1,
      all.Count()); Assert.NotNull(one); Assert.AreEqual(3, some.Count()); }); }
      以上測(cè)試會(huì)通過(guò).

      注意以上的xxx==xxx并不是比較兩個(gè)對(duì)象,Mock利用它進(jìn)行賦值


      很多初接觸單元測(cè)試的朋友看完以上代碼后可能感覺(jué)一臉懵,完全不理解利用moq在dao層生成一些看似無(wú)意義的假數(shù)據(jù)有什么意義,其實(shí)大家要明白單元測(cè)試的目的是什么,單元測(cè)試是以代碼塊為基礎(chǔ)(通常是一個(gè)方法),測(cè)試這一個(gè)單元邏輯的正確性,在dao層,我們只關(guān)心這一層拿到數(shù)據(jù)后的處理邏輯.很多朋友可能知道ef可以搭建內(nèi)存服務(wù)器來(lái)模擬真實(shí)數(shù)據(jù)庫(kù),這樣也同樣不依賴(lài)于外部的數(shù)據(jù)庫(kù).其實(shí)大家也可以這樣做,也可以不這樣而使用moq來(lái)模擬一個(gè)數(shù)據(jù)庫(kù)連接上下文對(duì)象.因?yàn)樵趩卧獪y(cè)試?yán)?真實(shí)的數(shù)據(jù)是什么樣的并不是首要關(guān)心的問(wèn)題,而是這個(gè)代碼單元邏輯的正確性.如果是做集成測(cè)試,我們則需要模擬一個(gè)真實(shí)環(huán)境,這個(gè)時(shí)候我們就需要使用內(nèi)存服務(wù)器甚至使用外部服務(wù)器.當(dāng)然,如果要做壓力測(cè)試,我們還需要模擬產(chǎn)品運(yùn)行時(shí)真實(shí)的物理環(huán)境,網(wǎng)絡(luò)環(huán)境等條件(當(dāng)然,很多時(shí)候直接在真實(shí)的運(yùn)行環(huán)境進(jìn)行測(cè)試了).總之我們要搞清楚不同的測(cè)試要解決什么樣的問(wèn)題,要達(dá)到什么樣的目的,剩下的才是工具框架的使用.

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問(wèn)題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          免费艹逼网站 | 欧美美女裸体艺术 | 久久精品三级视频 | 大鸡巴日骚逼 | 日本美女黄色电影 | 久久精品yywww麻豆md一区 | 一二区无码 | 欧美日日澡夜夜澡 | 91人人爱| 精品欧美乱码久久久久久 |