设计问题:静态类只初始化一次,中断单元测试

时间:2013-03-31 14:36:23

标签: c# unit-testing nunit static-constructor

我有一个静态Configuration类,负责整个系统的数据设置。它在构造函数中从注册表加载某些值,并且它的所有方法都基于这些值。如果它无法从注册表中获取值(如果应用程序尚未激活,则可能),它会抛出一个异常,转换为TypeInitializationException,这对我来说很好。

我使用NUnit编写了单元测试,以确保Configuration的构造函数正确处理所有情况 - 正常值,空值,空值。每个测试使用相关值初始化注册表,然后在Configuration中调用一些方法。 这就是问题所在:NUnit已经决定先运行Null测试。它清除注册表,初始化Configuration,抛出异常 - 一切都很好。但是,因为这是一个静态类,其构造函数失败了 - 它不会为其他测试重新构造类,并且它们都会失败。

即使没有Null测试,我也会遇到问题,因为对于所有使用它的类,配置可能(我猜测)会被初始化一次。

我的问题是:我是否应该使用反射为每个测试重新构造类,或者我应该重新设计此类以检查属性中的注册表而不是构造函数?

4 个答案:

答案 0 :(得分:9)

我的建议是重新设计你的Configuration课程。因为你的问题本质上是理论性的,有一点实际意义(单元测试失败),我会提供一些链接来支持我的想法。

首先,使Configuration成为非静态类。谷歌的工程师MiškoHevery对OO Design for Testability发表了非常好的演讲,他特意将全球国家作为一个糟糕的设计选择,特别是如果你想测试它。

您的配置可以通过构造函数接受RegistryProvider实例(我假设您已经了解了依赖注入原则)。 RegistryProvider责任只是从注册表中读取值,这是它应该做的唯一事情。现在当你测试Configuration时,你将提供RegistryProvider stub(如果你不知道什么是存根和模拟 - 谷歌它们,它们本质上是简单的),你将硬编码特定注册表项的值。

现在,好处:

  • 你很好unit tests,因为你不依赖于注册表
  • 您没有全局状态(可测试性)
  • 你的测试不依赖于 彼此(每个都有单独的Configuration实例)
  • 您的测试不依赖于执行它们的环境(您可能没有权限访问注册表,但您仍然可以测试Configuration类)

如果您觉得自己并不擅长依赖注射,我会推荐一种奇妙的艺术和工程,由Mark Seemann的天才提供给凡人,称为Dependency Injection in .NET。我读过的关于类设计的最好的书之一,面向.NET开发人员。

让我的答案更具体:

  

我应该使用反射为每个测试重新构建类吗?

不,你不应该在你的测试中使用反射(只有在没有其他情况下)。 Reflexion会让你测试:

  • 脆弱
  • 难以理解
  • 难以维护

结合使用面向对象的实践来实现隐藏实现。然后只测试外部行为,不依赖于内部实现细节。这将使您的测试仅依赖于外部行为,而不是内部实现,这可能会发生很大变化。

  

我应该重新设计这个类来检查属性中的注册表   而不是构造函数?

按照我的回答中的描述设计你的课程将使你能够测试你的课程根本不访问注册表。这是单元测试的基石 - 不依赖于外部系统(数据库,文件系统,Web,注册表等......)。如果您想测试可以完全访问注册表 - 编写单独的集成测试,您将在其中写入注册表并从中读取。

现在我没有足够的信息告诉您是否应该通过RegistryProvider构造函数中的Configuration读取注册表,或者根据需要懒惰地阅读注册表,这是一个棘手的问题。但我绝对可以说 - 尝试尽可能地避免全局状态,尽量减少对测试中实现细节的依赖(这与整个OO原则有关)并尝试单独测试对象,即无需访问外部系统。然后,您可以模仿特殊情况,例如,当注册表不可用时,您的类是否按预期运行?如果直接通过Configuration类的静态成员访问注册表,则很难重新创建此类场景。

答案 1 :(得分:1)

众所周知,静态类/方法难以进行单元测试 (另请注意,您目前所做的不是单元测试;它是集成测试(您正在更改测试的注册表值)。)

我担心你必须在你的班级可测试和静态之间做出选择。

您可以做出的妥协是将“逻辑”位(即验证等)移动到另一个非静态类,该类将由主静态类调用。
然后可以轻松测试非静态类。

答案 2 :(得分:0)

我会选择重新设计。 如果出现任何问题,也会出现TypeInitializationException,这可能会使用户/开发人员感到困惑。

答案 3 :(得分:-1)

我建议您调整代码以使用单例模式作为第一步。