调用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; } }
现在,当您将依赖项注入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")); }
并使用apllication标准方式实现默认设置( Console.In
为TextReader
, Console.Out
为TextWriter
)
IConfirmationProvider cp = new ConfirmationProvider();
这就是全部 – 一个字段初始化的附加ctor。