Unit testing 应该避免服务对象的动态依赖关系吗?

Unit testing 应该避免服务对象的动态依赖关系吗?,unit-testing,language-agnostic,dependency-injection,Unit Testing,Language Agnostic,Dependency Injection,这个问题是关于基于主要价值对象和服务的可测试软件设计 使用DI容器时,具有静态依赖关系的服务可以直接实例化或配置。但是,在某些情况下,服务需要仅在运行时已知的依赖项 比方说,想象一个简单的FileSystemDataStore,其中包含一些CRUD方法,用于管理目录中的文件。此服务需要一个目录名作为其构造函数参数之一。该名称只能在运行时知道,并且必须由其合作者提供 这似乎有点问题,因为您不能在DI容器中配置这样的服务,因为它是动态的。您可能必须使用工厂来创建此类服务。然而,这将导致服务客户端的单

这个问题是关于基于主要价值对象和服务的可测试软件设计

使用DI容器时,具有静态依赖关系的服务可以直接实例化或配置。但是,在某些情况下,服务需要仅在运行时已知的依赖项

比方说,想象一个简单的FileSystemDataStore,其中包含一些CRUD方法,用于管理目录中的文件。此服务需要一个目录名作为其构造函数参数之一。该名称只能在运行时知道,并且必须由其合作者提供

这似乎有点问题,因为您不能在DI容器中配置这样的服务,因为它是动态的。您可能必须使用工厂来创建此类服务。然而,这将导致服务客户端的单元测试出现异常。您必须模拟工厂才能返回服务的模拟。这增加了单元测试的复杂性。mock返回mock通常被认为是一种测试气味


你对这个问题有什么看法?这在你的经历中是个问题吗?是否应该将这些服务重构为更“纯粹”的服务呢?

一般来说,当服务依赖于运行时值时


但是,正如问题中所指出的,这确实会影响测试的可维护性,因此如果您可以重新设计API以避免这种情况,那么您应该这样做。但这并不总是可能的。

您希望插入目录名,但在构建阶段不知道该名称。我在这里看到三种选择

1。插入提供程序

您不是说“这是您需要的目录名”,而是说“这是一个可以在运行时为您提供目录名的对象”。实现这一点的方法是声明一个构造函数参数
Provider directoryNameProvider
。构造函数将对此提供程序的引用存储为成员变量。当调用apon在运行阶段执行一些实际工作时,当需要目录名时,该类将包含如下代码:

directoryName=directoryNameProvider.get()

在java中,您实现的接口是
[javax.inject.Provider][1]
。这有一个方法:
get()
,它返回类型
T
。使用通用提供程序接口意味着您没有大量的接口

当涉及到单元测试时,您可以注入一个匿名的内部类,该类实现了
Provider
的单个方法,从而非常容易地返回一个常量值。我们的代码库有一个
SimpleProvider
类,它将给定对象包装在提供者接口中

Pro:允许您在主构造阶段构造对象。单元测试非常简单

缺点:依赖项创建问题的详细信息正在泄漏到类中,而这些问题本应完全由工厂负责。如果类已经编写并接受了
directoryName
而不是
directoryNameProvider
,那就太糟糕了

尽管有一长串的缺点,但这是我经常使用的一个选项。我认为这里缺少一个语言结构

2。稍后构造问题对象

当您了解更多信息时,可以输入内部范围。在运行阶段方法中,可以输入新的范围。这意味着您将经历一个全新的迷你构建阶段,然后是迷你运行阶段。这与您的应用程序
main()
中发生的情况类似,但级别较低

赞成:接收依赖项的类保持纯净

缺点:进入和退出太多的作用域会使应用程序和对象生命周期难以理解

3。使用方法参数

您可以决定directoryName是一个方法参数,并在运行阶段将其传递给类,而不是尝试将其作为构造函数参数注入。这实际上是决定在这种情况下不使用依赖注入样式

优点:简单

Con:将directoryName作为方法参数传递的类与需要它的类紧密耦合。实现依赖于数据库连接的替代实现将非常困难

这些是我最近一直在考虑的问题,所以我对任何评论或编辑都感兴趣。还有其他选择吗