我的工具中有两个案例,我在使用静态字段进行集成测试时遇到了一些困难。在应用程序中这些字段没有问题。在我的测试中,我基本上用每种测试方法创建一个新的应用程序当我在一次测试运行中运行这些时,你可以想象,会有一些问题。首先让我展示两个这样的案例。
案例1
class SomeClass
{
private static IService service;
public static void Initialize(IService service)
{
SomeClass.service = service;
}
public void DoSomthing()
{
service.Foo();
}
}
基本上这个类的对象将被大量创建。为了能够使用IService
对象,它将存储为静态字段。
在我的测试中,这是一个问题,因为IService
实际上是IServiceProvider
,并且在第一次测试中仅检索一次服务。在第二次测试中,使用第一次测试的服务。
在实际应用中,只有一个IService
或IServiceProvider
。因此,我们在这里没有问题。
案例2
abstract class BaseClass
{
private static readonly Lazy<SpecificClass> specificClass = new Lazy<SpecificClass>(() => new SpecificClass());
public static SpecificClass SpecificClass
{
get { return specificClass.Value; }
}
}
class SpecificClass : BaseClass
{
}
这对我的测试来说更麻烦,因为即使我创建了一个完整的新应用程序,当在第一个测试中使用SpecificClass
时,它在第二个测试中也是同一个对象。
在我的测试中,我有一个内存泄漏,因为SpecificClass
有一个列表,它记住第一次测试中的对象。随着每个测试,越来越多的对象被添加到列表中。
在实际应用程序中,列表仅在应用程序启动时填充一次。所以这里没有内存泄漏。
我知道,测试通常会显示设计缺陷,但我在这里看不到。删除这些我能想到的静态字段的唯一理由是,我的测试不起作用。
所以现在我的问题是,为什么在这些情况下使用静态字段会被认为是错误的代码,还是不是?我不想知道这些案件的解决方案。我只需要证明为什么除了“我无法正确测试它”之外,还需要更改代码。
答案 0 :(得分:3)
这里的设计缺陷是组件决定其依赖项的生命周期,这在实际应用程序中都很糟糕,并且在编写测试时会使事情变得更难。
您需要的是使用具有依赖项注入支持的控件容器的反转,并让它通过配置注入依赖项的生命周期来决定。
例如,在Castle Windsor中,它将配置如下:
IWindsorContainer container = new WindsorContainer();
container.Register
(
Component.For<SomeClass>().LifeStyleTransient(),
Component.For<IService>().ImplementedBy<SomeService>().LifeStyleSingleton()
);
所以你决定IService
是一个单身的配置,但你的代码依赖于SomeClass
的实例将注入一个IService
实现的实例。
为什么它对实际应用程序不利。我只需要一个理由 为什么需要更改代码。
没有简单的单元测试:如果不使用实例构造函数和/或属性,则无法使用自动依赖项注入。这使得您的代码更难以测试,因为简单的配置无法注入伪而不是实际的实现。
无自动依赖注入。不能将不同的应用程序和服务配置为使用相同或不同的依赖接口实现,并且您丢失了一个大特性:对象生命周期(瞬态,单一,每个请求,每个线程)可以通过配置定义你的代码不应该知道这一点。
没有单一责任原则。您的类是负责决定其依赖关系的生命周期,还是不应该定义它的框架?
你在Jon Skeet的评论中写了一些评论:
@JonSkeet我知道为什么他们对我的测试不好。但那不是 只要它适用于实际应用程序,就可以接受参数。那 这就是为什么我想找到一个不同的论点。 -
软件质量与实际应用程序一样真实。如果开发高质量的软件对您来说不是一个公认的理由,那么您可能需要回到根源并找到为什么应用程序或服务必须是可测试的。
答案 1 :(得分:0)
您所描述的是所有不适合依赖注入的对象的有效用例(ORM实体就是一个例子)。
在这些情况下,静态字段的替代方法是将依赖项作为方法参数提供:
class SomeClass
{
public void DoSomething(IService service)
{
service.Foo();
}
}
除了更简单的测试之外,这种方法的一个优点是您不必小心手动正确初始化静态(有时可能难以实现,并且当静态依赖性开始依赖于此时特别难以维护彼此,因为初始化静态字段的顺序等。
此外,将来重构和更改代码更容易,因为当您决定将使用它们的逻辑提取到单独的类等时,您不必移动/复制静态字段定义和初始化。