我在Visual Studio中的测试项目,我想使用来测试我的控制台应用程序(在相同的解决方案)。
我试图建立其调用控制台应用程序具有特定参数的测试,与实际输出与我的期望比较,然后做我平时断言语句以适当的通过/失败测试。
要做到这一点,我能想出的最好的办法,就是用的System.Diagnostics.Process执行单元测试中的应用程序exe文件。 这工作。 我可以读取输出,一切都很好。
我遇到的问题是,当我想设置控制台应用程序代码中的断点,所以我可以做一些调试。 由于过程拉开序幕控制台应用程序,Visual Studio是不看的控制台应用程序,所以它不会打破。 有没有像“等待来自外部应用程序的请求”之类没有与Web应用程序的,我明白为什么,但是这基本上就是我要找的。
所以我的问题是,有没有什么办法来设置这些单元测试的Visual Studio,在那里我仍然可以调试控制台应用程序里面? 我能想到的唯一的解决方法是设置在控制台应用程序开始行动启动外部程序,它会叫MSTest.exe,并运行相应的单元测试的方式。 但这似乎这我只是想错了一个问题,居然还有一个更明显的解决方案。
让你的控制台应用程序尽可能薄,并且将所有的业务逻辑域类。 例如
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo(args);
}
}
之后,你可以很容易地写你的Foo类的单元测试。
单元测试不应该需要人工交互。 为了使应用程序的单位可测试的 ,控制台交互应该被抽象-这可以很容易地使用完成TextReader
和TextWriter
类。 您可能会发现这个问题有帮助。
有很多在本问题的答案,以及以最佳方式进行单元测试控制台C#应用程序 , NUnit测试-环流式- 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>)
来运行子任务)。