从单元测试中获取发送到Console.Out的输出?

60

我正在使用NUnit构建C#单元测试,并且想要测试主程序根据命令行参数实际输出正确的输出。

在调用Program.Main(...)的NUnit测试方法中,是否有一种方式可以抓取写入到Console.OutConsole.Error的所有内容,以便我可以进行验证?


1
这是一项集成测试而不是单元测试。 - Oliver Hanappi
我同意,我正在重新设计解决方案的布局以反映这一点。 - Lasse V. Karlsen
虽然这有点模糊的地带,但我实际上并没有调用任何外部程序,只是在我的程序文件中调用代码,但我仍然认为这更像是一个集成测试而不是单元测试。 - Lasse V. Karlsen
3个回答

93

你可以将 Console.InConsole.OutConsole.Error 重定向到自定义的StringWriter,像这样:

[TestMethod]
public void ValidateConsoleOutput()
{
    using (StringWriter sw = new StringWriter())
    {
        Console.SetOut(sw);

        ConsoleUser cu = new ConsoleUser();
        cu.DoWork();

        string expected = string.Format("Ploeh{0}", Environment.NewLine);
        Assert.AreEqual<string>(expected, sw.ToString());
    }
}

有关详细信息,请参见此博客文章


4
在每个测试结束时,您应该通过使用Console.SetOut(new StreamWriter(Console.OpenStandardError())(您可能还需要将Autoflush设置为true)来重置标准输出。这样做后,它将适用于任何测试运行器,包括R#。 - Abel
1
@Abel,你是指OpenStandardError还是OpenStandardOutput? - Daryn
我已经尝试过这个,似乎不需要使用Console.SetOut(new StreamWriter(Console.OpenStandardError())。 - Daryn
1
@Abel 我发现如果我将一系列测试中的每个测试分别运行,输出结果是正确的,但是如果我连续运行所有测试,那么只有第一个测试成功。其他测试失败,因为在第一个测试之后 StreamWriter 中没有任何内容。然而,我使用了您的方法(Console.SetOut)在每个测试之后重置标准输出。我不明白。 - jeancallisti
1
@Lombas,“ConsoleUser”是使用“System.Console”的类的替身 - 您的测试系统(SUT)。 - Mark Seemann
显示剩余7条评论

23
你可以使用这个简单的类来使用`using`语句获取输出:

您可以使用此简单类来使用“using”语句获取输出:

public class ConsoleOutput : IDisposable
{
    private StringWriter stringWriter;
    private TextWriter originalOutput;

    public ConsoleOutput()
    {
        stringWriter = new StringWriter();
        originalOutput = Console.Out;
        Console.SetOut(stringWriter);
    }

    public string GetOuput()
    {
        return stringWriter.ToString();
    }

    public void Dispose()
    {
        Console.SetOut(originalOutput);
        stringWriter.Dispose();
    }
}

以下是如何使用它的示例:

using (var consoleOutput = new ConsoleOutput())
{
    target.WriteToConsole(text);

    Assert.AreEqual(text, consoleOutput.GetOuput());
}

你可以在我的博客文章中找到更详细的信息和可用的代码示例 - 在单元测试中获取控制台输出.


4
你似乎没有阅读自我推广常见问题解答。你发表的每一个回答都是你博客的链接。 - Andrew Barber
4
我认为这个答案符合此处列出的可接受标准。您可以建议他们阅读FAQ,并提供链接,而不是以“你没有阅读”开始,这样会更友善 :) https://meta.stackexchange.com/questions/7931/faq-for-stack-exchange-sites - Dan Walmsley
我正在使用这个类,如果我单独运行每个测试,输出是正确的,但如果我连续运行所有测试,那么只有第一个测试成功。其他测试失败,因为GetOutput中没有任何内容。就好像标准输出在第一个测试后没有被正确重置一样,尽管变量“originalOutput”正是为此而存在。 - jeancallisti

1

你仍然需要一个集成测试,而不是单元测试,正如其他人正确建议的那样。

集成测试

ProgramTest.cs

using NUnit.Framework;

class ConsoleTests
{
    [Test]
    public void TestsStdOut()
    {
        var capturedStdOut = CapturedStdOut(() =>
        {
            RunApp();
        });

        Assert.AreEqual("Welcome, John!", capturedStdOut);
    }

    void RunApp(string[]? arguments = default)
    {
        var entryPoint = typeof(Program).Assembly.EntryPoint!;
        entryPoint.Invoke(null, new object[] { arguments ?? Array.Empty<string>() });
    }

    string CapturedStdOut(Action callback)
    {
        TextWriter originalStdOut = Console.Out;

        using var newStdOut = new StringWriter();
        Console.SetOut(newStdOut);

        callback.Invoke();
        var capturedOutput = newStdOut.ToString();

        Console.SetOut(originalStdOut);

        return capturedOutput;
    }
}

实施

Program.cs

Console.Write($"Welcome, John!");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接