C# 我应该如何测试一个复杂的方法?

C# 我应该如何测试一个复杂的方法?,c#,unit-testing,C#,Unit Testing,我想测试一个复杂的方法, 它接收用户、用户更新的详细信息(密码、恢复问题和电子邮件)和密码 并应检查以下各项: 确保用户更新了他必须更新的所有字段 独立验证每个字段 确保密码与用户密码匹配(验证) 更新必填字段 如果密码/问题答案已更改,请重新加密敏感数据的特殊密钥(这是一个随机长密钥,用于加密敏感数据,如生日、名字等…密钥还通过密码和问题答案加密保存,而不是现在保存) 主方法使用小方法,即使用其他小方法。 问题是它们都是私有方法,没有理由不使用它们(我不打算在其他任何地方使用它们) 测试这一切

我想测试一个复杂的方法, 它接收用户、用户更新的详细信息(密码、恢复问题和电子邮件)和密码

并应检查以下各项:
  • 确保用户更新了他必须更新的所有字段
  • 独立验证每个字段
  • 确保密码与用户密码匹配(验证)
  • 更新必填字段
  • 如果密码/问题答案已更改,请重新加密敏感数据的特殊密钥(这是一个随机长密钥,用于加密敏感数据,如生日、名字等…密钥还通过密码和问题答案加密保存,而不是现在保存)
  • 主方法使用小方法,即使用其他小方法。 问题是它们都是私有方法,没有理由不使用它们(我不打算在其他任何地方使用它们)

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

    实现如下所示:
    UpdateUserDetails(此用户,
    用户详细信息用户详细信息,
    字符串密码,
    输出错误列表(错误列表)
    {
    更新操作;
    errorList=user.ValidateUserDetails(userDetails,out操作);
    if(errorList.IsSuccess()&&actions!=UpdateActions.None)
    {
    var status=Auth(user.UserId,password);//易于模拟
    if(状态:IsSuccess)
    {
    尝试
    {
    var newUser=user.UpdateUserDetails(userDetails,actions);
    犯罪
    返回新用户;
    }
    抓住
    {
    回降;
    投掷;
    }
    }
    其他的
    errorList.Add(AuthErrors.ErrorPassword);
    }
    返回null;
    }
    枚举更新操作
    {
    无=0,
    密码=1,
    电子邮件=2,
    问题=4,
    全部=密码、电子邮件和问题
    }
    
    编辑: 顺便说一句,我很容易模仿Auth实现,还有DAL实现(向用户发起攻击),其他都是问题

    仅就方法内部的方法给出一些观点:

    ValidateUserDetails(此用户,
    用户详细信息用户详细信息,
    out UpdateActions(操作)
    {
    actions=UpdateActions.None;
    ErrorList ErrorList=新的ErrorList()
    if(userDetails.password!=null | | user.RequirePasswordUpdate)
    {
    errorList.AddRange(validatePassword(userDetails.password)//PublicMethod,已经有了测试。
    actions.Add(UpdateActions.Password)//ExtensionMethod
    }
    if(userDetails.email!=null | | user.RequireEmailUpdate)
    {
    errorList.AddRange(validateEmail(userDetails.Email)//PublicMethod,已经有了测试。
    actions.Add(UpdateActions.Email)//ExtensionMethod
    }
    if(userDetails.Question!=null | | | userDetails.QuestionAnswer | | | user.requirementUpdate)
    {
    errorList.AddRange(validateQuestion(userDetails.Question,userDetails.QuestionAnswer)//PublicMethod,已经有了测试。
    actions.Add(UpdateActions.Question)//ExtensionMethod
    }
    }
    UpdateUserDetails(此用户、UserDetails用户详细信息、UpdateActions操作、字符串oldPassword)
    {
    if(actions.Has(UpdateActions.UpdatePassword)
    user.UpdatePassword(userDetails.password)
    ...
    返回DataAccess.UpdateUser(用户);//易于模拟
    }
    UpdatePassword(此用户、字符串密码、字符串密码)
    {
    user.Password=\u ienceryptionmanager.BcryptEncrypt(Password);//易于模拟的加密方法
    user.SensKey=\u ienceryptionmanager.DesEncrypt(\u ienceryptionmanager.DesDecrypt(user.SensKey,
    旧密码),
    密码);
    }
    
    非常感谢您的帮助,
    谢谢大家,

    Amir。

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

    您可以使单元测试项目“参见”通过使用包含这些方法的项目的
    AssemblyInfo.cs
    中的
    InternalsVisible
    (MSDN文档)属性来创建私有方法

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


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

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

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

    • CanValidateUserDetails()
    • WontaddUserWithErrorPassword()
    • CanAddUserWithValidPassword()
    • CanAuthenticateUser()
    在断言所有这些方法(特性)都通过了测试后,只需对
    UpdateUserDetails()
    实际执行的操作进行单元测试,即:

    user.UpdateUserDetails(userDetails, actions);
    
    通过:

    • CanUpdateUserDetails()

    不要费心测试私有位,
    UpdateUserDetails
    是您感兴趣的内容,此方法应首先进行测试。私有部分将作为公共方法测试的附带损害进行测试。最好
    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);
                                          
    }
    
    user.UpdateUserDetails(userDetails, actions);