我在我的应用程序中使用了此服务定位器模式并作为单例实现:
服务定位器模式
现在我想测试它。到目前为止,我已经编写了一个测试来验证我的类是否是单例。我还编写了这个测试:
[Test]
[ExpectedException(typeof(ApplicationException))]
public void GetService_Throws_Exception_When_Invalid_Key_Is_Provided()
{
locator.GetService<IRandomService>();
}
但我不太喜欢最后一个测试,因为我永远不会使用 IRandomService。因此,我正在寻找一种更好的方法来测试 GetService
是否抛出异常。我还想知道我是否可以为这门课编写其他相关测试。
我正在使用最新版本的 NUnit。
干杯
I've used this Service Locator Pattern in my Application and implemented as a Singleton:
Service Locator Pattern
And now I want test it .So far I've written a test verifying that my class is a Singleton. I've also written this test:
[Test]
[ExpectedException(typeof(ApplicationException))]
public void GetService_Throws_Exception_When_Invalid_Key_Is_Provided()
{
locator.GetService<IRandomService>();
}
But I don't really like the last test since I'm never going to use the IRandomService. So I'm looking for a nicer way to test that the GetService<T>
throws an exception. Also I like to know if there is any other relevants tests I could write for this class.
I'm using the latest version of NUnit.
Cheers
发布评论
评论(3)
有些事情:
C# 中的单例模式:
服务定位器是一种反模式。听起来不错,但它并没有真正解决它创建时要解决的问题。主要问题是它将您与服务定位器紧密耦合;如果定位器发生变化,则使用它的每个类都会发生变化。相比之下,依赖注入无需花哨的框架即可完成;你只需确保任何复杂的、昂贵的、可重用的等对象都通过其构造函数传递给需要它的对象。 DI/IoC 框架只是通过确保提供所需对象的所有已知依赖关系来简化此过程,即使图中的对象无法了解其子对象的依赖关系。
您已经开发了该课程。 TDD/BDD 的精神是,您编写的测试将证明您尚未编写的代码是正确的。我并不是说现在编写测试没有任何作用,但是失败的测试需要打开并修复对象,如果代码已经集成,您可能会破坏其他东西。
单元测试大量使用永远不会出现在生产中的结构。模拟、存根、代理和其他“测试助手”的存在是为了将被测对象与其通常集成的环境隔离开来,保证如果输入是 A、B 和 C,则被测对象将执行 X,无论因此,不要担心创建简单的构造,例如您不会在生产中使用的骨架接口;只要它能够很好地表示您在测试用例中期望的输入,就可以了。
Some things:
The Singleton Pattern in C#:
The service locator is an anti-pattern. It sounds great, but it doesn't really solve the problems that it was created to solve. The chief problem is that it tightly couples you to the service locator; if the locator changes, every class that uses it changes. By contrast, Dependency Injection can be done without a fancy framework; you just make sure any complex, expensive, reusable, etc. object is passed in to the object that needs it via its constructor. DI/IoC frameworks just streamline this process by ensuring that all known dependencies of a required object are provided, even if an object in the graph can't know about dependencies of its children.
You have already developed the class. The spirit of TDD/BDD is that you write tests that will prove that code you haven't yet written will be correct. I'm not saying writing tests now wouldn't serve a purpose, but a failing test requires the object be opened up and fixed, and if the code is already integrated you could break other things.
Unit tests make heavy use of constructs that will never see production. Mocks, stubs, proxies and other "test helpers" exist to isolate the object under test from the environment into which it is normally integrated, guaranteeing that if the inputs are A, B, and C, the object under test will do X, regardless of whether what it normally hooks into will give it A, B, and C. Therefore, don't worry about creating simple constructs like a skeleton interface that you wouldn't use in production; as long as it is a good representation of the input you would expect in the test case, it's fine.
但这就是重点。您在测试设置方法中连接定位器(排列),然后查找未连接的键(行动),然后检查是否引发了异常(断言)。您不需要使用实际要使用的类型,您只是想对您的方法是否有效有一定的信心。
好吧,我要在这里回答一个不同的问题。
是的,这是纯粹的邪恶。
它违背了依赖注入的目的,因为它没有使依赖关系显式化(任何类都可以从服务定位器中提取任何内容)。此外,它使您的所有组件都依赖于这一类。
它使维护成为一场难以置信的噩梦,因为现在您拥有的这个组件遍布您的代码库。你已经与这一类紧密耦合了。
此外,测试是一场噩梦。假设您已经有了
并且想要测试
Foo.Bar
。然后你运行你的测试,你得到一个异常
什么?哦,那是因为你的构造函数看起来像这样:
等等。
避免,避免,避免。
But that's kind of the point. You wire up your locator in the test setup method (arrange), and then you lookup a key that was not wired up (act), then you check that an exception was thrown (assert). You don't need to use types you're actually going to use, you just want to get some confidence that your method is working.
Well, I'm going to answer a different question here.
Yes, it is pure evil.
It defeats the purpose of dependency injection because it doesn't make dependencies explicit (any class can pull anything out of the service locator). Moreover, it makes your all of your components dependent on this one class.
It makes maintenance an unbelievable nightmare because now you have this one component that is just spread all over your codebase. You have become tightly coupled to this one class.
Further, testing is a nightmare. Let's say you have
and you want to test
Foo.Bar
.and you run your test and you get an exception
What? Oh that's because your constructor looks like this:
And on and on.
avoid, Avoid, AVOID.
我不太明白你的问题。您是否对请求一个永远不会在生产中使用的类型感到不满意?您是否对以不指示生产代码的方式使用服务定位器感到不满意?测试本身对我来说看起来没问题——你正在请求一些不存在的东西并证明预期的行为发生了。对于单元测试来说,这样做是完全合理的。
使用依赖项注入容器时我们做的一件事是将应用程序接线阶段分离为模块,然后我们尝试在集成测试中解析根类型以确保应用程序可以接线。如果接线测试失败,则表明该应用程序无法正常工作(尽管它也不能证明该应用程序可以正常工作)。使用服务定位器时执行此类操作会比较麻烦。
我也 100% 同意 Jason 的观点——服务定位器似乎是个好主意,但很快就会变得令人讨厌。它们“拉”(使用服务定位器实例的类型必须与其耦合),而依赖注入容器“推”(绝大多数应用程序与 DI 无关,因此代码远不那么脆弱且更可重用)。
其他一些事情:
[ExpectedException]
已弃用。使用断言.抛出
相反
ApplicationException
是也已弃用。
I don't quite understand your question. Are you dissatisfied with requesting a type that you'll never use in production? Are you dissatisfied with using the service locator in a fashion that is not indicative of production code? The test itself looks ok to me -- you're requesting something that doesn't exist and proving that the expected behaviour occurs. For a unit test, such a thing is perfectly reasonable to do.
One thing we did when using a dependency injection container was to separate our application wiring phase into modules, then we'd try and resolve the root type in an integration test to ensure that the app could be wired up. If the wiring test failed, it was a good sign that the app wasn't working (though it didn't prove that the app worked, either). It'd be tricker to do this sort of thing when using a service locator.
I agree 100% with Jason, too -- service locators seem like a good idea, but quickly turn nasty. They 'pull' (types using the service locator instance must be coupled to it), whereas Dependency Injection containers 'push' (the vast majority of the application is DI agnostic, so the code is far less brittle and more re-usable).
A couple of other things:
[ExpectedException]
is deprecated.use Assert.Throws
instead
ApplicationException
isalso deprecated.