<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      Fork me on GitHub

      基于VS2012 Fakes框架的TDD實(shí)戰(zhàn)——接口模擬

      前言

        最近團(tuán)隊(duì)要嘗試TDD(測(cè)試驅(qū)動(dòng)開發(fā))的實(shí)踐,很多人習(xí)慣了先代碼后測(cè)試的流程,對(duì)于TDD總心存恐懼,認(rèn)為沒有代碼的情況下寫測(cè)試代碼時(shí)被架空了,沒法寫下來,其實(shí),根據(jù)個(gè)人實(shí)踐經(jīng)驗(yàn),TDD并不可怕,還很可愛,只要你真正去實(shí)踐了幾十個(gè)測(cè)試用例之后,你會(huì)愛上這種開發(fā)方式的。微軟對(duì)于TDD的開發(fā)方式是大力支持和推薦的,新發(fā)布的VS2012的團(tuán)隊(duì)模板就是根據(jù)。新的Visual Studio 2012給我們帶來了Fakes框架,這是一個(gè)針對(duì)代碼測(cè)試時(shí)對(duì)測(cè)試的外界依賴(如數(shù)據(jù)庫,文件等)進(jìn)行模擬的Mock框架,用上了之后,我立即從Moq的陣營中叛變了^_^。截止到寫此文的時(shí)間,網(wǎng)上還沒有一篇關(guān)于Fakes框架的文章(除了“VS11將擁有更好的單元測(cè)試工具和Fakes框架”這篇介紹性的之外),就讓我們來慢慢摸索著用吧。廢話少說,下面我們就來一步一步的使用Visual Studio 2012的Fakes框架來實(shí)戰(zhàn)一把TDD。

      需求說明

        我們要做的是一個(gè)普通的用戶注冊(cè)中“檢查用戶名是否存在”的功能,需求如下:

      1. 用戶名不能重復(fù)
      2. 可設(shè)置是否啟用郵件激活,如果不啟用郵件激活,則直接在“正式用戶信息表”中檢查,反之則還要進(jìn)入“未激活用戶信息表”中進(jìn)行查詢

      項(xiàng)目結(jié)構(gòu)

        先分解一下項(xiàng)目的結(jié)構(gòu),還是傳統(tǒng)的三層結(jié)構(gòu),從底層到上層:

      1. Liuliu.Components.Tools:通用工具組件
      2. Liuliu.Components.Data:通用數(shù)據(jù)訪問組件,目前只定義了一個(gè)數(shù)據(jù)訪問接口的通用基接口IRepository
      3. Liuliu.Demo.Core.Models:數(shù)據(jù)實(shí)體類,分兩個(gè)模塊,賬戶模塊(Account)與通用模塊(Common)
      4. Liuliu.Demo.Core:業(yè)務(wù)核心層,里面包含Business與DataAccess兩個(gè)子層,DataAccess實(shí)現(xiàn)實(shí)體類的數(shù)據(jù)訪問,Business層實(shí)現(xiàn)模塊的業(yè)務(wù)邏輯,因?yàn)闇y(cè)試的過程中數(shù)據(jù)訪問層的數(shù)據(jù)庫實(shí)現(xiàn)會(huì)用Fakes框架來模擬,所以數(shù)據(jù)訪問層只提供了接口,不提供實(shí)現(xiàn),Business只調(diào)用了DataAccess的接口。我們要做的工作就是用Fakes框架來模擬數(shù)據(jù)訪問層,用TDD的方式來編寫B(tài)usiness中的業(yè)務(wù)實(shí)現(xiàn)
      5. Liuliu.Demo.Core.Business.UnitTest:?jiǎn)卧獪y(cè)試項(xiàng)目,存放著測(cè)試Business實(shí)現(xiàn)的測(cè)試用例。
      6. Liuliu.Demo.Consoles:用戶操作控制臺(tái),功能實(shí)現(xiàn)后進(jìn)行用戶操作的UI項(xiàng)目

        其他的項(xiàng)目與測(cè)試無關(guān),略過。

      開發(fā)準(zhǔn)備

      應(yīng)用代碼準(zhǔn)備

      Entity:實(shí)體類的通用數(shù)據(jù)結(jié)構(gòu)

       1     /// <summary>
       2     ///   數(shù)據(jù)實(shí)體類基類,定義數(shù)據(jù)庫存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)的通用部分
       3     /// </summary>
       4     public abstract class Entity
       5     {
       6         /// <summary>
       7         ///   編號(hào)
       8         /// </summary>
       9         public int Id { get; set; }
      10 
      11         /// <summary>
      12         ///   是否邏輯刪除(相當(dāng)于回收站,非物理刪除)
      13         /// </summary>
      14         public bool IsDelete { get; set; }
      15 
      16         /// <summary>
      17         ///   添加時(shí)間
      18         /// </summary>
      19         public DateTime AddDate { get; set; }
      20     }

      IRepository:通用數(shù)據(jù)訪問接口,簡(jiǎn)單起見,只寫了幾個(gè)增刪改查的接口

       1     /// <summary>
       2     /// 定義倉儲(chǔ)模式中的數(shù)據(jù)標(biāo)準(zhǔn)操作,其實(shí)現(xiàn)類是倉儲(chǔ)類型。
       3     /// </summary>
       4     /// <typeparam name="TEntity">要實(shí)現(xiàn)倉儲(chǔ)的類型</typeparam>
       5     public interface IRepository<TEntity> where TEntity : Entity
       6     {
       7         #region 公用方法
       8 
       9         /// <summary>
      10         ///   插入實(shí)體記錄
      11         /// </summary>
      12         /// <param name="entity"> 實(shí)體對(duì)象 </param>
      13         /// <param name="isSave"> 是否執(zhí)行保存 </param>
      14         /// <returns> 操作影響的行數(shù) </returns>
      15         int Insert(TEntity entity, bool isSave = true);
      16 
      17         /// <summary>
      18         ///   刪除實(shí)體記錄
      19         /// </summary>
      20         /// <param name="entity"> 實(shí)體對(duì)象 </param>
      21         /// <param name="isSave"> 是否執(zhí)行保存 </param>
      22         /// <returns> 操作影響的行數(shù) </returns>
      23         int Delete(TEntity entity, bool isSave = true);
      24 
      25         /// <summary>
      26         ///   更新實(shí)體記錄
      27         /// </summary>
      28         /// <param name="entity"> 實(shí)體對(duì)象 </param>
      29         /// <param name="isSave"> 是否執(zhí)行保存 </param>
      30         /// <returns> 操作影響的行數(shù) </returns>
      31         int Update(TEntity entity, bool isSave = true);
      32 
      33         /// <summary>
      34         /// 提交當(dāng)前的Unit Of Work事務(wù),作用與 IUnitOfWork.Commit() 相同。
      35         /// </summary>
      36         /// <returns>提交事務(wù)影響的行數(shù)</returns>
      37         int Commit();
      38 
      39         /// <summary>
      40         ///   查找指定編號(hào)的實(shí)體記錄
      41         /// </summary>
      42         /// <param name="id"> 指定編號(hào) </param>
      43         /// <returns> 符合編號(hào)的記錄,不存在返回null </returns>
      44         TEntity GetById(object id);
      45 
      46         /// <summary>
      47         /// 查找指定名稱的實(shí)體記錄,注意:如實(shí)體無名稱屬性則不支持
      48         /// </summary>
      49         /// <param name="name">名稱</param>
      50         /// <returns>符合名稱的記錄,不存在則返回null</returns>
      51         /// <exception cref="NotSupportedException">當(dāng)對(duì)應(yīng)實(shí)體無名稱時(shí)引發(fā)將引發(fā)異常</exception>
      52         TEntity GetByName(string name);
      53 
      54         #endregion
      55     }

      Member:實(shí)體類——用戶信息

       1     /// <summary>
       2     ///   實(shí)體類——用戶信息
       3     /// </summary>
       4     public class Member : Entity
       5     {
       6         public string UserName { get; set; }
       7 
       8         public string Password { get; set; }
       9 
      10         public string Email { get; set; }
      11     }

      MemberInactive:實(shí)體類——未激活用戶信息

       1     /// <summary>
       2     ///   實(shí)體類——未激活用戶信息
       3     /// </summary>
       4     public class MemberInactive : Entity
       5     {
       6         public string UserName { get; set; }
       7 
       8         public string Password { get; set; }
       9 
      10         public string Email { get; set; }
      11     }

      ConfigInfo:實(shí)體類——系統(tǒng)配置信息

       1     /// <summary>
       2     ///   實(shí)體類——系統(tǒng)配置信息
       3     /// </summary>
       4     public class ConfigInfo : Entity
       5     {
       6         public ConfigInfo()
       7         {
       8             RegisterConfig = new RegisterConfig();
       9         }
      10 
      11         public RegisterConfig RegisterConfig { get; set; }
      12     }
      13 
      14 
      15     public class RegisterConfig
      16     {
      17         /// <summary>
      18         ///   注冊(cè)時(shí)是否需要Email激活
      19         /// </summary>
      20         public bool NeedActive { get; set; }
      21 
      22         /// <summary>
      23         ///   激活郵件有效期,單位:分鐘
      24         /// </summary>
      25         public int ActiveTimeout { get; set; }
      26 
      27         /// <summary>
      28         ///   允許同一Email注冊(cè)不同會(huì)員
      29         /// </summary>
      30         public bool EmailRepeat { get; set; }
      31     }

      IMemberDao:數(shù)據(jù)訪問接口——用戶信息,僅添加IRepository不滿足的接口

       1     /// <summary>
       2     ///   數(shù)據(jù)訪問接口——用戶信息
       3     /// </summary>
       4     public interface IMemberDao : IRepository<Member>
       5     {
       6         /// <summary>
       7         ///   由電子郵箱查找用戶信息
       8         /// </summary>
       9         /// <param name="email"> 電子郵箱地址 </param>
      10         /// <returns> </returns>
      11         IEnumerable<Member> GetByEmail(string email);
      12     }

      IMemberInactiveDao:數(shù)據(jù)訪問接口——未激活用戶信息,僅添加IRepository不滿足的接口

       1     /// <summary>
       2     ///   數(shù)據(jù)訪問接口——未激活用戶信息
       3     /// </summary>
       4     public interface IMemberInactiveDao : IRepository<MemberInactive>
       5     {
       6         /// <summary>
       7         ///   由電子郵箱獲取未激活的用戶信息
       8         /// </summary>
       9         /// <param name="email"> 電子郵箱地址 </param>
      10         /// <returns> </returns>
      11         IEnumerable<MemberInactive> GetByEmail(string email);
      12     }

      IConfigInfoDao:數(shù)據(jù)訪問接口——系統(tǒng)配置,無額外需求的接口,所以為空接口

      1     /// <summary>
      2     ///   數(shù)據(jù)訪問接口——系統(tǒng)配置信息
      3     /// </summary>
      4     public interface IConfigInfoDao : IRepository<ConfigInfo> 
      5     { }

      IAccountContract:賬戶模塊業(yè)務(wù)契約——定義了三個(gè)操作,用作注冊(cè)前的數(shù)據(jù)檢查和注冊(cè)提交

       1     /// <summary>
       2     ///   核心業(yè)務(wù)契約——賬戶模塊
       3     /// </summary>
       4     public interface IAccountContract
       5     {
       6         /// <summary>
       7         /// 用戶名重復(fù)檢查
       8         /// </summary>
       9         /// <param name="userName">用戶名</param>
      10         /// <param name="configName">系統(tǒng)配置名稱</param>
      11         /// <returns></returns>
      12         bool UserNameExistsCheck(string userName, string configName);
      13 
      14         /// <summary>
      15         /// 電子郵箱重復(fù)檢查
      16         /// </summary>
      17         /// <param name="email">電子郵箱</param>
      18         /// <param name="configName">系統(tǒng)配置名稱</param>
      19         /// <returns></returns>
      20         bool EmailExistsCheck(string email, string configName);
      21         
      22         /// <summary>
      23         /// 用戶注冊(cè)
      24         /// </summary>
      25         /// <param name="model">注冊(cè)信息模型</param>
      26         /// <param name="configName">系統(tǒng)配置名稱</param>
      27         /// <returns></returns>
      28         RegisterResults Register(Member model, string configName);
      29     }

      以上代碼本來想收起來的,但測(cè)試時(shí)代碼展開老失效,所以辛苦大家劃了那麼長的鼠標(biāo)來看下面的正題了\(^o^)/

      測(cè)試類準(zhǔn)備

      1. 添加測(cè)試項(xiàng)目的引用

      2. 添加要模擬實(shí)現(xiàn)接口的Fakes程序集,要模擬的接口在Liuliu.Demo.Core程序集中,所以在該程序集上點(diǎn)右鍵,選擇“添加Fakes程序集”菜單項(xiàng)

      3. 添加好了之后,F(xiàn)akes框架會(huì)在測(cè)試項(xiàng)目中添加一個(gè)Fakes文件夾和一個(gè)配置文件,并自動(dòng)生成引用一個(gè) 模擬程序集.Fakes 的程序集和Fakes框架的運(yùn)行環(huán)境Microsoft.QualityTools.Testing.Fakes

      4. 打開對(duì)象查看器,可看到生成的Fakes程序集的內(nèi)容,所有的接口都生成了一個(gè)對(duì)應(yīng)的模擬類
         
      5. 通過ILSpy對(duì)Fakes程序集進(jìn)行反向,可以看到生成的模擬類如下所示,StubIMemberDao實(shí)現(xiàn)了接口IMemberDao,而接口中的公共成員都生成了“方法名+參數(shù)類型名”的委托模擬,用以接收外部給模擬方法的執(zhí)行結(jié)果賦值,這樣每個(gè)方法的返回值都可以被控制
      6. 另外生成的Fakes文件夾中的配置文件Liuliu.Demo.Core.fakes內(nèi)容如下所示
        1 <Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
        2   <Assembly Name="Liuliu.Demo.Core"/>
        3 </Fakes>

         這個(gè)配置默認(rèn)會(huì)把測(cè)試程序集中的所有接口、類都生成模擬類,當(dāng)然也可以配置生成指定的類型的模擬,相關(guān)知識(shí)這里就不講了,請(qǐng)參閱官方文檔:Microsoft Fakes 中的代碼生成、編譯和命名約定

      7. 需要特別說明的是,每次生成,F(xiàn)akes程序集都會(huì)重新生成,所以測(cè)試類有更改后想刷新Fakes程序集,只需要把原來的程序集刪除再進(jìn)行生成,或者在測(cè)試項(xiàng)目能編譯的時(shí)候重新編譯測(cè)試項(xiàng)目即可。

      TDD正式開始

      1. 給測(cè)試項(xiàng)目添加一個(gè)單元測(cè)試類文件,添加新項(xiàng) -> Visual C#項(xiàng) -> 測(cè)試 -> 單元測(cè)試,命名為AccountServiceTest.cs,推薦命名方式為“測(cè)試類名+Test”的方式
      2. 添加一個(gè)測(cè)試方法,關(guān)于測(cè)試方法的命名,各人有各人的方案,這里推薦一種方案:“測(cè)試方法名_執(zhí)行結(jié)果_得到此結(jié)果的條件/原因”,并且測(cè)試方法是可以使用中文的,比如“UserNameExistsCheck_用戶名已存在_用戶名在用戶信息表中已存在記錄”,這種方式好很多好處,特別是團(tuán)隊(duì)成員英文水平不太好的時(shí)候,如果翻譯成英文的方式,很有可能會(huì)不知所云,并且中文與需求文檔一一對(duì)應(yīng),非常明了,以下的測(cè)試用例中都會(huì)運(yùn)用這種方式,如果不適應(yīng)請(qǐng)?jiān)谀X中自行翻譯\(^o^)/,建立測(cè)試方法如下:
        1         [TestMethod]
        2         public void UserNameExistsCheck_用戶名不存在()
        3         {
        4             var userName = "柳柳英俠";
        5             var configName = "configName";
        6             var accountService = new AccountService();
        7             Assert.IsFalse(accountService.UserNameExistsCheck(userName, configName));
        8         }

         當(dāng)然,此時(shí)運(yùn)行測(cè)試是編譯不過的,因?yàn)锳ccountService類根本還沒有創(chuàng)建。在Liuliu.Demo.Core.Business.Impl文件夾下添加AccountService類,并實(shí)現(xiàn)IAccountContract接口

         1     /// <summary>
         2     /// 賬戶模塊業(yè)務(wù)實(shí)現(xiàn)類
         3     /// </summary>
         4     public class AccountService : IAccountContract
         5     {
         6         /// <summary>
         7         /// 用戶名重復(fù)檢查
         8         /// </summary>
         9         /// <param name="userName">用戶名</param>
        10         /// <param name="configName">系統(tǒng)配置名稱</param>
        11         /// <returns></returns>
        12         public bool UserNameExistsCheck(string userName, string configName)
        13         {
        14             throw new NotImplementedException();
        15         }
        16 
        17         /// <summary>
        18         /// 電子郵箱重復(fù)檢查
        19         /// </summary>
        20         /// <param name="email">電子郵箱</param>
        21         /// <param name="configName">系統(tǒng)配置名稱</param>
        22         /// <returns></returns>
        23         public bool EmailExistsCheck(string email, string configName)
        24         {
        25             throw new NotImplementedException();
        26         }
        27 
        28         /// <summary>
        29         /// 用戶注冊(cè)
        30         /// </summary>
        31         /// <param name="model">注冊(cè)信息模型</param>
        32         /// <param name="configName">系統(tǒng)配置名稱</param>
        33         /// <returns></returns>
        34         public RegisterResults Register(Member model, string configName)
        35         {
        36             throw new NotImplementedException();
        37         }
        38     }

        再次運(yùn)行測(cè)試,是通不過,TDD的基本做法就是讓測(cè)試盡快通過,所以修改方法UserNameExistsCheck為如下:

         1         /// <summary>
         2         /// 用戶名重復(fù)檢查
         3         /// </summary>
         4         /// <param name="userName">用戶名</param>
         5         /// <param name="configName">系統(tǒng)配置名稱</param>
         6         /// <returns></returns>
         7         public bool UserNameExistsCheck(string userName, string configName)
         8         {
         9             return false;
        10         }

        再次運(yùn)行測(cè)試用例,紅叉終于變成綠勾了,我敢打賭,如果你真正實(shí)踐TDD的話,綠色將是你一定會(huì)喜歡的顏色


        參數(shù)的字符串,值的有效性一定要檢查的,所以添加以下兩個(gè)測(cè)試用例,通過ExpectedException特性可能確定拋出異常的類型

         1         [TestMethod]
         2         [ExpectedException(typeof(ArgumentNullException))]
         3         public void UserNameExistsCheck_引發(fā)ArgumentNullException異常_參數(shù)userName為空()
         4         {
         5             string userName = null;
         6             var configName = "configName";
         7             var accountService = new AccountService();
         8             accountService.UserNameExistsCheck(userName, configName);
         9         }
        10 
        11         [TestMethod]
        12         [ExpectedException(typeof(ArgumentNullException))]
        13         public void UserNameExistsCheck_引發(fā)ArgumentNullException異常_參數(shù)configName為空()
        14         {
        15             var userName = "柳柳英俠";
        16             string configName = null;
        17             var accountService = new AccountService();
        18             accountService.UserNameExistsCheck(userName, configName);
        19         }

        運(yùn)行測(cè)試,結(jié)果如下,原因?yàn)檫€沒有寫異常代碼,期望的異常沒有引發(fā)。└(^o^)┘平常我們很怕出異常,現(xiàn)在要去期望出異常


        異常代碼編寫很簡(jiǎn)單,修改為如下即可通過:

         1         public bool UserNameExistsCheck(string userName, string configName)
         2         {
         3             if (string.IsNullOrEmpty(userName))
         4             {
         5                 throw new ArgumentNullException("userName");
         6             }
         7             if (string.IsNullOrEmpty(configName))
         8             {
         9                 throw new ArgumentNullException("configName");
        10             }
        11             return false;
        12         }

        給AccountService類添加如下屬性,以便在接下來的操作中能模擬調(diào)用數(shù)據(jù)訪問層的操作

         1         #region 屬性
         2 
         3         /// <summary>
         4         /// 獲取或設(shè)置 數(shù)據(jù)訪問對(duì)象——用戶信息
         5         /// </summary>
         6         public IMemberDao MemberDao { get; set; }
         7 
         8         /// <summary>
         9         /// 獲取或設(shè)置 數(shù)據(jù)訪問對(duì)象——未激活用戶信息
        10         /// </summary>
        11         public IMemberInactiveDao MemberInactiveDao { get; set; }
        12 
        13         /// <summary>
        14         /// 獲取或設(shè)置 數(shù)據(jù)訪問對(duì)象——系統(tǒng)配置信息
        15         /// </summary>
        16         public IConfigInfoDao ConfigInfoDao { get; set; }
        17 
        18         #endregion

        接下來該進(jìn)行用戶名存在的判斷了,即為在用戶信息數(shù)據(jù)庫中(MemberDao)存在相同用戶名的用戶信息,在這里的查詢實(shí)際并不是到數(shù)據(jù)庫中查詢,而是通過Fakes框架生成的模擬類模擬出一個(gè)查詢過程與獲得查詢結(jié)果。添加的測(cè)試用例如下:

         1         [TestMethod]
         2         public void UserNameExistsCheck_用戶名存在_該用戶名在用戶數(shù)據(jù)庫中已存在記錄()
         3         {
         4             var userName = "柳柳英俠";
         5             var configName = "configName";
         6             var accountService = new AccountService();
         7             var memberDao = new StubIMemberDao();
         8             memberDao.GetByNameString = str => new Member();
         9             accountService.MemberDao = memberDao;
        10             Assert.IsTrue(accountService.UserNameExistsCheck(userName, configName));
        11         }

        StubIMemberDao類即為Fakes框架由IMemberDao接口生成的一個(gè)模擬類,第7行實(shí)例化了一個(gè)該類的對(duì)象, 這個(gè)對(duì)象有一個(gè)委托類型的字段GetByNameString開放出來,我們就可以通過這個(gè)字段給接口的GetByName方法賦一個(gè)執(zhí)行結(jié)果,即第8行的操作。再把這個(gè)對(duì)象賦給AccountService類中的IMemberDao類型的屬性(第9行),即相當(dāng)于給AccountService類添加了一個(gè)操作用戶信息數(shù)據(jù)層的實(shí)現(xiàn)。
        修改UserNameExistsCheck方法使測(cè)試通過

         1         public bool UserNameExistsCheck(string userName, string configName)
         2         {
         3             if (string.IsNullOrEmpty(userName))
         4             {
         5                 throw new ArgumentNullException("userName");
         6             }
         7             if (string.IsNullOrEmpty(configName))
         8             {
         9                 throw new ArgumentNullException("configName");
        10             }
        11             var member = MemberDao.GetByName(userName);
        12             if (member != null)
        13             {
        14                 return true;
        15             }
        16             return false;
        17         }

        運(yùn)行測(cè)試,上面這個(gè)測(cè)試通過了,但第一個(gè)測(cè)試卻失敗了。


        這不合乎TDD的要求了,TDD要求后面添加的功能不能影響原來的功能。看代碼實(shí)現(xiàn)是沒有問題的,看來問題是出在測(cè)試用例上。
        當(dāng)我們走到“UserNameExistsCheck_用戶名存在_該用戶名在用戶數(shù)據(jù)庫中已存在記錄”這個(gè)測(cè)試用例的時(shí)候,添加了一些屬性,而這些屬性在第一個(gè)測(cè)試用例“UserNameExistsCheck_用戶名不存在”并沒有進(jìn)行初始化,所以報(bào)了一個(gè)NullReferenceException異常。
        接下來我們來優(yōu)化測(cè)試類的結(jié)構(gòu)來解決這些問題:
        a. 每個(gè)測(cè)試用例的先決條件都要從0開始初始化,太麻煩
        b. 測(cè)試環(huán)境沒有初始化,新增條件會(huì)影響到舊的測(cè)試用例的運(yùn)行

      3. 根據(jù)以上提出的問題,給出下面的解決方案
        a. 進(jìn)行公共環(huán)境的初始化,即讓所有測(cè)試用例在相同的環(huán)境下運(yùn)行
        b. 所有的模擬環(huán)境都初始化為“正確的”,結(jié)合現(xiàn)有場(chǎng)景,即認(rèn)為:數(shù)據(jù)訪問層的所有操作是可用的,并且能提供運(yùn)行結(jié)果的,即查詢能查到數(shù)據(jù),增刪改能操作成功。
        c. 當(dāng)需要不正確的環(huán)境時(shí)再單獨(dú)進(jìn)行覆蓋設(shè)置(即重新給模擬方法的執(zhí)行結(jié)果賦值)
        根據(jù)以上方案對(duì)測(cè)試類初始化為如下:給測(cè)試類添加字段和每個(gè)方法運(yùn)行前都運(yùn)行的公共方法
         1         #region 字段
         2 
         3         private readonly AccountService _accountService = new AccountService();
         4         private readonly StubIMemberDao _memberDao = new StubIMemberDao();
         5         private readonly StubIMemberInactiveDao _memberInactiveDao = new StubIMemberInactiveDao();
         6         private readonly StubIConfigInfoDao _configInfoDao = new StubIConfigInfoDao();
         7 
         8         private int _num = 1;
         9         private Member _member = new Member();
        10         private readonly List<Member> _memberList = new List<Member>();
        11         private MemberInactive _memberInactive = new MemberInactive();
        12         private readonly List<MemberInactive> _memberInactiveList = new List<MemberInactive>();
        13         private ConfigInfo _configInfo = new ConfigInfo();
        14 
        15         #endregion
         1         // 在運(yùn)行每個(gè)測(cè)試之前,使用 TestInitialize 來運(yùn)行代碼
         2         [TestInitialize()]
         3         public void MyTestInitialize()
         4         {
         5             _memberDao.Commit = () => _num;
         6             _memberDao.DeleteMemberBoolean = (@member, @bool) => _num;
         7             _memberDao.GetByEmailString = @string => _memberList;
         8             _memberDao.GetByIdObject = @id => _member;
         9             _memberDao.GetByNameString = @string => _member;
        10             _memberDao.InsertMemberBoolean = (@member, @bool) => _num;
        11             _accountService.MemberDao = _memberDao;
        12 
        13             _memberInactiveDao.Commit = () => _num;
        14             _memberInactiveDao.DeleteMemberInactiveBoolean = (@memberInactive, @bool) => _num;
        15             _memberInactiveDao.GetByEmailString = @string => _memberInactiveList;
        16             _memberInactiveDao.GetByIdObject = @id => _memberInactive;
        17             _memberInactiveDao.GetByNameString = @string => _memberInactive;
        18             _memberInactiveDao.InsertMemberInactiveBoolean = (@memberInactive, @bool) => _num;
        19             _accountService.MemberInactiveDao = _memberInactiveDao;
        20 
        21             _configInfoDao.Commit = () => _num;
        22             _configInfoDao.DeleteConfigInfoBoolean = (@configInfo, @bool) => _num;
        23             _configInfoDao.GetByIdObject = @id => _configInfo;
        24             _configInfoDao.GetByNameString = @string => _configInfo;
        25             _configInfoDao.InsertConfigInfoBoolean = (@configInfo, @bool) => _num;
        26             _accountService.ConfigInfoDao = _configInfoDao;
        27 
        28         }

        有了初始化以后,原來的測(cè)試用例就可以如此的簡(jiǎn)單,只需要初始化不成立的條件即可

         1         #region UserNameExistsCheck
         2         [TestMethod]
         3         public void UserNameExistsCheck_用戶名不存在()
         4         {
         5             var userName = "柳柳英俠";
         6             var configName = "configName";
         7             _member = null;
         8             Assert.IsFalse(_accountService.UserNameExistsCheck(userName, configName));
         9         }
        10         
        11         [TestMethod]
        12         [ExpectedException(typeof(ArgumentNullException))]
        13         public void UserNameExistsCheck_引發(fā)ArgumentNullException異常_參數(shù)userName為空()
        14         {
        15             string userName = null;
        16             var configName = "configName";
        17             _accountService.UserNameExistsCheck(userName, configName);
        18         }
        19 
        20         [TestMethod]
        21         [ExpectedException(typeof(ArgumentNullException))]
        22         public void UserNameExistsCheck_引發(fā)ArgumentNullException異常_參數(shù)configName為空()
        23         {
        24             var userName = "柳柳英俠";
        25             string configName = null;
        26             _accountService.UserNameExistsCheck(userName, configName);
        27         }
        28 
        29         [TestMethod]
        30         public void UserNameExistsCheck_用戶名存在_該用戶名在用戶數(shù)據(jù)庫中已存在記錄()
        31         {
        32             var userName = "柳柳英俠";
        33             var configName = "configName";
        34             Assert.IsTrue(_accountService.UserNameExistsCheck(userName, configName));
        35         }
        36 
        37         #endregion

        所有條件都初始化好了,繼續(xù)研究需求,就可以把測(cè)試用例的所有情況都寫出來

         1         [TestMethod]
         2         [ExpectedException(typeof(NullReferenceException))]
         3         public void UserNameExistsCheck_引發(fā)NullReferenceException異常_系統(tǒng)配置信息無法找到()
         4         {
         5             var userName = "柳柳英俠";
         6             var configName = "configName";
         7             _member = null;
         8             _configInfo = null;
         9             _accountService.UserNameExistsCheck(userName, configName);
        10         }
        11 
        12         [TestMethod]
        13         public void UserNameExistsCheck_用戶不存在_用戶在用戶數(shù)據(jù)庫中不存在_and_注冊(cè)不需要激活()
        14         {
        15             var userName = "柳柳英俠";
        16             var configName = "configName";
        17             _member = null;
        18             _configInfo.RegisterConfig.NeedActive = false;
        19             Assert.IsFalse(_accountService.UserNameExistsCheck(userName, configName));
        20         }
        21 
        22         [TestMethod]
        23         public void UserNameExistsCheck_用戶不存在_用戶在用戶數(shù)據(jù)庫中不存在_and_注冊(cè)需要激活_and_用戶名在未激活用戶數(shù)據(jù)庫中不存在()
        24         {
        25             var userName = "柳柳英俠";
        26             var configName = "configName";
        27             _member = null;
        28             _configInfo.RegisterConfig.NeedActive = true;
        29             _memberInactive = null;
        30             Assert.IsFalse(_accountService.UserNameExistsCheck(userName, configName));
        31         }

        編寫代碼讓測(cè)試通過

         1         public bool UserNameExistsCheck(string userName, string configName)
         2         {
         3             if (string.IsNullOrEmpty(userName))
         4             {
         5                 throw new ArgumentNullException("userName");
         6             }
         7             if (string.IsNullOrEmpty(configName))
         8             {
         9                 throw new ArgumentNullException("configName");
        10             }
        11             var member = MemberDao.GetByName(userName);
        12             if (member != null)
        13             {
        14                 return true;
        15             }
        16             var configInfo = ConfigInfoDao.GetByName(configName);
        17             if (configInfo == null)
        18             {
        19                 throw new NullReferenceException("系統(tǒng)配置信息為空。");
        20             }
        21             if (!configInfo.RegisterConfig.NeedActive)
        22             {
        23                 return false;
        24             }
        25             var memberInactive = MemberInactiveDao.GetByName(userName);
        26             if (memberInactive != null)
        27             {
        28                 return true;
        29             }
        30             return false;
        31         }

         

      總結(jié)

        看起來文章寫得挺長了,其實(shí)內(nèi)容并沒有多少,篇幅都被代碼拉開了。我們來總結(jié)一下使用Fakes框架進(jìn)行TDD開發(fā)的步驟:

      1. 建立底層接口
      2. 創(chuàng)建測(cè)試接口的Fakes程序集
      3. 創(chuàng)建環(huán)境完全初始化的測(cè)試類(這點(diǎn)比較麻煩,可以配合T4模板進(jìn)行生成)
      4. 分析需求寫測(cè)試用例
      5. 編寫代碼讓測(cè)試用例通過
      6. 重構(gòu)代碼,并保證重構(gòu)的代碼仍然能讓測(cè)試用例通過

        另外有幾點(diǎn)經(jīng)驗(yàn)之談:

      1. 測(cè)試用例的方法名完全可以包含中文,清晰明了
      2. 由于測(cè)試類的環(huán)境已完全初始化,可以根據(jù)需求把所有的測(cè)試用例一次寫出來,不確定的可以留為空方法,也不會(huì)影響測(cè)試通過
      3. 當(dāng)你習(xí)慣了TDD之后,你會(huì)離不開它的└(^o^)┘

      本篇只對(duì)底層的接口進(jìn)行了模擬,在下篇將對(duì)測(cè)試類中的私有方法,靜態(tài)方法等進(jìn)行模擬,敬請(qǐng)期待^_^o~ 努力!

      源碼下載

      LiuliuTDDFakesDemo01.rar

      參考資料

       1.Microsoft Fakes 中的代碼生成、編譯和命名約定:
      http://msdn.microsoft.com/zh-cn/library/hh708916
      2.使用存根隔離對(duì)單元測(cè)試方法中虛擬函數(shù)的調(diào)用
      http://msdn.microsoft.com/zh-cn/library/hh549174
      3.使用填充碼隔離對(duì)單元測(cè)試方法中非虛擬函數(shù)的調(diào)用
      http://msdn.microsoft.com/zh-cn/library/hh549176

       

       

       

       

       

       

       

      posted @ 2012-08-25 20:36  郭明鋒  閱讀(13388)  評(píng)論(21)    收藏  舉報(bào)

      主站蜘蛛池模板: 国产av剧情md精品麻豆| 四虎永久免费高清视频| 国内精品综合九九久久精品| 嫩草研究院久久久精品| 国内少妇偷人精品免费| 国产激情文学亚洲区综合| 在线视频一区二区三区色| 中国女人熟毛茸茸A毛片| 久章草在线毛片视频播放| 综合久久婷婷综合久久| 国产精品av免费观看| 国产亚洲精品自在久久vr| 一区二区三区四区黄色网| 无码人妻aⅴ一区二区三区69岛| 久久久久蜜桃精品成人片公司 | 吉川爱美一区二区三区视频| 婷婷综合亚洲| 欧美和黑人xxxx猛交视频| 郎溪县| 午夜福利日本一区二区无码| 在线A级毛片无码免费真人| 一本色道久久加勒比综合| 国产精品中文字幕在线| 聂拉木县| 国产suv精品一区二区五| av色蜜桃一区二区三区| 国产精品无遮挡又爽又黄| 人人人澡人人肉久久精品| 精品国产免费一区二区三区香蕉| av高清无码 在线播放| 超碰成人人人做人人爽| 日韩熟女熟妇久久精品综合| 亚洲区色欧美另类图片| 国产日韩一区二区在线| 亚洲www永久成人网站| 最新国产精品精品视频| 国产成人无码区免费内射一片色欲 | 无码A级毛片免费视频下载| 免费黄色大全一区二区三区| 亚洲中文字幕国产综合| 国产口爆吞精在线视频2020版|