2009-06-19 17 views
15

Jestem nowy w testowaniu jednostkowym i próbuję przetestować niektóre z moich członkostwa .NET, które pisałem.Jak przystąpić do testowania członkostwa Asp.net?

Próbuję sprawdzić moją metodę VerifyUser, która sprawdza, czy poświadczenia użytkowników są prawidłowe, czy nie.

Tak to jest, jak to wygląda:

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

a teraz za każdym razem uruchomić mój testów jednostkowych zawiedzie. Wiem, że przekazuję odpowiednie referencje i takie tam. Wtedy zaświtało mi, że być może mój Test Project (który jest w tym samym Rozwiązaniu co mój prawdziwy projekt) może potrzebować własnego pliku web.config z łańcuchem połączeń i podobnymi rzeczami. Lub plik konfiguracyjny aplikacji, ponieważ jest to projekt biblioteki aplikacji.

Po prostu skopiuj plik web.config z mojego prawdziwego projektu i nazwij go dziennie? Czy powinienem brać tylko od niego części? A może po prostu jestem daleko.

Moja baza danych używa niestandardowej bazy danych z członkostwem .net połączonym z moją bazą danych. Tak więc w moim pliku konfiguracyjnym musiałem określić ManagerProvider i roleProvider.

ten sposób mój testów jednostkowych wygląda

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


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

także później mam w jednym z mojego ASP.NET MVC ActionResult Metody (the Zaloguj się aby być dokładne) mam to:

FormsAuthentication.RedirectFromLoginPage (loginValidation.UserName, rememberMe);

Więc jak mogę napisać test jednostkowy, który zrobiłby to, co zrobiłby użytkownik. Załóżmy, że zaczynają od strony głównej, a następnie klikną stronę logowania i pomyślnie się zalogują. Chcę, aby przekierowano ich na stronę główną.

Nie jestem pewien, jak to przedstawić w kodzie. Jestem całkiem pewien, że RedirectFromLoginPage działa i to jest to, co naprawdę testuję. Testuję fakt, że mam 3 rzeczy, które mogą się zdarzyć w metodzie logowania ActionResult.

  1. Użytkownik loguje się i odsyła, skąd przyszedł.
  2. Użytkownik nie loguje się i zostaje odesłany do LoginView i wyświetla komunikaty o błędach.
  3. Użytkownik próbował przejść do bezpiecznego i został przekierowany do strony logowania. Jeśli logowanie powiodło się, nastąpi przekierowanie z powrotem do bezpiecznej strony za pomocą ReturnUrl.

Więc chcę zrobić test, aby sprawdzić, czy te działają tak, jak powinny. Dlatego właśnie potrzebuję mieć takiego użytkownika, jak na stronie głównej, aby sprawdzić, czy później zostanie on z powrotem przekierowany, a jeśli pochodzą z bezpiecznej strony, przekierowują się do niej później.

Jestem również przy okazji, używając NUnit 2.5 i VS2008 Pro.


To właśnie próbuję przetestować. Jestem w części, w której próbuję sprawdzić, czy użytkownik jest ważny, czy nie (instrukcja if). Nie mam pojęcia, jak to przetestować.

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"); 
     } 

Odpowiedz

7

można przetestować kontrolery i znacznie od swojego dostawcy niestandardowej przez refaktoryzacji niestandardowy kod członkostwa w dwóch warstwach: repozytorium dostępu do danych, który współdziała tylko z bazą danych oraz warstwę usługi korzystające z repozytorium komponentów aby zapewnić Interfejs API. Warstwa usługowa służy do sprawdzania poprawności argumentów, utrzymywania i wymuszania parametrów, takich jak EnablePasswordReset i tłumaczenia dowolnych wyjątków bazy danych lub kodów statusu w formę odpowiednią do zużycia kontrolera.

