2

前言:我的默认操作模式是使用 IoC 容器和构造函数注入。这使得使用模拟依赖项进行测试变得微不足道。

我开始开发一个 IntelliJ 插件,我想利用控制反转。由于这是一个插件,因此实际上没有容器选项(对吗?)所以我想我需要使用服务定位器模式。

如何使用带有服务定位器模式的模拟进行测试?

我能想到的最好方法是为我的定位器使用一个接口,使用静态 getter 在每个服务的默认构造函数中设置它,并拥有一个 setter,以便我可以设置一个模拟定位器。它看起来像这样:

public class MyService {
    private IServiceLocator locator;

    public MyService() {
        setLocator(ServiceLocator.locator());
    }

    public void setLocator(IServiceLocator locator) {
        this.locator = locator;
    }
}

现在我可以模拟IServiceLocatorMyService在我的测试中设置它。然后我可以期待一个类似的调用locator.dependency1()并让它返回一个模拟的依赖项。

这种方法的主要问题是定位器设置器仅用于支持测试。有没有更好的办法?

4

2 回答 2

2

首先,我建议阅读出色的“有效地使用遗留代码”,其中有一堆处理此类事情的模式。尽管您正在编写新代码,但您会受到一些与遗留代码相同的限制。

为此,一种更简单的方法可能是提供第二个受保护的构造函数,该构造函数显式接受您的依赖项,并让无参数构造函数使用您想要的默认值。所以类似于以下内容:

 public class MyService {
    private Dependency1 dep1;
    private Dependency2 dep2;

    protected MyService(Dependency1 dep1, Dependency2 dep2) {
        this.dep1 = dep1;
        this.dep2 = dep2;
    }

    public MyService() {
         this(new ConcreteDependency1(), new ConcreteDependency2());
    }
}

您还可以添加注释以明确这是用于测试的,即Guava 的 @VisibleForTesting

于 2014-05-04T19:48:53.063 回答
1

您可以按照您描述的方式进行操作。我想到了其他选择:

您可以对服务定位器字段使用包私有访问

现在,如果您将测试类与服务放在同一个包中 - 它可以直接操作测试类中的字段 - 所以很容易模拟服务定位器。

您可以使用测试/模拟库的功能来注入服务定位器

例如 Mockito ( https://code.google.com/p/mockito/ ) 可以通过两种方式注入私有字段:

  • Whitebox类 - 但这不是最好的主意,因为您必须知道要模拟的字段名称(更改字段名称会破坏测试),并且您需要手动执行Withebox.setInternalState方法
  • @InjectMocksannotation - 您创建使用@Mockannotation 注释的服务定位器字段,并且使用和 mockito 注释的服务字段@InjectMocks将使用服务定位器字段并将其自动注入服务。

您可以使用不依赖于容器的依赖注入机制

例如查看 Google Guice 库:https ://code.google.com/p/google-guice/

您可以使用“手动”依赖注入

DI 是模式 - DI 容器/框架使这种模式易于实现,但您始终可以使用工厂类自己完成(但需要更多时间)。这是关于 DI 的精彩讨论,涵盖“手动”DI:https ://www.youtube.com/watch?v=RlfLCWKxHJ0

很难说哪个选项最适合您。如果您想坚持使用服务定位器 - 我会推荐 Mockito。如果你想要 DI - 我会推荐 Google Guice。就个人而言,我认为服务定位器更像是反模式。

于 2014-05-04T20:05:21.183 回答