我正在尝试使一个非常大、非常遗留的项目可测试。
我们的大多数代码都使用了许多静态可用的服务。问题是这些很难模拟。他们曾经是单身人士。现在它们是伪单例——相同的静态接口,但函数委托给可以切换的实例对象。像这样:
class ServiceEveryoneNeeds
{
public static IImplementation _implementation = new RealImplementation();
public IEnumerable<FooBar> GetAllTheThings() { return _implementation.GetAllTheThings(); }
}
现在在我的单元测试中:
void MyTest()
{
ServiceEveryoneNeeds._implementation = new MockImplementation();
}
到现在为止还挺好。在产品中,我们只需要一个实现。但是测试并行运行并且可能需要不同的模拟,所以我这样做了:
class Dependencies
{
//set this in prod to the real impl
public static IImplementation _realImplementation;
//unit tests set these
[ThreadStatic]
public static IImplementation _mock;
public static IImplementation TheImplementation
{ get {return _realImplementation ?? _mock; } }
public static void Cleanup() { _mock = null; }
}
进而:
class ServiceEveryoneNeeds
{
static IImplementation GetImpl() { return Dependencies.TheImplementation; }
public static IEnumerable<FooBar> GetAllTheThings() {return GetImpl().GetAllTheThings(); }
}
//and
void MyTest()
{
Dependencies._mock = new BestMockEver();
//test
Dependencies.Cleanup();
}
我们采用这条路线是因为构造函数将这些服务注入到每个需要它们的类中是一个庞大的项目。同时,这些是我们代码库中大多数功能所依赖的通用服务。
我知道这种模式是不好的,因为它隐藏了依赖关系,而不是使依赖关系显式的构造函数注入。
但是好处是:
- 我们可以立即开始单元测试,而不是进行 3 个月的重构然后进行单元测试。
- 我们仍然有全局变量,但这似乎比我们原来的要好。
虽然我们的依赖关系仍然是隐含的,但我认为这种方法比我们拥有的方法要好得多。除了隐藏的依赖关系之外,这是否比使用适当的 DI 容器更糟糕?我会遇到什么问题?