我该如何测试复杂的方法?

时间:2012-04-12 07:51:57

标签: c# unit-testing

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

它应检查以下内容:

  1. 确保用户更新了他必须
  2. 的所有字段
  3. 独立验证每个字段
  4. 确保密码与用户密码(验证)
  5. 匹配
  6. 更新必填字段
  7. 如果密码/问题解答已更改,请重新加密敏感数据的特殊密钥(它是用于加密敏感数据的随机长密钥,例如生日,名字和内容......密钥也会被保存。加密密码和问题答案现在不是重点。
  8. 主要方法使用小方法,使用其他小方法。 问题是它们都是私有方法,并且没有理由它们应该是其他方式(我不会在其他地方使用它们)

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

    实现看起来像这样:

    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 个答案:

答案 0 :(得分:1)

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

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

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

断言所有这些方法(功能)传递之后进行测试时,只需单元测试UpdateUserDetails()实际执行的操作:

user.UpdateUserDetails(userDetails, actions);

通过:

  • CanUpdateUserDetails()

答案 1 :(得分:1)

不要打扰测试私有位,UpdateUserDetails是你感兴趣的,这个方法应该初步测试。私有部件将作为公共方法测试的附带损害进行测试。最好的部分是,你已经知道你应该测试什么,你自己用你发布的列表指定它。但是,它可以改进一点:

  

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

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

  

独立验证每个字段

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

  

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

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

  

更新必填字段

这是经过测试的单位,此时是否应正确更新这些字段。

  

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

密钥生成应该在其他地方完成,因为它听起来与更新用户详细信息不同,并且可能值得对其进行测试。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

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

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