【发布时间】:2010-09-19 18:00:02
【问题描述】:
对不返回任何内容的方法进行单元测试的最佳方法是什么?特别是在 c# 中。
我真正想要测试的是一种获取日志文件并将其解析为特定字符串的方法。然后将字符串插入数据库。以前没有做过任何事情,但是对于 TDD 来说是非常新的,我想知道是否有可能对此进行测试,或者它是否真的没有经过测试。
【问题讨论】:
标签: c# unit-testing void
对不返回任何内容的方法进行单元测试的最佳方法是什么?特别是在 c# 中。
我真正想要测试的是一种获取日志文件并将其解析为特定字符串的方法。然后将字符串插入数据库。以前没有做过任何事情,但是对于 TDD 来说是非常新的,我想知道是否有可能对此进行测试,或者它是否真的没有经过测试。
【问题讨论】:
标签: c# unit-testing void
它会对一个对象产生一些影响....查询效果的结果。如果它没有明显的效果,则不值得进行单元测试!
【讨论】:
取决于它在做什么。如果它有参数,则传入你可以稍后询问它们是否使用正确的参数集调用的模拟。
【讨论】:
一如既往:测试方法应该做什么!
是否应该在某处更改全局状态(呃,代码异味!)?
它应该调用一个接口吗?
用错误的参数调用时是否应该抛出异常?
使用正确的参数调用时是否应该不抛出异常?
应该...?
【讨论】:
大概该方法做了一些事情,而不是简单地返回?
假设是这样,那么:
如果你让我们知道该方法的作用,我可以更具体。
【讨论】:
如果一个方法没有返回任何东西,那么它就是以下之一
命令式方法 - 您可以验证任务是否实际执行。验证状态更改是否实际发生。例如
void DeductFromBalance( dAmount )
可以通过验证发送此消息的余额是否确实小于初始值 dAmount 来测试
信息性方法 - 作为对象公共接口的成员很少见...因此通常不经过单元测试。但是,如果必须,您可以验证是否要对通知进行处理。例如
void OnAccountDebit( dAmount ) // emails account holder with info
可以通过验证是否正在发送电子邮件来测试
发布有关您的实际方法的更多详细信息,人们将能够更好地回答。
更新:您的方法正在做两件事。我实际上将它分成两种现在可以独立测试的方法。
string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );
String[] 可以通过为第一个方法提供一个虚拟文件和预期的字符串来轻松验证。第二个有点棘手..您可以使用 Mock(谷歌或在模拟框架上搜索 stackoverflow)来模拟数据库或点击实际的数据库并验证字符串是否插入到正确的位置。查看this thread 以获得一些好书...如果您处于紧要关头,我会推荐实用单元测试。
在代码中,它将像
InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
【讨论】:
测试它的副作用。这包括:
当然,您可以测试多少是有限度的。例如,您通常无法测试所有可能的输入。务实地进行测试 - 足以让您确信您的代码设计得当并正确实施,并且足以充当调用者可能期望的补充文档。
【讨论】:
使用Rhino Mocks 设置可能预期的调用、操作和异常。假设您可以模拟或存根您的方法的某些部分。如果不知道这里有关该方法甚至上下文的一些细节,很难知道。
【讨论】:
Void 返回类型/子例程是旧消息。我已经有 8 年没有做过 Void 返回类型(除非我非常懒惰)(从这个答案开始,所以就在这个问题被问到之前)。
而不是像这样的方法:
public void SendEmailToCustomer()
创建一个遵循 Microsoft 的 int.TryParse() 范例的方法:
public bool TrySendEmailToCustomer()
也许从长远来看,您的方法不需要返回任何信息以供使用,但在方法执行其工作后返回该方法的状态对调用者来说是一个巨大的用途。
另外,bool 不是唯一的状态类型。很多时候,先前制作的子程序实际上可以返回三个或更多不同的状态(好、正常、坏等)。在这些情况下,您只需使用
public StateEnum TrySendEmailToCustomer()
但是,尽管 Try-Paradigm 在某种程度上回答了这个关于如何测试 void return 的问题,但也有其他考虑因素。例如,在“TDD”周期期间/之后,您将“重构”并注意到您正在用您的方法做两件事……从而打破了“单一责任原则”。所以应该先处理好。其次,您可能已经确定了一个依赖项......您正在触摸“持久”数据。
如果您在有问题的方法中进行数据访问,则需要重构为 n 层或 n 层架构。但是我们可以假设,当您说“然后将字符串插入数据库”时,您实际上的意思是您正在调用业务逻辑层或其他东西。是的,我们会假设。
当您的对象被实例化时,您现在了解您的对象具有依赖关系。这是您需要决定是否要对对象或方法进行依赖注入的时候。这意味着您的构造函数或有问题的方法需要一个新参数:
public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)
现在您可以接受业务/数据层对象的接口,您可以在单元测试期间对其进行模拟,并且无需依赖或担心“意外”集成测试。
因此,在您的实时代码中,您传入一个 REAL IBusinessDataEtc 对象。但是在您的单元测试中,您传入一个 MOCK IBusinessDataEtc 对象。在该 Mock 中,您可以包含非接口属性,例如 int XMethodWasCalledCount 或在调用接口方法时更新其状态的东西。
因此,您的单元测试将通过您的 Method(s)-In-Question,执行它们具有的任何逻辑,并调用 IBusinessDataEtc 对象中的一个或两个或一组选定的方法。当您在单元测试结束时执行断言时,您现在有几件事要测试。
IBusinessDataEtc 对象的状态。有关构建级别的依赖注入思想的更多信息......因为它们与单元测试有关......请查看构建器设计模式。它为您当前拥有的每个接口/类增加了一个接口和类,但它们非常小,并为更好的单元测试提供了巨大的功能增加。
【讨论】:
public void sendEmailToCustomer() throws UndeliveredMailException这样的东西吗?
void 方法中,尤其是在面向对象的语言中,它是唯一真正的选择。微软最古老的替代方案是我讨论的 Try-Paradigm,以及像 Monads / Maybes 这样的函数式范例。因此,命令(在 CQS 中)仍然可以返回有价值的状态信息,而不是依赖于 throw,这类似于 GOTO(我们知道这是不好的)。 throwing(和 goto)速度很慢,难以调试,而且不是好的做法。
试试这个:
[TestMethod]
public void TestSomething()
{
try
{
YourMethodCall();
Assert.IsTrue(true);
}
catch {
Assert.IsTrue(false);
}
}
【讨论】:
ExpectedAttribute 旨在让这个测试更清晰。
你使用什么实例来调用 void 方法,你可以使用,Verfiy
例如:
在我的例子中,_Log 是实例,LogMessage 是要测试的方法:
try
{
this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch
{
Assert.IsFalse(ex is Moq.MockException);
}
Verify 是否由于测试失败的方法失败而引发异常?
【讨论】:
你甚至可以这样尝试:
[TestMethod]
public void ReadFiles()
{
try
{
Read();
return; // indicates success
}
catch (Exception ex)
{
Assert.Fail(ex.Message);
}
}
【讨论】: