我在Visual Studio中有一个测试项目我想用(在同一个解决方案中)测试我的控制台应用程序。
我正在尝试设置使用特定参数调用控制台应用程序的测试,并将实际输出与我期望的结果进行比较,然后执行我常用的Assert语句以适当地通过/不通过测试。
我能想到的最好的方法是在单元测试中使用System.Diagnostics.Process执行app exe。这有效。我可以阅读输出,一切都很好。
我遇到的问题是当我想在控制台应用程序代码中设置断点时,我可以做一些调试。由于Process启动了控制台应用程序,因此Visual Studio不会监视控制台应用程序,因此它不会中断。没有什么比“等待来自外部应用程序的请求”更像Web应用程序,我理解为什么,但这基本上就是我正在寻找的。 p>
所以我的问题是,有没有办法在Visual Studio中设置这些单元测试,在那里我仍然可以调试控制台应用程序?我能想到的唯一解决方法是在控制台应用程序上设置Start Action以启动一个外部程序,该程序将调用MSTest.exe,并以这种方式运行相应的单元测试。但这似乎是一个问题,我只是想错了,实际上有一个很多更明显的解决方案。
答案 0 :(得分:10)
使您的控制台应用程序尽可能精简,并将所有业务逻辑移至域类。 E.g。
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo(args);
}
}
之后,您可以轻松地为您的Foo类编写单元测试。
答案 1 :(得分:1)
单元测试不应该需要人工干预。要使您的应用程序可单元测试,应该抽象控制台交互 - 这可以使用TextReader
和TextWriter
类轻松完成。您可能会发现this question有帮助。
答案 2 :(得分:1)
本问题以及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>)
之类的东西来运行子任务)。