208

对不返回任何内容的方法进行单元测试的最佳方法是什么?特别是在 c# 中。

我真正想要测试的是一种获取日志文件并将其解析为特定字符串的方法。然后将字符串插入数据库。以前没有做过任何事情,但是对于 TDD 来说是非常新的,我想知道是否有可能对此进行测试,或者它是否真的没有经过测试。

4

11 回答 11

169

如果一个方法没有返回任何东西,它是以下之一

  • 命令式-您要么要求对象对自己做某事..例如更改状态(不期望任何确认..假设它会完成)
  • 信息性 - 只是分别通知某人发生了某事(不期望采取行动或回应)。

命令式方法 - 您可以验证任务是否实际执行。验证状态更改是否实际发生。例如

void DeductFromBalance( dAmount ) 

可以通过验证发送此消息的余额是否确实小于初始值 dAmount 来进行测试

信息方法 - 作为对象公共接口的成员很少见......因此通常不经过单元测试。但是,如果必须,您可以验证是否要对通知进行处理。例如

void OnAccountDebit( dAmount )  // emails account holder with info

可以通过验证电子邮件是否正在发送来进行测试

发布有关您的实际方法的更多详细信息,人们将能够更好地回答。
更新:您的方法正在做两件事。我实际上将它分成两种现在可以独立测试的方法。

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

通过为第一个方法提供一个虚拟文件和预期的字符串,可以轻松地验证 String[]。第二个有点棘手..您可以使用 Mock(谷歌或在模拟框架上搜索 stackoverflow)来模仿数据库或点击实际的数据库并验证字符串是否插入到正确的位置。检查此线程以获取一些好书...如果您处于紧缩状态,我会推荐实用单元测试。
在代码中,它将像

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
于 2008-10-29T07:39:52.050 回答
67

测试它的副作用。这包括:

  • 它会抛出任何异常吗?(如果应该,请检查它是否存在。如果不应该,请尝试一些极端情况,如果你不小心可能会 - 空参数是最明显的事情。)
  • 它的参数可以很好地发挥作用吗?(如果它们是可变的,它会在不应该改变它们的时候改变它们吗?反之亦然?)
  • 它对你调用它的对象/类型的状态有正确的影响吗?

当然,您可以测试的数量是有限度的。例如,您通常无法测试所有可能的输入。务实地测试 - 足以让您确信您的代码设计得当并正确实施,并且足以充当调用者可能期望的补充文档。

于 2008-10-29T07:39:58.977 回答
32

与往常一样:测试该方法应该做什么!

它应该在某处改变全局状态(呃,代码味道!)吗?

它应该调用一个接口吗?

使用错误的参数调用时是否应该抛出异常?

当使用正确的参数调用时它是否应该不抛出异常?

应该是 ...?

于 2008-10-29T07:34:25.413 回答
21

试试这个:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
于 2016-05-12T07:47:27.413 回答
13

无效返回类型/子例程是旧消息。我已经有 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)

现在您可以接受业务/数据层对象的接口,您可以在单元测试期间对其进行模拟,并且无需依赖或担心“意外”集成测试。

因此,在您的实时代码中,您传入一个 REALIBusinessDataEtc对象。但是在您的单元测试中,您传入一个 MOCKIBusinessDataEtc对象。在该 Mock 中,您可以包含非接口属性,例如int XMethodWasCalledCount在调用接口方法时更新其状态的东西。

因此,您的单元测试将通过您的 Method(s)-In-Question,执行它们具有的任何逻辑,并调用一个或两个,或在您的IBusinessDataEtc对象中选择一组方法。当您在单元测试结束时执行断言时,您现在有几件事要测试。

  1. “子程序”的状态,现在是 Try-Paradigm 方法。
  2. 你的 MockIBusinessDataEtc对象的状态。

有关构建级别的依赖注入思想的更多信息......因为它们与单元测试有关......请查看构建器设计模式。它为您拥有的每个当前接口/类添加了一个更多接口和类,但它们非常小并且为更好的单元测试提供了巨大的功能增加。

于 2014-05-30T16:47:38.103 回答
12

你甚至可以这样尝试:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
于 2019-05-29T10:16:40.013 回答
6

它会对对象产生一些影响....查询效果的结果。如果它没有明显的效果,则不值得进行单元测试!

于 2008-10-29T07:30:33.160 回答
4

大概该方法做了一些事情,而不是简单地返回?

假设是这种情况,那么:

  1. 如果它修改了它的所有者对象的状态,那么您应该测试状态是否正确更改。
  2. 如果它接受某个对象作为参数并修改该对象,那么您应该测试该对象是否被正确修改。
  3. 如果在某些情况下抛出异常,请测试是否正确抛出了这些异常。
  4. 如果它的行为根据自己的对象或其他对象的状态而变化,则预设状态并通过上述三种测试方法之一测试该方法是否正确)。

如果你让我们知道该方法的作用,我可能会更具体。

于 2008-10-29T07:39:21.847 回答
3

使用Rhino Mocks设置可能预期的调用、操作和异常。假设您可以模拟或存根您的方法的某些部分。如果不知道这里有关该方法甚至上下文的一些细节,很难知道。

于 2008-10-29T08:03:53.827 回答
1

取决于它在做什么。如果它有参数,则传入你可以稍后询问它们是否已使用正确的参数集调用的模拟。

于 2008-10-29T07:33:02.140 回答
0

无论您使用什么实例来调用 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由于测试失败的方法失败而引发异常?

于 2017-02-27T15:52:35.363 回答