<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實戰——接口模擬

      前言

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

      需求說明

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

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

      項目結構

        先分解一下項目的結構,還是傳統的三層結構,從底層到上層:

      1. Liuliu.Components.Tools:通用工具組件
      2. Liuliu.Components.Data:通用數據訪問組件,目前只定義了一個數據訪問接口的通用基接口IRepository
      3. Liuliu.Demo.Core.Models:數據實體類,分兩個模塊,賬戶模塊(Account)與通用模塊(Common)
      4. Liuliu.Demo.Core:業務核心層,里面包含Business與DataAccess兩個子層,DataAccess實現實體類的數據訪問,Business層實現模塊的業務邏輯,因為測試的過程中數據訪問層的數據庫實現會用Fakes框架來模擬,所以數據訪問層只提供了接口,不提供實現,Business只調用了DataAccess的接口。我們要做的工作就是用Fakes框架來模擬數據訪問層,用TDD的方式來編寫Business中的業務實現
      5. Liuliu.Demo.Core.Business.UnitTest:單元測試項目,存放著測試Business實現的測試用例。
      6. Liuliu.Demo.Consoles:用戶操作控制臺,功能實現后進行用戶操作的UI項目

        其他的項目與測試無關,略過。

      開發準備

      應用代碼準備

      Entity:實體類的通用數據結構

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

      IRepository:通用數據訪問接口,簡單起見,只寫了幾個增刪改查的接口

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

      Member:實體類——用戶信息

       1     /// <summary>
       2     ///   實體類——用戶信息
       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:實體類——未激活用戶信息

       1     /// <summary>
       2     ///   實體類——未激活用戶信息
       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:實體類——系統配置信息

       1     /// <summary>
       2     ///   實體類——系統配置信息
       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         ///   注冊時是否需要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注冊不同會員
      29         /// </summary>
      30         public bool EmailRepeat { get; set; }
      31     }

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

       1     /// <summary>
       2     ///   數據訪問接口——用戶信息
       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:數據訪問接口——未激活用戶信息,僅添加IRepository不滿足的接口

       1     /// <summary>
       2     ///   數據訪問接口——未激活用戶信息
       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:數據訪問接口——系統配置,無額外需求的接口,所以為空接口

      1     /// <summary>
      2     ///   數據訪問接口——系統配置信息
      3     /// </summary>
      4     public interface IConfigInfoDao : IRepository<ConfigInfo> 
      5     { }

      IAccountContract:賬戶模塊業務契約——定義了三個操作,用作注冊前的數據檢查和注冊提交

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

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

      測試類準備

      1. 添加測試項目的引用

      2. 添加要模擬實現接口的Fakes程序集,要模擬的接口在Liuliu.Demo.Core程序集中,所以在該程序集上點右鍵,選擇“添加Fakes程序集”菜單項

      3. 添加好了之后,Fakes框架會在測試項目中添加一個Fakes文件夾和一個配置文件,并自動生成引用一個 模擬程序集.Fakes 的程序集和Fakes框架的運行環境Microsoft.QualityTools.Testing.Fakes

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

         這個配置默認會把測試程序集中的所有接口、類都生成模擬類,當然也可以配置生成指定的類型的模擬,相關知識這里就不講了,請參閱官方文檔:Microsoft Fakes 中的代碼生成、編譯和命名約定

      7. 需要特別說明的是,每次生成,Fakes程序集都會重新生成,所以測試類有更改后想刷新Fakes程序集,只需要把原來的程序集刪除再進行生成,或者在測試項目能編譯的時候重新編譯測試項目即可。

      TDD正式開始

      1. 給測試項目添加一個單元測試類文件,添加新項 -> Visual C#項 -> 測試 -> 單元測試,命名為AccountServiceTest.cs,推薦命名方式為“測試類名+Test”的方式
      2. 添加一個測試方法,關于測試方法的命名,各人有各人的方案,這里推薦一種方案:“測試方法名_執行結果_得到此結果的條件/原因”,并且測試方法是可以使用中文的,比如“UserNameExistsCheck_用戶名已存在_用戶名在用戶信息表中已存在記錄”,這種方式好很多好處,特別是團隊成員英文水平不太好的時候,如果翻譯成英文的方式,很有可能會不知所云,并且中文與需求文檔一一對應,非常明了,以下的測試用例中都會運用這種方式,如果不適應請在腦中自行翻譯\(^o^)/,建立測試方法如下:
        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         }

         當然,此時運行測試是編譯不過的,因為AccountService類根本還沒有創建。在Liuliu.Demo.Core.Business.Impl文件夾下添加AccountService類,并實現IAccountContract接口

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

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

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

        再次運行測試用例,紅叉終于變成綠勾了,我敢打賭,如果你真正實踐TDD的話,綠色將是你一定會喜歡的顏色


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

         1         [TestMethod]
         2         [ExpectedException(typeof(ArgumentNullException))]
         3         public void UserNameExistsCheck_引發ArgumentNullException異常_參數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_引發ArgumentNullException異常_參數configName為空()
        14         {
        15             var userName = "柳柳英俠";
        16             string configName = null;
        17             var accountService = new AccountService();
        18             accountService.UserNameExistsCheck(userName, configName);
        19         }

        運行測試,結果如下,原因為還沒有寫異常代碼,期望的異常沒有引發。└(^o^)┘平常我們很怕出異常,現在要去期望出異常


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

         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類添加如下屬性,以便在接下來的操作中能模擬調用數據訪問層的操作

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

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

         1         [TestMethod]
         2         public void UserNameExistsCheck_用戶名存在_該用戶名在用戶數據庫中已存在記錄()
         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接口生成的一個模擬類,第7行實例化了一個該類的對象, 這個對象有一個委托類型的字段GetByNameString開放出來,我們就可以通過這個字段給接口的GetByName方法賦一個執行結果,即第8行的操作。再把這個對象賦給AccountService類中的IMemberDao類型的屬性(第9行),即相當于給AccountService類添加了一個操作用戶信息數據層的實現。
        修改UserNameExistsCheck方法使測試通過

         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         }

        運行測試,上面這個測試通過了,但第一個測試卻失敗了。


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

      3. 根據以上提出的問題,給出下面的解決方案
        a. 進行公共環境的初始化,即讓所有測試用例在相同的環境下運行
        b. 所有的模擬環境都初始化為“正確的”,結合現有場景,即認為:數據訪問層的所有操作是可用的,并且能提供運行結果的,即查詢能查到數據,增刪改能操作成功。
        c. 當需要不正確的環境時再單獨進行覆蓋設置(即重新給模擬方法的執行結果賦值)
        根據以上方案對測試類初始化為如下:給測試類添加字段和每個方法運行前都運行的公共方法
         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         // 在運行每個測試之前,使用 TestInitialize 來運行代碼
         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         }

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

         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_引發ArgumentNullException異常_參數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_引發ArgumentNullException異常_參數configName為空()
        23         {
        24             var userName = "柳柳英俠";
        25             string configName = null;
        26             _accountService.UserNameExistsCheck(userName, configName);
        27         }
        28 
        29         [TestMethod]
        30         public void UserNameExistsCheck_用戶名存在_該用戶名在用戶數據庫中已存在記錄()
        31         {
        32             var userName = "柳柳英俠";
        33             var configName = "configName";
        34             Assert.IsTrue(_accountService.UserNameExistsCheck(userName, configName));
        35         }
        36 
        37         #endregion

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

         1         [TestMethod]
         2         [ExpectedException(typeof(NullReferenceException))]
         3         public void UserNameExistsCheck_引發NullReferenceException異常_系統配置信息無法找到()
         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_用戶不存在_用戶在用戶數據庫中不存在_and_注冊不需要激活()
        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_用戶不存在_用戶在用戶數據庫中不存在_and_注冊需要激活_and_用戶名在未激活用戶數據庫中不存在()
        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         }

        編寫代碼讓測試通過

         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("系統配置信息為空。");
        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         }

         

      總結

        看起來文章寫得挺長了,其實內容并沒有多少,篇幅都被代碼拉開了。我們來總結一下使用Fakes框架進行TDD開發的步驟:

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

        另外有幾點經驗之談:

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

      本篇只對底層的接口進行了模擬,在下篇將對測試類中的私有方法,靜態方法等進行模擬,敬請期待^_^o~ 努力!

      源碼下載

      LiuliuTDDFakesDemo01.rar

      參考資料

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

       

       

       

       

       

       

       

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

      主站蜘蛛池模板: 日韩一区二区三区高清视频| 精品人妻伦九区久久69| 色视频不卡一区二区三区| 亚洲男女内射在线播放| 精品国产一区二区三区av片| 亚洲最大的成人网站| 亚洲精品综合一区二区三区| 国产激情视频在线观看首页| 精品一区二区中文字幕| 亚洲欧洲一区二区三区久久 | 日本高清成本人视频一区| 中文字幕人妻有码久视频| 国产-第1页-浮力影院| 日韩三级一区二区在线看| 久久精品国产亚洲av麻豆不卡| 成人婷婷网色偷偷亚洲男人的天堂| 狠狠色狠狠色五月激情| 99在线精品国自产拍中文字幕| 人妻日韩人妻中文字幕| 不卡国产一区二区三区| 中文字幕乱偷无码av先锋蜜桃| 好看的国产精品自拍视频| 亚洲男人的天堂在线观看| 国产精品日日摸夜夜添夜夜添无码 | 人妻熟女欲求不满在线| 亚洲天堂一区二区三区四区| 久章草在线毛片视频播放| 亚洲香蕉免费有线视频| 国产亚洲精品在av| 大陆精大陆国产国语精品| 人妻有码av中文字幕久久琪| 2019香蕉在线观看直播视频| 在线看高清中文字幕一区| 精品视频不卡免费观看| 97人妻精品一区二区三区| 午夜精品久久久久久久久| 稷山县| 亚洲综合一区无码精品| 日本韩国日韩少妇熟女少妇| 国产免费视频一区二区| 任我爽精品视频在线播放|