2009-06-19 25 views
15

Soy nuevo en pruebas unitarias y estoy tratando de probar algunas de mis cosas de membresía de .NET que he estado escribiendo.¿Cómo puedo probar la membresía de Asp.net?

Así que estoy tratando de verificar mi método VerifyUser que comprueba si las credenciales de los usuarios son válidas o no.

Así que esto es lo que parece:

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

Y ahora cada vez que paso la prueba de la unidad falla. Sé que estoy pasando las credenciales correctas y esas cosas. Entonces me di cuenta de que tal vez mi Proyecto de Prueba (que está bajo la misma Solución que mi proyecto real) podría necesitar su propio archivo web.config con la cadena de conexión y esas cosas. O un archivo de configuración de la aplicación tal vez porque es un proyecto de la Biblioteca de aplicaciones.

¿Solo copio el archivo web.config de mi proyecto real y lo llamo un día? ¿O debería solo tomar partes de él? ¿O estoy solo lejos?

Mi base de datos está utilizando una base de datos personalizada con la membresía .net combinada con mi base de datos. Entonces en mi archivo de configuración tuve que especificar un ManagerProvider y un roleProvider.

Así es como mi prueba de la unidad parece

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


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

también más adelante que tengo en uno de mis asp.net mvc Métodos ActionResult (Ver la sesión para ser exactos) tengo esto:

FormsAuthentication.RedirectFromLoginPage (loginValidation.UserName, rememberMe);

Entonces, ¿cómo puedo escribir una prueba de unidad que haría lo que un usuario haría? Supongamos que comienzan en la página de inicio y luego hacen clic en la página de inicio de sesión e inician sesión con éxito. Quiero que sean redirigidos a la página de inicio.

No estoy seguro de cómo representar eso en el código. Estoy bastante seguro de que el RedirectFromLoginPage funciona y eso es lo que realmente estoy probando. Estoy probando el hecho de que tengo 3 cosas que pueden suceder en el método de inicio de sesión ActionResult.

  1. El usuario inicia sesión y se le envía de vuelta de donde vino.
  2. El usuario no puede iniciar sesión y se le devuelve al LoginView y ve los mensajes de error.
  3. El usuario ha intentado acceder a un lugar seguro y ha sido redirigido a la página de inicio de sesión. Si el inicio de sesión con éxito será redirigido a la página segura a través de ReturnUrl.

Así que quiero hacer una prueba para ver si funcionan como deberían. De modo que es por eso que necesito que el usuario venga desde la página de inicio para ver si se les redirige a ella más tarde y si provienen de una página segura se redireccionarán a eso más adelante.

También estoy por cierto usando NUnit 2.5 y VS2008 Pro.


Esto es lo que estoy tratando de probar. Estoy en la parte donde trato de ver si el usuario es válido o no (la declaración if). No tengo ni idea de cómo probarlo.

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

Respuesta

7

Puede probar sus controladores y gran parte de su proveedor personalizado refactorizando su código de membresía personalizado en dos capas: un repositorio de acceso a datos que solo interactúa con la base de datos y una capa de servicio que utiliza API de membresía. La capa de servicio es donde validar argumentos, mantener y aplicar parámetros como EnablePasswordReset y traducir cualquier excepción de base de datos o códigos de estado en un formulario adecuado para el consumo del controlador.

Cuando especifica cada capa con su propia interfaz, los consumidores pueden escribir en esa interfaz independientemente de cómo se implemente. Cuando su aplicación se está ejecutando, su proveedor, por supuesto, está hablando con la base de datos a través de estas interfaces, pero para las pruebas puede simular el repositorio o las interfaces de servicio. Puede probar su capa de servicio burlándose del nivel de repositorio sin tener que meterse con la base de datos o el archivo web.config, y puede probar sus controladores burlándose de la capa de servicio. Si no desea refaccionar a todo el proveedor, aún puede probar sus controladores si solo crea la interfaz de servicio y hace que los controladores la utilicen.

Para ser más específicos, si un poco, sus interfaces de repositorio y de servicios detallados podría ser algo como:

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

Desafortunadamente, no puede simplemente copiar su web.config o su app.config y hacer que funcione de esa manera. La razón es que su ensamblaje se ejecuta dentro del proceso NUnit, no debajo de su aplicación.

Para remediar su situación, es probable que tenga que simular o anotar los miembros de Membresía a los que llama, o seguir un enfoque de Convención sobre Configuración a la configuración que ha almacenado en su web.config.

Hay muchos marcos burlones por ahí, pero aquí hay un par: Rhino Mocks, Moq

Además, para seguir la Convención sobre configuración de aproximación, se podría hacer algo como esto:

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

Usted podría usar este código así:

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

De esta manera su prueba funcionará, y cuando la ejecute bajo su aplicación cation utilizará los ajustes de configuración que haya almacenado.

+0

Lo siento, no entiendo la "Convención sobre el enfoque de configuración" leí acerca de burla en el libro que tengo.¿Tiene algo que ver con el uso de interfaces o algo así? Encontré este enlace pero supongo que no funcionará para mí ya que estoy usando Nunit y él está usando MbUnit? http://aspalliance.com/1590 – chobo2

+0

@ chobo2 Le explicaré cómo usarlo. Los marcos burlones definitivamente tienen una inclinación hacia el uso de interfaces, y por buenas razones. El uso de interfaces proporciona un buen desacoplamiento. Sin embargo, no está obligado a utilizar interfaces en todos los marcos de burla. Además, NUnit y MbUnit son similares en muchas formas, por lo que algo que se hace en MbUnit probablemente tenga un paralelo con NUnit. Vale la pena investigar un poco. – Joseph

+0

Ok gracias. Todavía no entiendo las interfaces y realmente no tengo idea con ellas cuando se trata de pruebas unitarias. Entonces, con suerte, uno de estos marcos lo hará un poco más fácil. ¿Cuál recomiendan? Veo que "Moq" usa linq sytanx, así que no estoy seguro si es correcto de ese bate si eso será bueno para mí ya que no sé mucho sobre linq. Además, ¿cómo funcionan estos marcos con nunit? Al igual que todavía uso nunit para ejecutar mi prueba o qué? – chobo2

3

El sistema de membresía Asp.Net está diseñado para funcionar en el contexto de una solicitud de Asp.Net. Entonces, tienes tres opciones aquí.

  1. La mayoría de las personas, cuando se enfrentan a una dependencia de este tipo, escriben una capa delgada alrededor de ella. El contenedor no hace nada, simplemente redirige todas las llamadas a la dependencia subyacente. Entonces, simplemente no lo prueban. Su AuthenticateUser es tal envoltorio. Probablemente debas virtualizar todos los métodos o extraer una interfaz para que se pueda burlar, pero esa es otra historia.
  2. Use TypeMock Isolator y membresía simulada.
  3. Use Ivonna framework y ejecute su prueba en el contexto de Asp.Net (eso sería una prueba de integración).
Cuestiones relacionadas