【问题标题】:Check Console Output In Unit Test [duplicate]检查单元测试中的控制台输出[重复]
【发布时间】:2014-01-02 22:06:47
【问题描述】:

有没有办法在我的抽象类问题的单元测试中检查控制台的输出?

我正在使用 NUnit 和起订量。

我的单元测试如下所示:

    [Test]
    public void QuestionAsk()
    {
        var mock = new Mock<Question>(new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        Question.Ask();

        mock.Verify(m => m.Ask(), Times.Exactly(1));

    }

在这里,我正在检查是否调用了 Question.Ask() 并且它工作正常。 Ask() 不返回值,因此我无法将其分配给变量。该函数只是输出到控制台。

有没有办法可以在测试中验证输出 == "question text" ?

编辑:忘了提 Question 是一个抽象基类。

我尝试了使用此代码建议的 Concole.Setout 方法:

    [Test]
    public void QuestionAsk()
    {
        var mock = new Mock<Question>(new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        using (var consoleText = new StringWriter())
        {
            Console.SetOut(consoleText);
            Question.Ask();
            Assert.That(consoleText.ToString(), Is.StringMatching("question text"));
        }
        mock.Verify(m => m.Ask(), Times.Exactly(1));

    }

但它花了 236 毫秒,这对于测试来说太长了。实现 IWriter 接口似乎是处理它的最佳方式,所以我现在就尝试一下。

【问题讨论】:

  • 除了@Keith 的链接,您实际上并不想从您的问题“业务对象”中调用控制台。执行输出是运行程序的任务,因为您现在不能将此类重用于 Web 应用程序。如果只是让它返回字符串,调用程序可以决定如何输出它。
  • @CodeCaster 好点。我最终将把它移植到网络上,所以我应该尽快解决这个问题。我正在做这个项目来学习 C#。我非常感谢您的建议。

标签: c# unit-testing testing nunit moq


【解决方案1】:

您可以使用自定义输出编写器初始化Question,然后模拟编写器以验证输出:

public interface IOutputWriter 
{
    void WriteLine(string s);
}

// Use this console writer for your live code
public class ConsoleOutputWriter : IOutputWriter
{
    public void WriteLine(string s)
    {
        Console.WriteLine(s);
    }
}

public abstract class Question
{
    private readonly IOutputWriter _writer;
    private readonly string _text;
    private readonly bool _default;

    public Question(IOutputWriter writer, params object[] args)
    {
        _writer = writer;
        _text = (string)args[0];
        _default = (bool)args[1];
    }

    public void Ask()
    {
        _writer.WriteLine(_text);
    }
}


[Test]
public void QuestionAsk()
    {
        var writer = new Mock<IOutputWriter>();

        var mock = new Mock<Question>(writer.Object, new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        Question.Ask();

        mock.Verify(m => m.Ask(), Times.Exactly(1));
        mock.Verify(w => w.WriteLine(It.Is<string>(s => s == "question text")), Times.Once)

    }

【讨论】:

  • 谢谢,这似乎是最好的方法。我最初喜欢 Console.SetOut 方法,因为它只需要一行代码来完成我现有的测试,但它使我的测试从 6 毫秒缩短到 236 毫秒。当我计划进行大量测试时,这会削弱我的能力。 SetOut 的这种性能下降是正常的还是我做错了什么?可以粘贴我的代码作为答案,还是我必须为此提出一个新问题?对不起,我还是新手
  • 您可以发布自己问题的答案。
  • 我怀疑最后一行 mock.Verify(w =&gt; w.WriteLine(It.Is&lt;string&gt;(s =&gt; s == "question text")), Times.Once) 应该是 writer.Verify(w =&gt; w.WriteLine(It.Is&lt;string&gt;(s =&gt; s == "question text")), Times.Once)。另一个问题,Question 类中的Ask 应该是virtual
【解决方案2】:

您的测试看起来很奇怪 - 您正在执行模拟对象而不是测试将由您的应用程序使用的一些真实对象。如果您正在测试Question 对象,那么您应该使用与您的应用程序使用的完全相同的Question 类型实例。应该嘲笑什么 - Question 的依赖关系。因为类应该单独进行单元测试(否则依赖关系问题会导致 Question 测试失败,这可以正常工作)。

因此,如果您有 Question 在控制台上显示某些内容(即它依赖于 Console),那么单元测试需要模拟这种依赖关系。您不能使用 Moq 模拟 Console,因为它是静态类。因此,您应该为控制台创建抽象,Question 将使用它:

public interface IConsole
{
    void Write(string message);
}

现在将这个依赖注入你的Question

public class Question
{
    private IConsole _console;
    private string _message;

    public class Question(IConsole console, string message)
    {
        _console = console;
    }
}

使用此代码,您可以为Question 行为编写测试:

[Test]
public void ShouldAskQuestionOnConsole()
{
    var message = "Hello World";
    var consoleMock = new Mock<IConsole>();
    consoleMock.Setup(c => c.Write(message));
    var question = new Question(consoleMock.Object, message);

    question.Ask();     

    consoleMock.VerifyAll();
}

此测试指定,当执行Ask 时,该问题应将其消息发送到控制台。实现很简单:

public void Ask()
{
    _console.Write(_message);
}

现在你有工作Question。您应该实现IConsole,您的应用程序将使用它。这是简单的包装器:

public class ConsoleWrapper : IConsole
{
     public void Write(string message)
     {
          Console.WriteLine(message);
     }
}

在你的真实应用程序中注入这个实现来提问(这可以通过依赖注入框架自动完成):

IConsole console = new ConsoleWrapper();
Question question = new Question(console, message);

注意:我会使用IView 之类的界面,而不是IConsole。这将从它所使用的UI 类型中完全抽象出Question 类。第二个注意事项是将业务逻辑与表示逻辑分开。通常你没有负责两件事的类 - 保存问题数据(并可能处理它),以及与用户交互。通常有控制器之类的东西,它接收用户输入、刷新 UI 并向业务模型询问数据。

【讨论】:

  • 对不起,我应该说清楚,问题是一个抽象类。
猜你喜欢
  • 1970-01-01
  • 2015-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多