C# 在单元测试中检查控制台输出

C# 在单元测试中检查控制台输出,c#,unit-testing,testing,nunit,moq,C#,Unit Testing,Testing,Nunit,Moq,在抽象类问题的单元测试中,有没有办法检查控制台的输出 我正在使用NUnit&Moq 我的单元测试如下所示: [Test] public void QuestionAsk() { var mock = new Mock<Question>(new object[]{"question text",true}); mock.CallBase = true; var Question = mock.Object;

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

我正在使用NUnit&Moq

我的单元测试如下所示:

    [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));

    }
[测试]
公众提问
{
var mock=new mock(新对象[]{“问题文本”,true});
mock.CallBase=true;
var-Question=mock.Object;
问;
mock.Verify(m=>m.Ask(),Times.justice(1));
}
在这里我检查这个问题。Ask()被调用,它工作正常。Ask()不返回值,因此无法将其分配给变量。该功能仅输出到控制台

是否有一种方法可以在测试中验证输出==“问题文本”

编辑:忘记提及问题是一个抽象基类

我尝试了Concole。此代码建议的放样方法:

    [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));

    }
[测试]
公众提问
{
var mock=new mock(新对象[]{“问题文本”,true});
mock.CallBase=true;
var-Question=mock.Object;
使用(var consoleText=new StringWriter())
{
控制台。放样(控制台文本);
问;
Assert.That(consoleText.ToString(),Is.StringMatching(“问题文本”));
}
mock.Verify(m=>m.Ask(),Times.justice(1));
}

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

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

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)

    }
公共接口IOutputWriter
{
无效写线(字符串s);
}
//将此控制台编写器用于实时代码
公共类ConsoleOutputWriter:IOOutputWriter
{
公共无效写线(字符串s)
{
控制台。写入线(s);
}
}
公共抽象类问题
{
私有只读IOutputWriter\u writer;
私有只读字符串\u文本;
私有只读bool\u默认值;
公共问题(IOutputWriter编写器,参数对象[]args)
{
_作家=作家;
_text=(字符串)参数[0];
_默认值=(bool)参数[1];
}
公开询问
{
_writer.WriteLine(_text);
}
}
[测试]
公众提问
{
var writer=newmock();
var mock=new mock(writer.Object,new Object[]{“问题文本”,true});
mock.CallBase=true;
var-Question=mock.Object;
问;
mock.Verify(m=>m.Ask(),Times.justice(1));
mock.Verify(w=>w.WriteLine(It.Is(s=>s==“问题文本”)),Times.one)
}

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

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

public interface IConsole
{
    void Write(string message);
}
public class Question
{
    private IConsole _console;
    private string _message;

    public class Question(IConsole console, string message)
    {
        _console = console;
    }
}
现在将此依赖项注入到您的
问题中

public interface IConsole
{
    void Write(string message);
}
public class Question
{
    private IConsole _console;
    private string _message;

    public class Question(IConsole console, string message)
    {
        _console = console;
    }
}
使用此代码,您可以为
问题
行为编写测试:

[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();
}
现在您有了工作
问题
。您应该实现应用程序将使用的
IConsole
。它很简单:

public class ConsoleWrapper : IConsole
{
     public void Write(string message)
     {
          Console.WriteLine(message);
     }
}
在实际应用程序中,将此实现注入到问题中(这将由依赖项注入框架自动完成):


注意:我会使用一些界面,比如
IView
,而不是
IConsole
。这将从它使用的
UI
类型中完全抽象出
Question
类。第二个注意事项是业务逻辑和表示逻辑的分离。通常情况下,您并没有负责两件事的类——保存问题数据(并可能对其进行处理)和与用户交互。通常有类似控制器的东西,它接收用户输入,刷新UI并向业务模型询问数据。

请参见:。除了@Keith的链接,实际上您不想从问题“业务对象”调用控制台。执行输出是正在运行的程序的任务,因为您现在不能将该类重新用于(比如)web应用程序。如果您只是让它返回字符串,调用程序可以决定如何输出它。@CodeCaster这是一个很好的观点。我最终将把它移植到web上,所以我应该尽早处理它。我做这个项目是为了学习C。我真的很感激你的建议。谢谢。这似乎是最好的方法。我最初喜欢Console.SetOut方法,因为它只需要一行代码就可以进行现有的测试,但它使我的测试从6ms变为236ms。这将使我瘫痪,因为我计划进行大量的测试。这种性能下降是正常的,还是我做错了什么?粘贴我的代码作为答案可以吗?还是我必须为此提出一个新问题?对不起,我还是新来的。你可以发布你自己问题的答案。我怀疑最后一行
mock.Verify(w=>w.WriteLine(It.Is(s=>s==“问题文本”)),Times.Once
应该是
writer.Verify(w=>w.WriteLine(It.Is(s=>s==“问题文本”)),Times.Once
。另一个问题,
Question
类中的
Ask
应该是
virtual