2

我有一个想要测试的复杂方法,它接收用户、UserUpdatedDetails(密码、恢复问题和电子邮件)和密码。

它应该检查以下内容:

  1. 确保用户更新了他必须更新的所有字段
  2. 独立验证每个字段
  3. 确保密码与用户密码匹配(验证)
  4. 更新必填字段
  5. 如果密码/问题答案已更改,请重新加密敏感数据的特殊密钥(它是一个随机长密钥,用于加密敏感数据,例如生日,名字和东西......密钥也通过密码加密保存和问题的答案不是现在的重点)。

主要方法使用小方法,即使用其他小方法。问题是它们都是私有方法,没有理由不应该这样做(我不会在其他任何地方使用它们)

测试这一切将是一场噩梦,即使我将独立测试每个小方法,它也不能让我清楚地了解主要方法正在做它必须做的事情。

实现看起来像这样:

UpdateUserDetails(this User user,
                  UserDetails userDetails,
                  string Password,
                  out ErrorList<AuthErrors> errorList)
{
    UpdateActions actions;
    errorList = user.ValidateUserDetails(userDetails, out actions);
    if (errorList.IsSuccess() && actions != UpdateActions.None)
    {
         var status = Auth(user.UserId, password); // Easy to mock
         if (status.IsSuccess)
         {
             try
             {
                 var newUser = user.UpdateUserDetails(userDetails, actions);
                 commit;
                 return newUser;
             }
             catch
             {
                 rollback;
                 throw;
             }
         }
         else
             errorList.Add(AuthErrors.WrongPassword);
    }
    return null;
}
enum UpdateActions
{
    None = 0,
    Password = 1,
    Email = 2,
    Question = 4,
    All = Password & Email & Question
}

编辑:顺便说一句,我很容易模拟 Auth 实现,以及 DAL 实现(更新用户)所有其他都是问题......

只是为了对方法内部的方法给出一些看法:

ValidateUserDetails(this User user, 
                    UserDetails userDetails,
                    out UpdateActions actions)
{
    actions = UpdateActions.None;
    ErrorList<AuthErrors> errorList = new ErrorList<AuthErrors>()
    if (userDetails.password != null || user.RequirePasswordUpdate)
    {
        errorList.AddRange(validatePassword(userDetails.password) // PublicMethod, already, which has tests.
        actions.Add(UpdateActions.Password) // ExtensionMethod
    }
    if (userDetails.email != null || user.RequireEmailUpdate)
    {
        errorList.AddRange(validateEmail(userDetails.Email) // PublicMethod, already, which has tests.
        actions.Add(UpdateActions.Email) // ExtensionMethod
    }
    if (userDetails.Question != null || userDetails.QuestionAnswer || user.RequireQuestionUpdate)
    {
        errorList.AddRange(validateQuestion(userDetails.Question, userDetails.QuestionAnswer) // PublicMethod, already, which has tests.
        actions.Add(UpdateActions.Question) // ExtensionMethod
    }
}
UpdateUserDetails(this User user, UserDetails userDetails, UpdateActions actions, string oldPassword)
{
    if (actions.Has(UpdateActions.UpdatePassword)
        user.UpdatePassword(userDetails.password)
    ...
    return DataAccess.UpdateUser(user); // Easy to Mock
}
UpdatePassword(this User user, string password, string oldPassword)
{
    user.Password = _IEncryptionManager.BcryptEncrypt(password); // easy to mock encryption methods
    user.SensKey = _IEncryptionManager.DesEncrypt(_IEncryptionManager.DesDecrypt(user.SensKey,
                                                          oldPassword),
                           password);
                                      
}

感谢您的帮助,
谢谢问候,
阿米尔。

4

4 回答 4

1

您应该首先单独对每个功能进行单元测试,因为您UpdateUserDetails()首先使用 验证用户详细信息ValidateUserDetails(),然后使用 等检查用户的身份验证Auth()

然后,您应该首先创建以下单元测试:

  • CanValidateUserDetails()
  • WontAddUserWithWrongPassword()
  • CanAddUserWithValidPassword()
  • CanAuthenticateUser().
  • ...

断言所有这些方法(功能)都通过了测试之后,只需对UpdateUserDetails()实际执行的操作进行单元测试:

user.UpdateUserDetails(userDetails, actions);

通过:

  • CanUpdateUserDetails().
于 2012-04-12T08:06:01.623 回答
1

不要费心测试私有位,UpdateUserDetails这是您感兴趣的,并且应该首先测试此方法。私人部分将作为公共方法测试的附带损害进行测试。最好的部分是,您已经知道应该测试什么,并且您在发布的列表中自己指定了它。但是,它可以改进一点:

确保用户更新了他必须更新的所有字段

这本质上是输入数据检查,应该这样测试(当用户发布不完整的数据时会发生什么?异常,错误消息?这是你想要测试/验证的 -当用户没有更新所有字段时会发生什么以及它是否确实发生了)。

独立验证每个字段

当字段无效时会发生什么?这是应该测试的(抛出异常,错误消息等)。成功的验证导致成功的更新,这将在最后一步进行测试。

确保密码与用户密码匹配(验证)

通常身份验证是繁重/复杂的过程。作为用户详细信息更新的一部分,它真的需要保密吗?除非你有充分的理由保持这种方式,否则我会说它是提取分离存在并作为依赖注入的一个很好的候选者。

更新必填字段

这是此处测试的单元,此时应测试这些字段是否正确更新。

如果密码/问题答案已更改,请重新加密敏感数据的特殊密钥(它是一个随机长密钥,用于加密敏感数据,例如生日,名字和东西......密钥也通过密码加密保存和问题的答案不是现在的重点)。

密钥生成应该在其他地方完成,例如,这听起来与更新用户详细信息的责任不同,也许值得对其进行测试。

于 2012-04-12T08:27:43.390 回答
0

为了实现这一点,您必须遍历此方法中的所有可用路径,尝试使用一些可用的模拟框架,如 rhino 模拟。使用模拟框架模拟方法的所有输入,以便在内部生成不同的行为。

于 2012-04-12T07:57:56.860 回答
0

您可以使用包含方法的项目中的InternalsVisible(MSDN 文档here)属性使您的单元测试项目“看到”私有方法。AssemblyInfo.cs

但是,如果您需要使用它,这可能表明您的解决方案存在架构问题。

另一种选择是使用一些函数,该函数返回指示工作流程成功的状态代码(而不是使用您在上面发布的扩展方法)。然后您可以在单元测试中测试不同的返回值。

于 2012-04-12T08:01:20.233 回答