我有一个静态Configuration
类负责我整个系统的数据设置。它在其构造函数中从注册表加载某些值,并且其所有方法都基于这些值。如果它无法从注册表中获取值(如果应用程序尚未激活则可能),它会抛出异常,转换为 TypeInitializationException
,这对我来说没问题。
我使用 NUnit 编写了单元测试,以确保 Configuration 的构造函数正确处理所有情况 - 正常值、空白值、Null 值。每个测试使用相关值初始化注册表,然后调用 Configuration 中的一些方法。 这就是问题所在:NUnit 决定首先运行 Null 测试。它清除注册表、初始化配置、抛出异常——一切都很好。但是,因为这是一个静态类,其构造函数刚刚失败 - 它不会为其他测试再次重新构造该类,并且它们都失败了。
即使没有 Null 测试,我也会遇到问题,因为配置可能(我猜)会为所有使用它的类初始化一次。
我的问题是:我应该使用反射为每个测试重新构造类,还是应该重新设计此类以检查属性而不是构造函数中的注册表?
最佳答案
我的建议是稍微重新设计您的 Configuration
类。因为你的问题本质上是理论性的,有一点实践方面(单元测试失败),我会提供一些链接来支持我的想法。
首先,使Configuration
成为一个非静态类。谷歌工程师 Miško Hevery 关于 OO Design for Testability 的演讲非常精彩他特别提到全局状态是一个糟糕的设计选择,特别是如果你想测试它的话。
您的配置可以通过构造函数接受 RegistryProvider
实例(我假设您听说过依赖注入(inject)原则)。 RegistryProvider
的责任只是从注册表中读取值,这是它唯一应该做的事情。现在,当您测试 Configuration
时,您将提供 RegistryProvider stub
(如果您不知道什么是 stub 和模拟 - google 一下,它们本质上很简单),您可以在其中将硬编码特定注册表项的值。
现在,好处:
- 你有很好的
单元测试
,因为你不依赖注册表 - 你没有全局状态(可测试性)
- 你的测试不依赖于
彼此(每个都有单独的
Configuration
实例) - 您的测试不依赖于执行它们的环境(您可能没有访问注册表的权限,但您仍然可以测试您的
Configuration
类)
如果您觉得自己不太擅长依赖注入(inject),我会推荐一个奇妙的艺术和工程作品,由 Mark Seemann 的天才提供给凡人的灵魂,称为 Dependency Injection in .NET .我读过的关于类设计的最好的书之一,它面向 .NET 开发人员。
为了让我的回答更具体:
Should I use reflection to re-construct the class for each test?
不,你永远不应该在你的测试中使用反射(只有在没有其他情况的情况下)。反射会让你测试:
- 脆弱的
- 难以理解
- 难以维护
使用面向对象的实践结合封装来实现实现的隐藏。然后只测试外部行为,不要依赖内部实现细节。这将使您的测试仅依赖于外部行为而不依赖于内部实现,后者可能会发生很大变化。
should I re-design this class to check the registry in a property rather than the constructor?
按照我的回答中的描述设计你的类(class)将使你能够测试你的类(class)而不访问注册表。这是单元测试的基石——不依赖外部系统(数据库、文件系统、网络、注册表 等...)。如果您想测试您是否可以访问注册表 - 编写单独的集成测试,您将在其中写入注册表并从中读取。
现在我没有足够的信息来告诉你是应该通过 Configuration
构造函数中的 RegistryProvider
读取注册表,还是按需延迟读取注册表,这是一个棘手的问题.但我绝对可以说 - 尽量避免全局状态,尽量减少对测试中实现细节的依赖(这与整个 OO 原则相关)并尝试隔离测试对象,即不访问外部系统。然后您可以模拟异常情况,例如,当注册表不可用时,您的类是否按预期行为?如果您直接通过 Configuration
类的静态成员访问注册表,则很难重现这种情况。
关于c# - 设计问题 : static class only initializes once, 破坏了单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15730509/