我经常看到将固定输入输入程序的测试,通常通过diff检查针对固定(预期)输出生成的输出。如果差异被接受,则认为代码通过了测试。
问题:
1)这是一个可接受的单元测试吗?
2)通常,单元测试输入从文件系统读入并且很大 xml文件(也许它们代表一个非常大的系统)。是单元测试 触摸文件系统?或者单元测试会动态创建一个小输入 并将其提供给要测试的代码?
3)如何重构现有代码以进行单元测试?
答案 0 :(得分:2)
如果您的要求是产生具有某种精确度的输出,那么这样的测试绝对没问题。做出最终决定的是你 - "这个输出是否足够好?" 。
您不希望您的测试与文件系统进行通信,而依赖某些文件存在于,以便您的测试能够正常工作(例如,从配置文件中读取值) )。它与测试输入资源有点不同 - 您通常可以将它们嵌入到测试中(或者至少是测试项目),将它们视为代码库的一部分,最重要的是它们通常应该在测试执行之前加载。例如,在测试相当大的XML时,将它们存储为separete文件是合理的,而不是代码文件中的字符串(有时可以代替它)。
重点是 - 您希望保持测试隔离且可重复。如果你可以在运行时加载文件来实现它 - 它可能很好。但是,将它们作为代码库/资源的一部分比将标准系统文件放在某处更好。
这个问题相当广泛,但为了让您朝着正确的方向前进 - 您想要引入更多solid设计,decouple对象和单独的职责。更好的设计将使测试更容易,而且最重要的是 - 可能。就像我说的那样,它是一个广泛而复杂的主题,entire books致力于它。
答案 1 :(得分:1)
1)这是一个可接受的单元测试吗?
根据定义,这不是单元测试。单元测试侧重于尽可能少的代码。您的测试仍然可以是一个有用的测试,回归测试,自我记录测试,TDD测试等。它不是一个单元测试,虽然它可能同样有用。
2)单元测试是否应该触及文件系统?
通常不会,除非您需要对与文件系统明确相关的内容进行单元测试。一个原因是,如果你有几百个单元测试,那么让它们在几秒而不是几分钟内运行是很好的。
3)如何重构现有代码以进行单元测试?
更好的问题是为什么您希望代码可以进行单元测试?如果您正在尝试学习TDD,最好从一个新项目开始。如果你有bug,那么试着为bug编写测试。如果设计减慢了你的速度,那么你可以随着时间的推移重构可测试性。
答案 2 :(得分:0)
仅解决第3个问题。这非常困难。您真的需要在编写代码的同时或之前编写测试。尝试将测试打到现有代码库上是一场噩梦,抛弃代码并重新开始通常会更有效率。
答案 3 :(得分:0)
这是一个可接受的单元测试。
正在读取的文件应该是测试项目的一部分,因此从存储库中检出项目的任何人都将在相同的相对位置拥有相同的文件。
进行黑盒测试是一个很好的开始,您可以重构现有代码并使用当前测试来验证它是否仍然有效(取决于测试的质量)。这是一篇关于重构可测试性的简短博客:http://www.beletsky.net/2011/02/refactoring-to-testability.html
答案 4 :(得分:0)
差异测试可以作为单元测试,特别是当您使用单元测试之间共享的测试数据时。
如果你不知道SUT中有多少项,你可以使用以下内容:
int itemsBeforeTest = SUT.Items.Count;
SUT.AddItem();
Assert.AreEqual(itemsBeforeTest + 1, SUT.Items.Count);
如果单元测试需要大量数据,需要从大型XML文件中读取,那么它不是真正的单元测试。单元测试应该完全隔离测试一个类并模拟所有依赖项。
使用类似Builder pattern的模式,也可以帮助您为单元测试创建测试数据。将测试数据放在单独的文件中的最大问题是,很难理解测试的确切作用。如果您在单元测试的安排部分创建测试数据,则会立即清楚对您的测试重要的是什么。
例如,假设您有以下安排代码来测试发票价格是否正确:
Address billingAddress = new Address("Stationsweg 9F",
"Groningen", "Nederland", "9726AE"); shippingAddress = new Address("Aweg 1",
"Groningen", "Nederland", "9726AB");
Customer customer = new Customer(99, "Piet", "Klaassens",
30,
billingAddress,
shippingAddress);
Product product = new Product(88, "Tafel", 19.99);
Invoice invoice = new Invoice(customer);
使用Builder时可以更改为以下内容
Invoice invoice = Builder<Invoice>.CreateNew()
.With(i => i.Product = Builder<Product>.CreateNew()
.With(p => p.Price = 19.99)
.Build())
.Build();
使用Builder时,更容易看到重要内容,并且您的代码也更易于维护。
重构代码以使其更易于测试是一个广泛的主题。它归结为“我将如何测试此代码?”当你在编写代码时。
采用以下示例:
public class AlarmClock
{
public AlarmClock()
{
SatelliteSyncService = new SatelliteSyncService();
HardwareClient = new HardwareClient();
}
}
这很难测试。在测试AlarmClock时,您需要确保SatteliteSyncService和HardwareClient都正常工作。
构造函数的这一改变使测试更加容易:
public AlarmClock(IHardwareClient hardwareClient, ISatelliteSyncService satelliteSyncService)
{
SatelliteSyncService = satelliteSyncService;
HardwareClient = hardwareClient;
}
Dependency Injection之类的技巧有助于重构代码以使其更易于测试。还要注意像DateTime.Now这样的静态值或使用Singleton,因为它们很难测试。
可以找到编写可测试代码的非常好的介绍here。
答案 5 :(得分:0)
您不应要求重构代码以便能够执行单元测试。顾名思义,单元测试正在测试系统的代码单元。最好的单元测试很小,执行起来很快,并且只运行被测试代码的一小部分(例如,类)。
进行小型,紧凑的单元测试只运行代码的一部分的原因是单元测试的目的是找到该代码单元中的错误。如果单元测试需要很长时间来执行并测试很多东西,那么就会更难以识别代码中的错误。
至于访问文件系统,我认为没问题。某些单元测试可能需要在执行测试之前构建数据库,要检查的输出在代码中编写检查会很困难或者时间昂贵。
单元测试的文件应该像其他代码一样处理 - 置于版本控制之下。如果您是偏执狂,您可以在单元测试中执行检查,例如在其上执行MD5并检查硬编码值,以便将来重新运行测试可以验证测试数据是否未发生意外更改。
只是我谦虚的想法。