检查单元测试中的控制台输出

时间:2013-12-15 13:53:56

标签: c# unit-testing testing nunit moq

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

我正在使用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()不返回值,因此我无法将其赋值给变量。该功能只输出到控制台。

我有没有办法在测试中验证输出==“问题文本”?

编辑:忘了提问题是一个抽象的基类。

我尝试使用此代码建议的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接口似乎是处理它的最佳方式,所以我现在就开始吧。

2 个答案:

答案 0 :(得分:2)

您可以使用自定义输出编写器初始化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)

    }

答案 1 :(得分: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。这将完全抽象Question类来自它所使用的UI类型。第二个注意事项是将业务逻辑与表示逻辑分开。通常你没有负责两件事的类 - 持有问题数据(并可能处理它),以及与用户交互。通常有类似控制器的东西,它接收用户输入,刷新UI并询问业务模型的数据。