Как пройти тестирование членства в Asp.net? - PullRequest
15 голосов
/ 19 июня 2009

Я новичок в модульном тестировании и пытаюсь протестировать некоторые из моих материалов .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, запомнитьMe);

Итак, как мне написать модульный тест, который будет делать то, что будет делать пользователь. Допустим, они начинаются с домашней страницы, затем нажимают на страницу входа и успешно входят в систему. Я хочу, чтобы они были перенаправлены на домашнюю страницу.

Я не уверен, как представить это в коде. Я почти уверен, что RedirectFromLoginPage работает, и это то, что я действительно тестирую. Я проверяю тот факт, что у меня есть 3 вещи, которые могут произойти в методе входа в систему ActionResult.

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

Ответы [ 3 ]

7 голосов
/ 22 июня 2009

Вы можете протестировать свои контроллеры и большую часть своего пользовательского провайдера путем рефакторинга своего пользовательского кода членства в два уровня: хранилище доступа к данным, которое взаимодействует только с базой данных, и сервисный уровень, который использует компоненты хранилища для предоставления 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;
    }
}
3 голосов
/ 21 июня 2009

Система членства Asp.Net разработана для работы в контексте запроса Asp.Net. Итак, у вас есть три варианта здесь.

  1. Большинство людей, сталкивающихся с такой зависимостью, пишут вокруг нее тонкую оболочку. Оболочка ничего не делает, просто перенаправляет все вызовы на основную зависимость. Так что они просто не проверяют это. Ваш AuthenticateUser - такая обертка. Вам, вероятно, следует сделать все методы виртуальными или извлечь интерфейс, чтобы сделать его смешным, но это другая история.
  2. Используйте TypeMock Isolator и имитируйте членство.
  3. Используйте Ivonna Framework и запустите свой тест в контексте Asp.Net (это будет интеграционный тест).
1 голос
/ 19 июня 2009

К сожалению, вы не можете просто скопировать ваш web.config или app.config и заставить его работать таким образом. Причина в том, что ваша сборка выполняется внутри процесса NUnit, а не под вашим приложением.

Чтобы исправить вашу ситуацию, вам, вероятно, придется использовать Mock или Stub членов, которым вы звоните, или следовать подходу «Соглашение о конфигурации» с настройками, сохраненными в вашем web.config.

Есть много насмешливых фреймворков, но вот пара: Носороги Rhino , Moq

Кроме того, следуя подходу «Соглашение над конфигурацией», вы можете сделать что-то вроде этого:

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   

Таким образом, ваш тест будет работать, и когда вы запустите его под своим приложением, он будет использовать все сохраненные вами параметры конфигурации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...