visual-studio 调用 Console.ReadLine() 的方法的 C# 单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3161341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# unit test for a method which calls Console.ReadLine()
提问by Petur Subev
I want to create a unit test for a member function of a class called ScoreBoardwhich is storing the top five players in a game.
我想为一个名为的类的成员函数创建一个单元测试,该类ScoreBoard存储游戏中的前五名玩家。
The problem is that the method I created a test for (SignInScoreBoard) is calling Console.ReadLine()so the user can type their name:
问题是我为 ( SignInScoreBoard)创建的测试方法正在调用,Console.ReadLine()因此用户可以键入他们的姓名:
public void SignInScoreBoard(int steps)
{
if (topScored.Count < 5)
{
Console.Write(ASK_FOR_NAME_MESSAGE);
string name = Console.ReadLine();
KeyValuePair<string, int> pair = new KeyValuePair<string, int>(name, steps);
topScored.Insert(topScored.Count, pair);
}
else
{
if (steps < topScored[4].Value)
{
topScored.RemoveAt(4);
Console.Write(ASK_FOR_NAME_MESSAGE);
string name = Console.ReadLine();
topScored.Insert(4, new KeyValuePair<string, int>(name, steps));
}
}
}
Is there a way to insert like ten users so I can check if the five with less moves (steps) are being stored?
有没有办法插入十个用户,以便我可以检查是否存储了五个移动(步)较少的用户?
回答by Reed Copsey
You'll need to refactor the lines of code that call Console.ReadLine into a separate object, so you can stub it out with your own implementation in your tests.
您需要将调用 Console.ReadLine 的代码行重构为一个单独的对象,以便您可以在测试中使用自己的实现将其存根。
As a quick example, you could just make a class like so:
举个简单的例子,你可以像这样创建一个类:
public class ConsoleNameRetriever {
public virtual string GetNextName()
{
return Console.ReadLine();
}
}
Then, in your method, refactor it to take an instance of this class instead. However, at test time, you could override this with a test implementation:
然后,在您的方法中,重构它以采用此类的实例。但是,在测试时,您可以使用测试实现覆盖它:
public class TestNameRetriever : ConsoleNameRetriever {
// This should give you the idea...
private string[] names = new string[] { "Foo", "Foo2", ... };
private int index = 0;
public override string GetNextName()
{
return names[index++];
}
}
When you test, swap out the implementation with a test implementation.
测试时,用测试实现替换实现。
Granted, I'd personally use a framework to make this easier, and use a clean interface instead of these implementations, but hopefully the above is enough to give you the right idea...
当然,我个人会使用一个框架来简化这个过程,并使用一个干净的界面而不是这些实现,但希望以上内容足以给你正确的想法......
回答by Lasse V. Karlsen
You should refactor your code to remove the dependency on the console from this code.
您应该重构您的代码以从此代码中删除对控制台的依赖。
For instance, you could do this:
例如,你可以这样做:
public interface IConsole
{
void Write(string message);
void WriteLine(string message);
string ReadLine();
}
and then change your code like this:
然后像这样改变你的代码:
public void SignInScoreBoard(int steps, IConsole console)
{
... just replace all references to Console with console
}
To run it in production, pass it an instance of this class:
要在生产中运行它,请将它传递给此类的一个实例:
public class ConsoleWrapper : IConsole
{
public void Write(string message)
{
Console.Write(message);
}
public void WriteLine(string message)
{
Console.WriteLine(message);
}
public string ReadLine()
{
return Console.ReadLine();
}
}
However, at test-time, use this:
但是,在测试时,请使用:
public class ConsoleWrapper : IConsole
{
public List<String> LinesToRead = new List<String>();
public void Write(string message)
{
}
public void WriteLine(string message)
{
}
public string ReadLine()
{
string result = LinesToRead[0];
LinesToRead.RemoveAt(0);
return result;
}
}
This makes your code easier to test.
这使您的代码更易于测试。
Of course, if you want to check that the correct output is written as well, you need to add code to the write methods to gather the output, so that you can assert on it in your test code.
当然,如果您还想检查是否写入了正确的输出,则需要向 write 方法添加代码以收集输出,以便您可以在测试代码中对其进行断言。
回答by Stephen Cleary
回答by OJ.
Why not create a new stream (file/memory) for both stdin and stdout, then redirect input/ouput to your new streams before calling the method? You could then check the content of the streams after the method has finished.
为什么不为 stdin 和 stdout 创建一个新流(文件/内存),然后在调用该方法之前将输入/输出重定向到新流?然后,您可以在方法完成后检查流的内容。
回答by Stéphane
Rather than abstracting Console, I would rather create a component to encapsulate this logic, and test this component, and use it in the console application.
与其抽象Console,我宁愿创建一个组件来封装这个逻辑,并测试这个组件,并在控制台应用程序中使用它。
回答by Mark H
public void SignInScoreBoard(int steps, Func<String> nameProvider)
{
...
string name = nameProvider();
...
}
In your test case, you can call it as
在您的测试用例中,您可以将其称为
SignInScoreBoard(val, () => "TestName");
In your normal implementation, call it as
在您的正常实现中,将其称为
SignInScoreBoard(val, Console.ReadLine);
If you're using C# 4.0, you can make Console.ReadLine a default value by saying
如果您使用的是 C# 4.0,则可以通过说将 Console.ReadLine 设为默认值
public void SignInScoreBoard(int steps, Func<String> nameProvider=null)
{
nameProvider = nameProvider ?? Console.ReadLine;
...
回答by Dan Ryan
I can't believe how many people have answered without looking at the question properly. The problem is the method in question does more than one thing i.e. asks for a name and inserts the top-score. Any reference to console can be taken out of this method and the name should be passed in instead:
我无法相信有多少人在没有正确看待问题的情况下回答了问题。问题是所讨论的方法不止一件事,即要求一个名字并插入最高分。任何对控制台的引用都可以从这个方法中取出,并且应该传入名称:
public void SignInScoreBoard(int steps, string nameOfTopScorer)
For other tests you will probably want to abstract out the reading of the console output as suggested in the other answers.
对于其他测试,您可能希望按照其他答案中的建议抽象出控制台输出的读数。
回答by honzakuzel1989
I had a similar issue few days ago. Encapsulation Console class seemed like overkill for me. Based on KISS principle and IoC/DI principle I putted dependencies for writer (output) and reader (input) to constructor. Let me show the example.
几天前我遇到了类似的问题。封装控制台类对我来说似乎有点矫枉过正。基于 KISS 原则和 IoC/DI 原则,我将编写器(输出)和读取器(输入)的依赖项放入构造函数。让我举个例子。
We can assume simple confirmation provider defined by interface IConfirmationProvider
我们可以假设由接口定义的简单确认提供者 IConfirmationProvider
public interface IConfirmationProvider
{
bool Confirm(string operation);
}
and his implementation is
他的实现是
public class ConfirmationProvider : IConfirmationProvider
{
private readonly TextReader input;
private readonly TextWriter output;
public ConfirmationProvider() : this(Console.In, Console.Out)
{
}
public ConfirmationProvider(TextReader input, TextWriter output)
{
this.input = input;
this.output = output;
}
public bool Confirm(string operation)
{
output.WriteLine($"Confirmed operation {operation}...");
if (input.ReadLine().Trim().ToLower() != "y")
{
output.WriteLine("Aborted!");
return false;
}
output.WriteLine("Confirmated!");
return true;
}
}
Now you can easily test your implementation when you inject dependency to your TextWriterand TextReader(in this example StreamReaderas TextReader)
现在,当您将依赖项注入您的TextWriter和TextReader(在本例中StreamReader为TextReader) 时,您可以轻松测试您的实现
[Test()]
public void Confirm_Yes_Test()
{
var cp = new ConfirmationProvider(new StringReader("y"), Console.Out);
Assert.IsTrue(cp.Confirm("operation"));
}
[Test()]
public void Confirm_No_Test()
{
var cp = new ConfirmationProvider(new StringReader("n"), Console.Out);
Assert.IsFalse(cp.Confirm("operation"));
}
And use your implementation from apllication standard way with defaults (Console.Inas TextReaderand Console.Outas TextWriter)
并使用默认值(Console.InasTextReader和Console.Outas TextWriter)从应用标准方式实现
IConfirmationProvider cp = new ConfirmationProvider();
That's all - one additional ctor with fields initialization.
这就是全部 - 一个带有字段初始化的额外 ctor。

