目前的问题有很多答案,Best way to unit test console c# app、NUnit Test - Looping - C# 可能还有很多其他问题,这表明对未经修改的“不可测试”控制台应用程序进行直接单元测试不是一个好方法去测试。他们都是正确的。
但是,如果您出于某种原因确实需要以这种方式进行测试,并且您能够将控制台应用程序作为您的测试项目的参考(如果两者在同一个解决方案中,您可能可以),可以在不诉诸Process.Start 的情况下这样做。在 .NET 4.5 或更高版本中,使用 xunit 语法:
[Theory]
[MemberData("YourStaticDataProviderField")]
public async void SomeTest(string initialString, string resultString, params int[] indexes)
{
using (var consoleInStream = new AnonymousPipeServerStream(PipeDirection.Out))
using (var consoleOutStream = new AnonymousPipeServerStream(PipeDirection.In))
using (var writer = new StreamWriter(consoleInStream, Encoding.Default, 1024, true))
using (var reader = new StreamReader(consoleOutStream, Encoding.Default, false, 1024, true))
using (var tokenSource = new CancellationTokenSource())
{
// AutoFlush must be set to true to emulate actual console behavior,
// else calls to Console.In.Read*() may hang waiting for input.
writer.AutoFlush = true;
Task programTask = Task.Run(() =>
{
using (var consoleInReader =
new StreamReader(new AnonymousPipeClientStream(PipeDirection.In,
consoleInStream.GetClientHandleAsString())))
using (var consoleOutWriter =
new StreamWriter(new AnonymousPipeClientStream(PipeDirection.Out,
consoleOutStream.GetClientHandleAsString())))
{
// Again, AutoFlush must be true
consoleOutWriter.AutoFlush = true;
Console.SetIn(consoleInReader);
Console.SetOut(consoleOutWriter);
// Of course, pass any arguments your console application
// needs to run your test. Assuming no arguments are
// needed:
Program.Main(new string[0]);
}
}, tokenSource.Token);
// Read and write as your test dictates.
await writer.WriteLineAsync(initialString.Length.ToString());
await writer.WriteLineAsync(initialString);
await writer.WriteLineAsync(indexes.Length.ToString());
await writer.WriteLineAsync(String.Join(" ", indexes));
var result = await reader.ReadLineAsync();
await writer.WriteLineAsync();
// It is probably a good idea to set a timeout in case
// the method under test does not behave as expected (e.g.,
// is still waiting for input). Adjust 5000 milliseconds
// to your liking.
if (!programTask.Wait(5000, tokenSource.Token))
{
tokenSource.Cancel();
Assert.False(true, "programTask did not complete");
}
// Assert whatever your test requires.
Assert.Null(programTask.Exception);
Assert.Equal(resultString, result);
}
}
如果您以不同方式处理异步方法,此解决方案可能适用于 .NET 3.5 或更高版本。 AnonymousPipe(Server|Client)Stream 是
在 .NET 3.5 中引入。其他单元测试框架应该与
适当的语法更改。
管道流System.IO.Pipes.AnonymousPipeServerStream 和System.IO.Pipes.AnonymousPipeClientStream 是使该解决方案发挥作用的关键。因为流具有当前位置,所以让两个不同的进程同时引用同一个MemoryStream 并不能可靠地工作。使用管道流允许在父进程和子进程中使用流,就像这里所做的那样。在子任务中运行Program.Main(string[]) 是必要的,以便单元测试进程可以在程序运行时从控制台读取和写入。根据文档,AnonymousPipeClientStream 对象应该属于子任务,这就是它们在任务运行器中创建的原因。
如果你需要测试异常,你可以从programTask对象获取异常数据(或者,在xunit下,使用Assert.ThrowsAsync<ExpectedException>(Func<Task>)之类的东西来运行子任务)。