调用Console.ReadLine()的方法的C#unit testing

我想为一个名为ScoreBoard的类的成员函数创建一个unit testing,该类存储游戏中的前五名玩家。

问题是我为( SignInScoreBoard )创建测试的方法是调用Console.ReadLine()因此用户可以输入他们的名字:

 public void SignInScoreBoard(int steps) { if (topScored.Count < 5) { Console.Write(ASK_FOR_NAME_MESSAGE); string name = Console.ReadLine(); KeyValuePair pair = new KeyValuePair(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(name, steps)); } } } 

有没有办法插入十个用户,所以我可以检查是否存储了五个较少的移动(步骤)?

您需要将调用Console.ReadLine的代码行重构为一个单独的对象,因此您可以在测试中使用自己的实现将其存根。

作为一个简单的例子,你可以像这样创建一个类:

 public class ConsoleNameRetriever { public virtual string GetNextName() { return Console.ReadLine(); } } 

然后,在您的方法中,重构它以取代此类的实例。 但是,在测试时,您可以使用测试实现覆盖它:

 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++]; } } 

测试时,请使用测试实现替换实现。

当然,我个人会使用一个框架来使这更容易,并使用一个干净的界面而不是这些实现,但希望上面的内容足以给你正确的想法……

您应该重构代码以从此代码中删除对控制台的依赖性。

例如,你可以这样做:

 public interface IConsole { void Write(string message); void WriteLine(string message); string ReadLine(); } 

然后像这样更改你的代码:

 public void SignInScoreBoard(int steps, IConsole console) { ... just replace all references to Console with console } 

要在生产中运行它,请传递此类的实例:

 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(); } } 

但是,在测试时,请使用:

 public class ConsoleWrapper : IConsole { public List LinesToRead = new List(); public void Write(string message) { } public void WriteLine(string message) { } public string ReadLine() { string result = LinesToRead[0]; LinesToRead.RemoveAt(0); return result; } } 

这使您的代码更容易测试。

当然,如果要检查是否也写入了正确的输出,则需要向write方法添加代码以收集输出,以便可以在测试代码中对其进行断言。

您可以使用Moles将Console.ReadLine替换为您自己的方法,而无需更改代码(设计和实现抽象控制台,支持dependency injection完全没必要)。

为什么不为stdin和stdout创建新的流(文件/内存),然后在调用方法之前将输入/输出重定向到新的流? 然后,您可以在方法完成后检查流的内容。

我宁愿创建一个组件来封装这个逻辑,并测试这个组件,并在控制台应用程序中使用它,而不是抽象Console。

 public void SignInScoreBoard(int steps, Func nameProvider) { ... string name = nameProvider(); ... } 

在您的测试用例中,您可以将其称为

 SignInScoreBoard(val, () => "TestName"); 

在您的正常实现中,将其命名为

 SignInScoreBoard(val, Console.ReadLine); 

如果您使用的是C#4.0,则可以通过说明将Console.ReadLine设置为默认值

 public void SignInScoreBoard(int steps, Func nameProvider=null) { nameProvider = nameProvider ?? Console.ReadLine; ... 

我不敢相信有多少人在没有正确看待问题的情况下回答。 问题是所讨论的方法不止一件事,即询问名称并插入最高分。 可以从此方法中取出对控制台的任何引用,而应该传入名称:

 public void SignInScoreBoard(int steps, string nameOfTopScorer) 

对于其他测试,您可能希望抽象出其他答案中建议的控制台输出读数。

几天前我遇到过类似的问题。 Encapsulation Console类似乎对我来说太过分了。 基于KISS原理和IoC / DI原理,我将编写器(输出)和读取器(输入)的依赖项放到构造函数中。 让我举个例子。

我们可以假设接口IConfirmationProvider定义的简单确认提供程序

 public interface IConfirmationProvider { bool Confirm(string operation); } 

他的实施是

 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; } } 

现在,当您将依赖项注入TextWriterTextReader时,您可以轻松地测试实现(在此示例中, StreamReaderTextReader

 [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")); } 

并使用apllication标准方式实现默认设置( Console.InTextReaderConsole.OutTextWriter

 IConfirmationProvider cp = new ConfirmationProvider(); 

这就是全部 – 一个字段初始化的附加ctor。