2

标题不是很具体,但让我们使用示例。我们有 ASP.NET MVC 操作代码:

[HttpPost]
[ExportModelStateToTempData]
public RedirectToRouteResult ChangePassword(int id, UserChangePasswordVM changePassword)
{
    if (ModelState.IsValid)
    {
        var user = _userService.GetUserByID(id);

        //Checking old password. Administrators can change password of every user, 
        //providing his password instead of user's old password.
        bool oldPasswordIsCorrect = _loginService.CheckPassword(
            CurrentPrincipal.IsInRole(CTRoles.IsAdmin) ?
            CurrentPrincipal.User.UserName : user.UserName,
            changePassword.OldPassword);

        if (oldPasswordIsCorrect)
        {
            TempDataWrapper.Message =
                _userService.ChangePassword(user.UserName, changePassword.NewPassword) ?
                CTRes.PasswordChangedSuccessfully : CTRes.ErrorProcessingRequest;
        }
        else
        {
            ModelStateWrapper.AddModelError("ChangePassword.OldPassword",
                CTRes.CurrentPasswordIsNotValid);
        }
    }

    return RedirectToAction(ControllerActions.Edit, new { id });
}

这是一种简单的方法。它需要用户 id 和密码更改表单的视图模型。如果模型状态有效,它会从服务层检索用户并调用函数来检查他的旧密码。管理员不必提供用户的旧密码,自己的就足够了。如果密码正确,则调用更改用户密码的函数。在成功或失败的情况下,适当的消息被放置在 TempData 中。操作以重定向到用户编辑页面结束,其中包含更改密码的表单并显示所有错误。

我有几个问题:

  • 这段代码应该测试什么?
  • 代码中的 if 语句很少。你会为每个场景编写一个测试吗?
  • 您在控制器中测试什么?

代码中使用的接口和类(实现是在构造函数中注入的,但没关系):

public interface IModelStateWrapper
{
    void AddModelError(string name, string error);
    bool IsValid { get; }
}

public interface IUserService
{
    User GetUserByID(int id);
    bool ChangePassword(string userName, string newPassword);
}

public interface ILoginService
{
    bool CheckPassword(string userName, string password);
}

public interface ITempDataWrapper
{
    string Message { get; set; }
}

public class UserChangePasswordVM : IValidatableObject
{
    [DataType(DataType.Password)]
    public string OldPassword { get; set; }

    [DataType(DataType.Password)]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    public string NewPasswordConfirmation { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (string.IsNullOrEmpty(NewPassword))
            yield return new ValidationResult(CTRes.PasswordNotEmpty, new[] { "NewPassword" });

        if (string.IsNullOrEmpty(NewPasswordConfirmation))
            yield return new ValidationResult(CTRes.ConfirmPassword, new[] { "NewPasswordConfirmation" });

        if (NewPassword != null)
            if (!NewPassword.Equals(NewPasswordConfirmation))
                yield return new ValidationResult(CTRes.PasswordsDontMatch, new[] { "NewPasswordConfirmation" });
    }
}
4

1 回答 1

2

单元测试应该简单易懂。您应该避免在单个测试中测试大量场景。因为如果某些断言失败,那么您不知道以下断言会发生什么。单元测试是一种文档。您应该将较大的分解成较小的,这样您就可以很好地控制单元测试。

我最近开始进行单元测试,尤其是针对 ASP.NET MVC 项目。每当我看到 if..else 时,我都会进行两次测试。如果我有这样的控制器操作,那么以下是我将编写的单元测试。

1. 动作结果测试

这是一个简单的测试,在这个测试中,我将检查输出操作结果是否包含操作名称ControllerActions.Editid路由值。由于无论条件如何,您总是返回具有相同值的相同操作结果,因此一个单元测试就足够了。

2. 角色测试

所以在这里我将编写两个单元测试,一个用于admin其余的,另一个用于其余的。我将为. _loginService_ admin_ (这是一个自定义对象吗?我不确定你将如何模拟它)。_loginServiceCurrentPrincipal.User.UserNameCurrentPrincipal

在非管理员测试中,我将在模拟对象中设置期望值,以便user.UserName传递给_loginService我期望的值。

在后面的测试中,我必须模拟_userService并存根该GetUserByID方法以返回自定义用户。

3.测试正确/错误的旧密码

这里我会写两个测试用例。如果旧密码是正确的,我是否得到了一些价值,TempData也不应该有模型错误,而其他测试则相反。

4.密码修改成功/失败测试

这里我们可能需要两个测试用例来测试TempData密码修改成功或异常失败时返回的消息。

于 2012-06-30T04:49:57.070 回答