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

単体テストはシンプルで理解しやすいものでなければなりません。1 回のテストで多数のシナリオをテストすることは避けてください。一部のアサートが失敗した場合、次のアサートがどうなるかわからないためです。単体テストは一種のドキュメントです。大きなものを小さなものに分割する必要があるため、単体テストを適切に制御できます。

ごく最近、特に ASP.NET MVC プロジェクトの単体テストを開始しました。if..else が表示されるたびに、2 つのテストに進みます。そのようなコントローラ アクションがある場合、次のユニット テストを記述します。

1. アクション結果のテスト

これは簡単なテストです。このテストでは、出力アクションの結果にアクション名ControllerActions.Editidルート内の値が含まれていることを確認します。条件に関係なく、常に同じ値で同じアクション結果を返すため、これには 1 つの単体テストで十分です。

2. 役割のテスト

したがって、ここでは 2 つの単体テストを作成し、1 つadminは残りの部分に使用します。のモックを作成し、_loginService期待値を設定して、ユーザーがadmin_loginService設定した値で が呼び出されるようにしますCurrentPrincipal.User.UserName。(それCurrentPrincipalはカスタムオブジェクトですか?ただし、どのようにモックするのかわかりません)。

管理者以外のテストでは、モック オブジェクトに期待値を設定して、 が期待値とともに にuser.UserName渡されるように_loginServiceします。

後のテストでは_userService、メソッドをモックしてスタブ化しGetUserByID、カスタム ユーザーを返す必要があります。

3. 古いパスワードの正誤のテスト

ここでは、2 つのテスト ケースを記述します。古いパスワードが正しい場合、値が得られているかどうかにかかわらTempDataず、モデルエラーはなく、他のテストでは逆になります。

4. パスワード変更の成否テスト

TempDataここでは、パスワードが正常に変更された場合、または何らかの例外により変更されなかった場合に返されるメッセージをテストするために、2 つのテスト ケースが必要になる場合があります。

于 2012-06-30T04:49:57.070 に答える