2009-06-19 69 views
15

我是單元測試的新手,我正在嘗試測試一些我一直在寫的.NET會員資料。如何單元測試Asp.net會員?

所以我想檢查我的VerifyUser方法,檢查用戶憑據是否有效。

因此,這是什麼樣子:

public bool VerifyUser(string userName, string password) 
    { 
     bool valid = Membership.ValidateUser(userName, password); 
     return valid; 
    } 

現在每次我跑我的單元測試失敗的時間。我知道我傳遞了正確的證書和內容。然後它就明白了,也許我的測試項目(與我的真實項目在同一解決方案下)可能需要自己的web.config文件以及連接字符串和內容。或者是一個應用程序配置文件,因爲它是一個應用程序庫項目。

那麼,我只是從我的真實項目複製web.config文件,並稱它爲一天?或者我應該只是從中取出零件?或者我剛剛離開。

我的數據庫正在使用與我的數據庫合併的.net成員資格的自定義數據庫。所以在我的配置文件中,我必須指定一個ManagerProvider和一個roleProvider。

這是我的單元測試看起來像

[Test] 
    public void TestVerifyUser() 
    { 
     AuthenticateUser authenitcate = new AuthenticateUser(); 
     bool vaild = authenitcate.VerifyUser("chobo3", "1234567"); 


     Assert.That(vaild, Is.True); 
    } 

而且後來我在我的asp.net MVC的ActionResult方法之一(登錄查看確切)我有這樣的:

FormsAuthentication.RedirectFromLoginPage(loginValidation.UserName,rememberMe);

那麼現在我該如何編寫一個單元測試來完成用戶會做的事情。假設他們從主頁開始,然後點擊登錄頁面併成功登錄。我希望他們重定向到主頁。

我不知道如何在代碼中表示。我很確定RedirectFromLoginPage的工作原理,現在我正在測試。我正在測試這樣的事實,即在登錄ActionResult方法中可能發生3件事情。

  1. 用戶登錄並被髮回,他們從哪裏來。
  2. 用戶未能登錄並被髮送回LoginView並看到錯誤消息。
  3. 用戶試圖轉到安全並已重定向到登錄頁面。如果登錄成功,將通過ReturnUrl重定向回安全頁面。

所以我想做一個測試,看看這些工作是否應該。這就是爲什麼我需要讓用戶像主頁一樣來看看它們是否會在稍後重定向到它,如果它們來自一個安全頁面,那麼它們將重定向回到稍後。

我也順便使用NUnit 2.5和VS2008 Pro。


這就是我想要測試的。我在我試圖查看用戶是否有效的部分(if語句)。我不知道如何測試它。

public ActionResult Login(string returnUrl, FormCollection form, bool rememberMe) 
     { 
      LoginValidation loginValidation = new LoginValidation(); 
      try 
      { 
       UpdateModel(loginValidation, form.ToValueProvider()); 

      } 
      catch 
      { 

       return View("Login"); 
      } 

      if (ModelState.IsValid == true) 
      { 

       bool valid = authenticate.VerifyUser(loginValidation.UserName, loginValidation.Password); 

       if (valid == false) 
       { 
        ModelState.AddModelError("frm_Login", "Either the Password or UserName is invalid"); 

       } 
       else if (string.IsNullOrEmpty(returnUrl) == false) 
       { 
        /* if the user has been sent away from a page that requires them to login and they do 
        * login then redirect them back to this area*/ 
        return Redirect(returnUrl); 
       } 
       else 
       { 

        FormsAuthentication.RedirectFromLoginPage(loginValidation.UserName, rememberMe); 
       } 

      } 


      return View("Login"); 
     } 

回答

7

您可以通過重構你的自定義成員資格的代碼放到一個兩層測試你的控制器和許多自定義提供的會員資格API。服務層是您驗證參數的位置,保存並執行像EnablePasswordReset這樣的參數,並將任何數據庫異常或狀態代碼轉換爲適合控制器使用的形式。

當您使用自己的接口指定每個圖層時,消費者可以寫入該接口,而不管其實現方式如何。當你的應用程序運行時,你的提供者當然是通過這些接口與數據庫交談,但是爲了測試,你可以模擬知識庫或服務接口。您可以通過模擬存儲庫級別來測試服務層,而不必混淆數據庫或web.config文件,並且可以通過模擬服務層來測試您的控制器。如果你不想重構整個提供者,那麼如果你只創建服務接口並讓你的控制器使用它,你仍然可以測試你的控制器。

具體而言,如果有點冗長,你的資料庫和服務接口可能看起來像:

namespace Domain.Abstract { 
    public interface IRepository { 
     string ConnectionString { get; } 
    } 
} 

