在某些情况下,单元测试可能非常困难。通常人们会说只测试你的公共API。但在某些情况下,这是不可能的。如果您的公共API依赖于文件或数据库,则无法正确进行单元测试。那你做什么?
因为这是我第一次使用TDD-ing,所以我试图为单元测试找到“我的风格”,因为似乎没有一种方法可以实现。我找到了两个解决这个问题的方法,它根本没有完美无缺。一方面,您可以尝试与您的程序集进行交互并测试内部功能。另一方面,您可以实现接口(仅用于单元测试)并在单元测试中创建假对象。这种方法首先看起来很不错,但是当你尝试使用这些假货传输数据时会变得更加丑陋。
这个问题有什么“好”的解决方案吗?哪一个缺点较少?或者甚至有第三种方法?
答案 0 :(得分:3)
我在TDD中做了几个错误的开始,正在努力解决这个问题。对我来说,当我意识到我的导师在他说:“我们不想测试框架时的意思时,突破就来了。” (在我们的例子中是.Net框架)。
在您的情况下,听起来好像您有一些与文件和数据库接口的业务逻辑。我要做的是尽可能在最薄的层中抽象文件和数据库逻辑。然后,您可以使用Mock(伪造或存根)来模拟文件和数据库层。这将允许您测试if-my-database-returns这样的场景 - 这种信息类型 - 我的业务逻辑 - 处理它是否正确?同样,对于文件访问,您可以测试代码,确定哪个文件在哪个路径中打开,您可以测试您的逻辑是否能够正确分离任何给定文件的内容并能够正确使用它。
例如,如果您的文件访问层包含一个采用路径名和文件名的函数,并以长字符串形式返回文件内容,那么您实际上不需要测试它,因为基本上您正在制作对框架/操作系统的单一调用,并没有很多可能出错的地方。
目前我正在开发一个系统,它将我们的数据库包装成一堆返回POCO列表的函数。易于理解业务层,易于通过模拟进行模拟。
以这种方式工作需要一些时间来适应,但一旦它点击你的想法它绝对是完整的。
最后,根据您的问题,我猜您正在使用遗留代码并尝试为新组件执行TDD。这比在全新开发中使用TDD要困难得多。如果可能的话,尝试在新的(或隔离的)系统上进行第一次TDD尝试。一旦学会了这些机制,将部分TDD位转移到遗留系统就会容易得多。
答案 1 :(得分:2)
If your public API depends on files or databases you can't unit test properly. So what do you do?
有一个可以使用的抽象级别。
由于此级别非常薄,因此集成测试易于编写和维护。所有其他代码都是单元测试友好的,因为它很容易模拟与文件系统和数据库的交互。
On the one hand, you could try to friend your assemblies and test the features that are internal.
当他们的班级违反single responsibility principle (SRP)并且未使用dependency injection (DI)时,他们会遇到此问题。
有一个很好的规则,即只能通过公共方法/属性测试类。如果其他人使用内部方法,那么测试它们是可以接受的。由于测试,不应将私有或受保护的方法设为内部。
On the other hand, you could implement interfaces (only for the purpose of unit testing) and create fake objects within your unit tests.
是的,由于模拟框架的限制,接口很容易被模拟。 如果您可以创建一个类型的实例(假/存根),那么您的依赖项不应该实现接口。
有时人们会为其域实体使用接口,但我不支持它们。
为简化伪造工作,使用了两种模式:
当我开始编写单元测试时,我开始使用'对象母亲'。现在我正在使用'Test Data Builder'。
Michael Feathers在书中Working Effectively with Legacy Code有很多好主意可以帮到你。
答案 2 :(得分:1)
不要让困难的东西妨碍你...如果由于db或文件集成而难以测试,那么暂时忽略它。最有可能你可以重构那么难以测试的东西,更容易测试使用依赖注入等模拟的东西...在那之前,测试简单的东西,并建立一个良好的单元测试套件......当你做重构的时候难以测试的东西,你会有一个更高的置信区间,它不会破坏其他任何东西......重构更容易测试的东西是重构的一个很好的理由......