代码之家  ›  专栏  ›  技术社区  ›  JamesEggers

如何模拟静态单例?

  •  14
  • JamesEggers  · 技术社区  · 15 年前

    我拥有的课程示例与以下类似:

    class Example1 : ISomeInterface
    {
        private static ISomeInterface _instance;
    
        private Example1()
        {
            // set properties via private static methods
        }
    
        static Example1()
        {
            _instance = new Example1();
        }
    
        public static ISomeInterface Instance() 
        {
            get { return _instance; }
        }
    
        // Instance properties 
    
        // Other Instance Properties that represent objects that follow a similar pattern.
    }
    

    所以当我给上面的班级打电话时,看起来是这样的。。。

    Example1.Instance.SomeObject.GoDownARabbitHole();
    

    有没有办法让我模仿一下 SomeObject.GoDownARabbitHole() 在这种情况下,还是模拟这种情况?

    7 回复  |  直到 15 年前
        1
  •  13
  •   Mark Seemann    15 年前

    单身汉与可测试性不一致,因为他们很难改变。你最好使用

    public class MyClass
    {
        private readonly ISomeInterface dependency;
    
        public MyClass(ISomeInterface dependency)
        {
            if(dependency == null)
            {
                throw new ArgumentNullException("dependency");
            }
    
            this.dependency = dependency;
        }
    
        // use this.dependency in other members
    }
    

    注意警卫克劳斯和 readonly 关键字保证ISomeInterface实例始终可用。

    这将允许您使用Rhino mock或其他动态模拟库来注入 Test Doubles 将接口插入到消费类中。

        2
  •  27
  •   No answer    11 年前

    只要使用反射。

    使用提供的示例代码,您需要确保在将静态字段设置为模拟对象之前调用静态构造函数。否则,它可能会覆盖模拟对象。在设置测试之前,只需调用singleton上没有影响的任何内容。

    ISomeInterface unused = Singleton.Instance();
    
    System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
    
    Mock<ISomeInterface> mockSingleton = new Mock<ISomeInterface>();
    instance.SetValue(null, mockSingleton.Object);
    

    我提供了用Moq进行模拟的代码,但我猜rhinomocks非常类似。

        3
  •  6
  •   Peter Seale    15 年前

    下面是一种使用委托的低接触方法,委托可以在初始设置并在运行时更改。最好通过示例(特别是模仿DateTime.Now)来解释:

    http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/11/09/systemtime-versus-isystemclock-dependencies-revisited.aspx

        4
  •  4
  •   Stealth Rabbi    8 年前

    : Working Effectively with Legacy Code

    .

    public class PermitRepository
    {
        private static PermitRepository instance = null;
        private PermitRepository() {}
        public static void setTestingInstance(PermitRepository newInstance)
        {
            instance = newInstance;
        }
        public static PermitRepository getInstance()
        {
            if (instance == null) 
            {
                instance = new PermitRepository();
            }
            return instance;
        }
        public Permit findAssociatedPermit(PermitNotice notice) 
        {
        ...
        }
    ...
    }
    

    现在我们有了这个setter,我们可以创建一个 允许存储并设置它。我们希望在测试设置中编写如下代码:

    public void setUp() {
    PermitRepository repository = new PermitRepository();
    ...
    // add permits to the repository here
    ...
    PermitRepository.setTestingInstance(repository);
    }
    
        5
  •  1
  •   TheSean    15 年前

    您可以模拟接口,也可以模拟接口。然后,重构使用依赖项注入的代码,以获得对singleton对象的引用。我在代码中多次遇到这个问题,我最喜欢这个解决方案。

    public class UseTheSingleton
    {
        private ISomeInterface myX;
    
        public UseTheSingleton(ISomeInterface x)
        {
            myX = x;
        }
    
        public void SomeMethod()
        {
            myX.
        }
    }
    

    然后。。。

    UseTheSingleton useIt = UseTheSingleton(Example1.Instance);
    
        6
  •  1
  •   Finglas    15 年前

    退房 Dependency Injection .

    adapter 设计模式来围绕这个难以测试的代码编写包装器。使用 interface 对于这个适配器,您可以单独测试代码。

    Google Testing Blog ,特别是米斯科的文章。

    您说您正在编写测试,所以可能已经太晚了,但是您能将静态代码重构到实例中吗?或者说,这个类应该保持静态状态,这有什么真正的原因吗?

        7
  •  0
  •   Steve Freeman    14 年前

    使用Resharper,不是吗?),大部分操作都很简单。如果这真的很复杂,那么您可以有多个构造函数,一个设置新的依赖项字段,另一个调用第一个,并将单例作为默认值。