namespace Domain.Abstract { 
    public interface IUserRepository : IRepository { 
     MembershipUser CreateUser(Guid userId, string userName, string password, PasswordFormat passwordFormat, string passwordSalt, 
       string email, string passwordQuestion, string passwordAnswer, bool isApproved, 
       DateTime currentTimeUtc, bool uniqueEmail); 
     MembershipUser GetUser(Guid userId, bool updateLastActivity, DateTime currentTimeUtc); 
     PasswordData GetPasswordData(Guid userId, bool updateLastLoginActivity, DateTime currentTimeUtc); 
     void UpdatePasswordStatus(Guid userId, bool isAuthenticated, int maxInvalidPasswordAttempts, int passwordAttemptWindow, 
         DateTime currentTimeUtc, bool updateLastLoginActivity, DateTime lastLoginDate, DateTime lastActivityDate); 
     //.... 
    } 
} 

namespace Domain.Abstract { 
    public interface IUserService { 
    bool EnablePasswordRetrieval { get; } 
    bool EnablePasswordReset { get; } 
    bool RequiresQuestionAndAnswer { get; } 
    bool RequiresUniqueEmail { get; } 
    //.... 

    MembershipUser CreateUser(string applicationName, string userName, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved); 
    MembershipUser GetUser(Guid userId, bool userIsOnline); 
    bool ValidateUser(Guid userId, string password); 
    //... 
    } 
} 

namespace Domain.Concrete { 
    public class UserService : IUserService { 
    private IUserRepository _userRepository; 


    public UserService(IUserRepository userRepository) { 
     _userRepository = userRepository; 
    } 
    //... 
    public bool ValidateUser(Guid userId, string password) { 
     // validate applicationName and password here 
     bool ret = false; 
     try { 
      PasswordData passwordData; 
      ret = CheckPassword(userId, true, true, DateTime.UtcNow, out passwordData); 
     } 
     catch (ObjectLockedException e) { 
      throw new RulesException("userName", Resource.User_AccountLockOut); 
     } 
     return ret; 
    } 

    private bool CheckPassword(Guid userId, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, 
           DateTime currentTimeUtc, out PasswordData passwordData) { 
     passwordData = _userRepository.GetPasswordData(userId, updateLastLoginActivityDate, currentTimeUtc); 

     if (!passwordData.IsApproved && failIfNotApproved) 
      return false; 

     string encodedPassword = EncodePassword(password, passwordData.PasswordFormat, passwordData.PasswordSalt); 
     bool isAuthenticated = passwordData.Password.Equals(encodedPassword); 

     if (isAuthenticated && passwordData.FailedPasswordAttemptCount == 0 && passwordData.FailedPasswordAnswerAttemptCount == 0) 
      return true; 
     _userRepository.UpdatePasswordStatus(userId, isAuthenticated, _maxInvalidPasswordAttempts, _passwordAttemptWindow, 
              currentTimeUtc, updateLastLoginActivityDate, 
              isAuthenticated ? currentTimeUtc : passwordData.LastLoginDate, 
              isAuthenticated ? currentTimeUtc : passwordData.LastActivityDate); 

     return isAuthenticated; 
    } 
} 
1

不幸的是,你不能只複製你的web.config或你的app.config並使其以這種方式工作。原因是你的程序集在NUnit進程內運行,而不是在你的應用程序下運行。

要解決您的情況,您可能需要對您調用的成員資格成員進行模擬或存根,或按照常規配置方法遵循您存儲在web.config中的設置。

有很多嘲弄的框架存在,但這裏有一對夫婦:Rhino MocksMoq

此外,遵循約定優於配置的方法,你可以做這樣的事情:

static ConfigurationSettings 
{ 
    static String SomeSetting 
    { 
     get 
     { 
      var result = "HARDCODEDVALUE"; 
      if (ConfigurationManager.AppSettings["SOMEKEY"] != null) 
       result = ConfigurationManager.AppSettings["SOMEKEY"]; 
      return result; 
    } 
} 

你然後可以使用此代碼:

//this is how the old code might look 
var mySetting = ConfigurationManager.AppSettings["SOMEKEY"]; 
//use the setting 

//this is how the new code would look 
var mySetting = ConfigurationSettings.SomeSetting; 
//use the setting 

這樣,你的測試將工作,當你在你的APPLI運行它會使用您存儲的任何配置設置。

+0

對不起,我不明白 我讀到的嘲諷「過度配置的方法的公約」我有書。它與使用接口或類似的東西? 我發現這個鏈接,但我想它不會爲我工作,因爲我使用Nunit和他使用MbUnit? http://aspalliance.com/1590 – chobo2 2009-06-19 14:23:46

3

Asp.Net成員系統被設計爲在Asp.Net請求的上下文中工作。所以,你有三個選擇。

  1. 大多數人在遇到這種依賴時會在它周圍寫一個薄薄的包裝。包裝器不做任何事情,只是將所有調用重定向到基礎依賴項。所以,他們只是不測試它。你的AuthenticateUser就是這樣一個包裝器。你可能應該使所有的方法都是虛擬的,或者提取一個接口來使其可以被嘲弄,但這是另一回事。
  2. 使用TypeMock Isolator和模擬會員。
  3. 使用Ivonna framework並在Asp.Net環境中運行測試(這將是一個集成測試)。數據訪問庫,只有與數據庫交互,以及使用存儲庫組件提供了一個服務層: