【问题标题】:Mocking the instantiation of an object for a unit test模拟单元测试的对象实例化
【发布时间】:2023-03-09 05:41:01
【问题描述】:

我目前正在重构一些执行 Windows 模拟以实现可测试性的代码,但遇到了一些障碍。这是我遇到问题的代码:

...
if (LogonUserA(user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) > 0)
{
    if (DuplicateToken(token, 2, ref tokenDuplicate))
    {
        var tempWindowsIdentity = new System.Security.Principal.WindowsIdentity(tokenDuplicate);
        var impersonationContext = tempWindowsIdentity.Impersonate();
        ...
    }
...
}

如何模拟实例化 WindowsIdentity 对象的行为?我已经想到了各种替代方案:

  • 传入将创建实例并模拟其行为的工厂类
  • 传入处理实例创建的委托(即像 C++ 函数指针)

这些替代方案对我来说似乎都不是特别好,因为我担心它们会模糊方法的意图,因为方法签名看起来如下所示:

public bool Impersonate(string user, string password, string domain, Factory factory)

public bool Impersonate(string user, string password, string domain, delegate WinIDCreator)

因为该方法的目的是模拟特定用户,所以对我来说,应该向它提供 Factory 类或 Delegate 是没有意义的。但是,我确实想隔离并模拟这种行为,因为每次运行一堆单元测试时都会创建一个新的 WindowsIdentity 实例,这让我感到不舒服。

有什么想法或方法吗?

【问题讨论】:

    标签: c# unit-testing rhino-mocks


    【解决方案1】:

    我认为您对工厂的想法是正确的,但我会将工厂注入到类构造函数中,而不是作为方法参数。如果未提供,您的默认构造函数可以创建默认 Factory 的实例。

    如果您不考虑 LogonUserA 和 DuplicateToken 方法,您还会遇到一些问题——比如在单元测试中需要真实的登录 ID 和密码。我建议围绕这个实现一个接口,你也可以在构造函数中注入一个瘦包装器。

    以下是一些亮点,向您展示如何开始构建它。

    public interface ILogonHelpers
    {
         bool LogonUser( string user, string domain, string password, ref int token );
         void DuplicateToken(  int token, ref int duplicateToken );
    }
    
    public class MyClass
    {
        public MyClass( ILogonHelper logonHelper, IIdentityFactory factory )
        {
            this.LogonHelper = logonHelper ?? new DefaultLogonHelper();
            this.IdentityFactory = factory ?? new DefaultIdentityFactory();
        }
    
        ...
    
    if (this.LogonHelper.Logon(user, domain, password, ref token) > 0)
    {
        if (this.LogonHelper.DuplicateToken(token, ref tokenDuplicate))
        {
            var tempWindowsIdentity = this.IdentityFactory.CreateIdentity(tokenDuplicate);
            var impersonationContext = tempWindowsIdentity.Impersonate();
            ...
        }
    ...
    }
    

    【讨论】:

    • 谢谢。我应该补充一点,我已将登录内容封装到 NativeMethods 类中,并且只是将它们模拟出来。我对将工厂注入类的担忧与将其用作方法参数相同 - 让一个类依赖于类工厂并不“自然”......
    • 即这个类执行模拟,因此它依赖于 NativeMethods 类(它封装了我需要的 Win API 东西)。我发现很难说服自己让它也依赖于 Factory 类是可以的。
    【解决方案2】:

    我是 Java 开发人员,但是 ...

    为什么不将“Factory”设为包含 Impersonate 方法的类的属性?

    “Factory”属性,可能是“windowIdentityFactory”,可以在构造函数中设置,也可以通过 setter 方法(使用某种依赖注入)进行设置。

    测试时,您将为班级提供一个模拟工厂(如您所建议的那样)。在生产中,你给它真正的交易。

    ...
    if (LogonUserA(user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) > 0)
    {
        if (DuplicateToken(token, 2, ref tokenDuplicate))
        {
            var tempWindowsIdentity = windowIdentityFactory.newInstance(tokenDuplicate);
            var impersonationContext = tempWindowsIdentity.Impersonate();
            ...
        }
    ...
    }
    

    【讨论】:

      【解决方案3】:

      我会创建一个您可以模拟的虚拟模拟方法。 Impersonate 方法如下:

      
      public virtual WindowsImpersonationContext Impersonate(string tokenDuplicate) {
          var tempWindowsIdentity = new System.Security.Principal.WindowsIdentity(tokenDuplicate);  
          var impersonationContext = tempWindowsIdentity.Impersonate();
      
          return impersonationContext;
      }
      

      【讨论】:

        【解决方案4】:

        我同意 tvanfosson 关于通过构造函数注入工厂的观点(或者如果您认为工厂是可选的,则可能通过属性)。但是,由于您对此不太满意,我建议您查看TypeMock Isolator,它可以让您以一种非常规的方式模拟实例。它通过注入 IL 来工作,类似于探查器所做的工作,它允许您在不更改对象设置的情况下使用模拟。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-31
          • 1970-01-01
          • 1970-01-01
          • 2014-06-07
          相关资源
          最近更新 更多