我在我的应用程序中使用了这个服务定位器模式,并实现为单例:
现在我想测试一下。到目前为止,我已经写了一个测试,证明我的班级是单身人士。我也写了这个测试:
[Test]
[ExpectedException(typeof(ApplicationException))]
public void GetService_Throws_Exception_When_Invalid_Key_Is_Provided()
{
locator.GetService<IRandomService>();
}
但我不喜欢上次测试,因为我永远不会使用IRandomService。所以我正在寻找一种更好的方法来测试GetService<T>
抛出异常。另外,我想知道我是否可以为这个课程编写任何其他相关测试。
我正在使用最新版本的NUnit。
干杯
答案 0 :(得分:4)
有些事情:
C#中的单例模式:
public class MySingleton()
{
//Could also be a readonly field, or private with a GetInstance method
public static MySingleton Instance {get; private set;}
static MySingleton()
{
Instance = new MySingleton();
}
private MySingleton() { ... }
}
...
//in external code
var mySingletonInstanceRef = MySingleTon.Instance; //right
var mySingletonInstanceRef = new MySingleton(); //does not compile
//EDIT: The thread-safe lazy-loaded singleton
public class MySingleton()
{
//static fields with initializers are initialized on first reference, so this behaves lazily
public static readonly MySingleton instance = new MySingleton();
//instead of the below you could make the field public, or have a GetInstance() method
public static MySingleton Instance {get{return instance;}
private MySingleton() { ... }
}
服务定位器是一种反模式。听起来不错,但它并没有真正解决它所要解决的问题。主要问题是它将您与服务定位器紧密联系在一起;如果定位器发生变化,则使用它的每个类都会发生变化。相比之下,依赖注入可以在没有花哨的框架的情况下完成;您只需确保通过其构造函数将任何复杂,昂贵,可重用等对象传递给需要它的对象。 DI / IoC框架通过确保提供所需的所有依赖关系来简化此过程,即使图中的对象无法知道其子项的依赖关系。
您已经开发了这门课程。 TDD / BDD的精神在于您编写的测试将证明您尚未编写的代码是正确的。我并不是说现在编写测试不会达到目的,但是测试失败需要打开并修复对象,如果代码已经集成,则可能会破坏其他内容。
单元测试大量使用永远不会看到生产的构造。存在模拟,存根,代理和其他“测试助手”,以将被测对象与通常集成的环境隔离开来,保证如果输入为A,B和C,则被测对象将执行X,无论如何它是否正常挂钩会给它A,B和C.因此,不要担心创建简单的结构,如你不会在生产中使用的骨架界面;只要它是您在测试用例中所期望的输入的良好表示,就可以了。
答案 1 :(得分:2)
但我不喜欢上次测试,因为我永远不会使用
IRandomService
。
但这就是重点。您在测试设置方法(排列)中连接定位器,然后查找未连接的键(act),然后检查是否抛出了异常(断言)。您不需要使用实际使用的类型,只是希望您的方法有效。
此外,我想知道我是否可以为此课程编写任何其他相关测试。
好吧,我将在这里回答一个不同的问题。
服务定位器模式是邪恶的吗?
是的,这是纯粹的邪恶。
它破坏了依赖注入的目的,因为它不会使依赖显式化(任何类都可以从服务定位器中取出任何东西)。此外,它使您的所有组件都依赖于这一类。
它使维护成为令人难以置信的噩梦,因为现在你有一个组件遍布整个代码库。你已经与这一课紧密结合了。
此外,测试是一场噩梦。假设你有
public class Foo {
public Foo() { // }
public string Bar() { // }
}
您要测试Foo.Bar
。
public void BarDoesSomething() {
var foo = new Foo();
Assert.Equal("Something", foo.Bar());
}
并且您运行测试并获得异常
ServiceLocator could not resolve component Frob.
什么?哦,那是因为你的构造函数看起来像这样:
public Foo() {
this.frob = ServiceLocator.GetService<Frob>();
}
不断。
避免,避免,避免。
答案 2 :(得分:1)
我不太明白你的问题。您是否对要求在生产中永远不会使用的类型不满意?您是否对以非生产代码指示的方式使用服务定位器感到不满意?测试本身对我来说没问题 - 你正在请求一些不存在的东西并证明预期的行为会发生。对于单元测试,这样的事情是完全合理的。
使用依赖注入容器时我们做的一件事是将我们的应用程序布线阶段分成模块,然后我们尝试在集成测试中解析根类型以确保应用程序可以连线。如果接线测试失败,这是一个好的迹象,表明应用程序无法正常工作(虽然它没有证明应用程序有效)。使用服务定位器时,做这种事情会很棘手。
我同意100%与杰森同意 - 服务定位器似乎是一个好主意,但很快变得讨厌。它们'pull'(使用服务定位器实例的类型必须与它耦合),而依赖注入容器'push'(绝大多数应用程序是DI不可知的,因此代码不那么脆弱且更易于重复使用)。
其他一些事情:
[ExpectedException]
已弃用。
使用Assert.Throws
代替ApplicationException
是
也弃用了。