Po określeniu każdej warstwy za pomocą własnego interfejsu konsumenci mogą pisać do tego interfejsu bez względu na sposób jego implementacji. Kiedy twoja aplikacja jest uruchomiona twój dostawca rozmawia oczywiście z bazą danych przez te interfejsy, ale do testowania możesz sfałszować repozytorium lub interfejsy serwisowe. Możesz przetestować warstwę usługi, kpiąc z poziomu repozytorium, bez konieczności bałagania w bazie danych lub pliku web.config, a także możesz przetestować kontrolery, kpiąc z warstwy usług. Jeśli nie chcesz zreorganizować całego dostawcy, możesz nadal testować kontrolery, jeśli tworzysz tylko interfejs usługi i czy używają go kontrolery.

Mówiąc konkretnie, jeśli trochę zbyt obszerne, swoje interfejsy repozytorium i usług mogą wyglądać mniej więcej tak:

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

Niestety nie można po prostu skopiować pliku web.config lub pliku app.config, aby działał w ten sposób. Powodem jest to, że twoje zgromadzenie działa w procesie NUnit, a nie pod twoją aplikacją.

Aby zaradzić tej sytuacji, prawdopodobnie będziesz musiał udawać lub podkradać członków członkostwa, do którego dzwonisz, lub postępować zgodnie z Konwencją dotyczącą konfiguracji w odniesieniu do ustawień zapisanych w pliku web.config.

Istnieje wiele ram szyderczy tam, ale tutaj jest kilka: Rhino Mocks, Moq

również śledzić Convention Over Configuration podejście, można zrobić coś takiego:

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

Ty mógł następnie użyć tego kodu:

//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 

W ten sposób Twój test będzie działał, a gdy uruchomisz go pod swoją aplikacją będzie używał wszelkich ustawień konfiguracyjnych, które zapisałeś.

+0

Niestety ja nie rozumiem „Konwencja nad podejściem Konfiguracja” Czytałem o wyśmianie w książka, którą mam.Czy ma coś wspólnego z używaniem interfejsów lub czegoś w tym stylu? Znalazłem ten link, ale myślę, że to nie zadziała, odkąd używam Nunit i on używa MbUnit? http://aspalliance.com/1590 – chobo2

+0

@ chobo2 Powiem coś o tym, jak z niego korzystać. Szokujące ramy zdecydowanie mają skłonność do używania interfejsów i nie bez powodu. Korzystanie z interfejsów zapewnia dobre oddzielenie. Jednak nie musisz używać interfejsów we wszystkich szyderczych frameworkach. Ponadto NUnit i MbUnit są podobne pod wieloma względami, więc coś, co jest zrobione w MbUnit, najprawdopodobniej ma parryllela do NUnit. Warto trochę zbadać. – Joseph

+0

OK Dzięki. Wciąż nie rozumiem interfejsów i naprawdę nie mam z nimi pojęcia, jeśli chodzi o testowanie jednostkowe. Mam więc nadzieję, że jedna z tych struktur sprawi, że będzie trochę łatwiej. Którego polecasz? Widzę, że "Moq" używa linq sytanx, więc nie jestem pewien, czy prawo tego nietoperza, jeśli to będzie dobre dla mnie, ponieważ nie wiem zbyt wiele o linq. Również, w jaki sposób te frameworki działają z nunit? Czy nadal używam nunit, aby uruchomić mój test lub co? – chobo2

3

System członkowski Asp.Net został zaprojektowany do pracy w kontekście żądania Asp.Net. Masz tutaj trzy opcje.

  1. Większość ludzi w obliczu takiej zależności napisze cienką owijkę. Opakowanie niczego nie robi, po prostu przekierowuje wszystkie wywołania do zależnej zależności. Po prostu go nie testują. Twój AuthenticateUser jest takim opakowaniem. Powinieneś prawdopodobnie uczynić wszystkie metody wirtualnymi lub wyodrębnić interfejs, aby uczynić go możliwym do opanowania, ale to już inna historia.
  2. Użyj izolatora typu wzorcowego i udawaj członkostwo.
  3. Użyj Ivonna framework i uruchom test w kontekście Asp.Net (to byłby test integracji).
Powiązane problemy