2014-03-27 78 views
2

我希望這不是太含糊,但我只是在學習使用NUnit和Moq的假貨進行單元測試。我理解這些概念,並且我可以毫無困難地爲執行簡單任務的邏輯方法編寫測試,例如操作某些值或調用僞造的服務。但是我有點摸不着頭腦,試圖弄清楚如何測試像下面這樣的東西,這是一種需要進行數據調用並具有多個依賴關係的方法。如何正確測試? (C#,NUnit,Moq)

這是一類我必須對數據庫進行驗證的用戶:

class Authenticator: IAuthenticator 
{ 

    private IConnectionHelper _connHelper; 

    public Authenticator(IConnectionHelper connHelper) 
    { 
     _connHelper = connHelper; 
    } 

    public string UserId { get; set; } 
    public string Password { get; set; } 
    public UserModel User { get; private set; } 

    public bool ValidateUser() 
    { 

      if (string.IsNullOrEmpty(UserId)) 
       return false; 

      if (string.IsNullOrEmpty(Password)) 
       return false; 

      using (Entities db = new Entities(_connHelper)) 
      { 
       MD5 md5 = MD5.Create(); 
       byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password); 
       byte[] hash = md5.ComputeHash(inputBytes); 
       string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty); 

       var query = from u in db.Users 
          where u.UserId.ToUpper().Trim() == this.UserId.ToUpper() 
          where u.CPassword.Trim() == md5Hash 
          select u; 

       this.User = query.FirstOrDefault(); 
      } 



     if (this.User == null) 
     { 
      Log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId)); 
      return false; 
     } 
     else 
     { 
      Log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId)); 
      return true; 
     } 
    } 


} 

我想創建這樣的測試夾具,但我不確定如何處理它。

我可以模擬IConnectionHelper,並測試如果UserId或密碼爲空/空的方法將失敗,但這是盡我所能。如果IConnectionHelper是假的,那麼我顯然不能測試數據庫的東西,我不知道我應該怎麼做。有人可以在這裏介紹一下最佳實踐嗎?

編輯

從StuartLc接受的答案肯定了我要在正確的方向。我也有做一些額外的設置工作,爲實體框架,基本都採用這個作爲參考:http://msdn.microsoft.com/en-us/data/dn314429.aspx

+0

您需要abstra爲數據庫提供一個界面,以便它可以被模擬。 –

+0

@RyanGates你的意思是抽象的EF DbContext(實體)?或者你的意思是創建另一個服務,我傳遞用戶/ PW到處理數據庫交互?在某些時候,我是否仍然需要測試我的查詢? – amnesia

+0

我認爲測試你的查詢和編程邏輯是兩種不同的測試。每當我編寫單元測試時,我寧願不依賴數據庫。這就是我的做法,但遠不是唯一的辦法。 –

回答

5

按照萊恩的評論,你的代碼是太緊密耦合的依賴EntitiesLog很容易進行單元測試(而不必求助於MolesFakes)。

作爲一般的規則,使用的new創建任何實質性的依賴,或static方法,如使用Log通常預防與像Moq框架分離的單元測試的元兇。

我的建議是重構代碼如下:

  1. 分離出來,創建Entities類(大概一個ORM神器像EF DbSet或Linq的DataContext)入廠的關注通過,一個界面。
  2. IConnectionHelper依賴關係可以移入工廠。
  3. 靜態記錄器也應該被剝離到一個接口中,並且一個實例也應該被注入到您的Authenticator中,並且它的生命週期也由您的IoC容器管理。該LoggerEntitiesFactory都可以注入

您應風的東西,如下面的:

public interface IEntitiesFactory 
    { 
    Entities Create(); 
    } 

    public interface ILog 
    { 
    void LogLine(TraceLevel level, string message); 
    } 

    class Authenticator : IAuthenticator 
    { 
    private readonly IEntitiesFactory _entitiesFactory; 
    private readonly ILog _log; 

    public Authenticator(IEntitiesFactory entitiesFactory, ILog log) 
    { 
     _entitiesFactory = entitiesFactory; 
     _log = log; 
    } 

    public string UserId { get; set; } 
    public string Password { get; set; } 
    public UserModel User { get; private set; } 

    public bool ValidateUser() 
    { 
     if (string.IsNullOrEmpty(UserId)) 
      return false; 

     if (string.IsNullOrEmpty(Password)) 
      return false; 

     using (var db = _entitiesFactory.Create()) 
     { 
      MD5 md5 = MD5.Create(); 
      byte[] inputBytes = Encoding.ASCII.GetBytes(this.Password); 
      byte[] hash = md5.ComputeHash(inputBytes); 
      string md5Hash = BitConverter.ToString(hash).ToUpper().Replace("-", string.Empty); 

      var query = from u in db.Users 
         where u.UserId.ToUpper().Trim() == this.UserId.ToUpper() 
         where u.CPassword.Trim() == md5Hash 
         select u; 

      this.User = query.FirstOrDefault(); 
     } 

     if (this.User == null) 
     { 
      _log.LogLine(TraceLevel.Verbose, string.Format("Authentication failed for user '{0}'", this.UserId)); 
      return false; 
     } 
     else 
     { 
      _log.LogLine(TraceLevel.Verbose, string.Format("Successfully authenticated user '{0}'", this.UserId)); 
      return true; 
     } 
    } 

,在那裏你可以現在Mock出實體工廠和記錄器,現在提供假Users爲找到/未找到場景數據,並驗證正確的東西被送到logger等,即

[Test] 
public void SomeTest() 
{ 
    var mockFactory = new Mock<IEntitiesFactory>(); 
    var mockEntities = new Mock<Entities>(); 
    var fakeUsers = new List<UserModel> 
     { 
      new UserModel 
      { 
       UserId = "Bob", 
       CPassword = "TheHashOfSecret" 
      } 
     }; 
    mockEntities.SetupGet(_ => _.Users).Returns(fakeUsers.AsQueryable()); 
    mockFactory.Setup(_ => _.Create()).Returns(mockEntities.Object); 
    var mockLog = new Mock<ILog>(); 
    var sut = new Authenticator(mockFactory.Object, mockLog.Object) 
     { 
      UserId = "Bob", 
      Password = "Secret" 
     }; 
    Assert.DoesNotThrow(() => sut.ValidateUser()); 
    Assert.IsNotNull(sut.User); 
    mockLog.Verify(_ => _.LogLine(), Times.Once); ... 
} 
+2

這非常有意義,我真的很感激你花時間提供這樣的詳細回覆! – amnesia