C# - 单元测试/模拟 - 遗留代码
我在十多年的旧代码中有以下逻辑,我必须为其编写单元测试。它是一个具体的类,以下逻辑位于 ctor 中。有没有一种好方法为此类遗留代码编写单元测试/模拟?我正在使用 MSTest / RhinoMocks 框架和带有 .Net Framework 4.0 的 VS 2010 IDE
public class SomeClass
{
/// ctor
public SomeClass(XmlNode node)
{
//Step 1: Initialise some private variable based on attributes values from the node
//Step 2: Lot of If , else -if statements ---> something like -
if (/*attributeValue is something*/)
{
// Connect to Db, fetch some value based on the attribute value.
// Again the logic of connecting and fetching is in another concrete class
}
else if (/*attributeValue is somthing else*/)
{
// fetch a value by loading a config file (this loading and reading of config file
// is again a singleton class where config file path is hardcoded)
}
else
{
// set some private member variable
}
}
}
I have the following logic in a more than decade old code for which I have to write Unit Tests. It is a concrete class and the following logic lies in the ctor. Is there a good way to write Unit Tests/Mocks for such legacy code. I am using MSTest / RhinoMocks framework and VS 2010 IDE with .Net framework 4.0
public class SomeClass
{
/// ctor
public SomeClass(XmlNode node)
{
//Step 1: Initialise some private variable based on attributes values from the node
//Step 2: Lot of If , else -if statements ---> something like -
if (/*attributeValue is something*/)
{
// Connect to Db, fetch some value based on the attribute value.
// Again the logic of connecting and fetching is in another concrete class
}
else if (/*attributeValue is somthing else*/)
{
// fetch a value by loading a config file (this loading and reading of config file
// is again a singleton class where config file path is hardcoded)
}
else
{
// set some private member variable
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
对遗留代码进行单元测试很棘手。一般来说,您必须首先重构才能编写单元测试。最好的选择是非常小的重构步骤,一一提高可测试性,同时让被测试的类处于“工作”状态。我建议:
1.) 引入“感知”变量,使您能够在关键位置(即数据库调用之前和之后)验证被测类的内部状态。这将允许您编写测试来验证类的当前行为(基于公共感知变量),而无需进行大量重构。根据这些测试验证类的行为并开始重构。感知变量是临时的,一旦完成重构就应该删除。它们只是为了能够同时编写测试,以便您可以安全地进行重构。
2.) 将具体类的引用一一替换为通过构造函数传递的接口引用。对于单例,您有两种选择,一种是让单例返回一个用于单元测试的特殊实例 - 这需要修改单例实现,但保持测试下的类不变。您可以这样做,直到您可以重构以使用接口依赖项来替换单例。
另外,我建议您获取一份有效使用旧代码的副本,其中描述了这一点逐步重构,尤其是详细的依赖破坏技术。
Unit testing legacy code is tricky. In general you will have to refactor first to be able to write unit tests. Your best bet are very small refactoring steps, that one by one improve testability while leaving the class under test in a "working" condition. I would recommend:
1.) Introduce "sensing" variables that allow you to verify the internal state of the class under test at key positions (i.e. before and after the DB call). This will allow you to write tests that verify the current behavior of the class (based on the public sensing variables) without having to refactor very much. Verify the behavior of the class based on these tests and start to refactor. The sensing variables are temporary and should be removed once you have finished your refactorings. They are only there to be able to write tests in the mean time so you somewhat safely refactor.
2.) One by one replace references to concrete classes to interface references that you pass via the constructor. For the singleton you have two options, one is to have the Singleton return a special instance for unit testing - this requires modifying the Singleton implementation but leaves your class under test unchanged. You can do this until you can refactor to use an interface dependency to replace the Singleton.
Also I would recommend picking up a copy of Working Effectively with Legacy Code which describes this step by step refactoring and especially dependency-breaking techniques in detail.
除了 BrokenGlass 所说的之外,您可能还需要考虑编写一些集成测试,以确保整个流程正常工作。例如,如果您的应用程序更新了数据库中的某些行,请编写针对测试数据库的可重复测试,以便您可以在重构并将整个事物分解为松散耦合的可测试块时继续确保正确的功能。
没有什么比重构一个类,为它编写一堆测试,然后意识到你的重构破坏了应用程序中的其他东西更糟糕的了。
In addition to what BrokenGlass said, you may want to consider writing a few integration tests to ensure that the overall process works properly. For example, if your application updates some rows in a database, write repeatable tests that go against a test database so that you can continue to ensure proper functionality while you refactor and break the entire thing down into loosely-coupled, testable chunks.
There's nothing worse than refactoring a class, writing a bunch of tests for it, and then realizing that your refactoring broke something else in the application.