我正在尝试学习TDD,但很难了解我需要编写的小应用程序测试内容/方法。
该应用程序的(简化的)规范如下:
需要从用户那里获取csv文件的位置,word文档mailmerge模板的位置和输出位置。
然后,应用程序将读取csv文件,对于每一行,将数据与单词模板合并并输出到指定的文件夹。
为了清楚起见,我不会问我将如何编写这样的应用程序,因为我确信如果我继续开始,我知道该怎么做。但是,如果我想使用TDD进行操作,那么我会猜测我不想测试读取真正的csv文件,或者测试执行合并的第三方组件或转换为pdf。
我认为只是一些一般的TDD指导会有很大的帮助!
答案 0 :(得分:34)
我首先考虑程序每个步骤的方案,从失败案例及其预期行为开始:
用户提供null csv文件位置(抛出ArgumentNullException
)。
用户提供一个空的csv文件位置(抛出ArgumentException
)。
用户指定的csv文件不存在(您认为合适的任何内容)。
接下来,为每个场景编写一个测试并确保它失败。接下来,编写足够的代码以使测试通过。对于某些条件来说这很容易,因为使测试通过的代码通常是最终代码:
public class Merger {
public void Merge(string csvPath, string templatePath, string outputPath) {
if (csvPath == null) { throw new ArgumentNullException("csvPath"); }
}
}
之后,进入标准方案:
指定的csv文件有一行(合并应调用一次,输出写入预期位置)。
指定的csv文件有两行(合并应调用两次,输出写入预期位置)。
输出文件的名称符合您的期望(无论是什么)。
等等。一旦你进入第二阶段,你将开始识别你想要存根和模拟的行为。例如,检查文件是否存在 - .NET不容易将其存根,因此您可能需要创建一个适配器接口和类,以便将程序与实际文件系统隔离(至更不用说实际的CSV文件和邮件合并模板了。还有其他技术可用,但这种方法非常标准:
public interface IFileFinder { bool FileExists(string path); }
// Concrete implementation to use in production
public class FileFinder: IFileFinder {
public bool FileExists(string path) { return File.Exists(path); }
}
public class Merger {
IFileFinder finder;
public Merger(IFileFinder finder) { this.finder = finder; }
}
在测试中,您将传入存根实现:
[Test]
[ExpectedException(typeof(FileNotFoundException))]
public void Fails_When_Csv_File_Does_Not_Exist() {
IFileFinder finder = mockery.NewMock<IFileFinder>();
Merger merger = new Merger(finder);
Stub.On(finder).Method("FileExists").Will(Return.Value(false));
merger.Merge("csvPath", "templatePath", "outputPath");
}
答案 1 :(得分:6)
简单的一般指导:
通过编写单元测试,您实际指定了要求,但在另一种形式中,易于阅读代码。
从另一个角度来看:当你收到一个新的黑盒装类和单元测试时,你应该阅读单元测试,看看这个类做了什么以及它的行为。
要了解有关单元测试的更多信息,我推荐一本非常好的书:Art Of Unit Testing
以下是有关TDD的StackOverflow文章的几个链接,以获取更多详细信息和示例:
答案 2 :(得分:2)
为了能够进行单元测试,您需要将类与任何依赖项分离,以便您可以有效地测试类本身。
要执行此操作,您需要将任何依赖项注入到类中。您通常会通过将实现依赖关系接口的对象传入构造函数中的类来完成此操作。
模拟框架用于创建您的类在测试期间可以调用的依赖项的模拟实例。您可以将mock定义为与依赖项相同的行为,然后在测试结束时验证它的状态。
我建议您玩Rhino mocks并浏览文档中的示例,以了解其工作原理。