我想为名为ScoreBoard
的类的成员函数创建单元测试,该类存储游戏中的前五名玩家。
问题是我为(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));
}
}
}
有没有办法插入十个用户,这样我就可以检查是否存储了五个移动较少(步骤)的用户?
答案 0 :(得分:23)
您需要将调用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++];
}
}
进行测试时,请使用测试实现替换实现。
当然,我个人会使用一个框架来使这更容易,并使用一个干净的界面而不是这些实现,但希望上面的内容足以给你正确的想法......
答案 1 :(得分:11)
您应该重构代码以从此代码中删除对控制台的依赖。
例如,你可以这样做:
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<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;
}
}
这使您的代码更容易测试。
当然,如果要检查是否也写入了正确的输出,则需要向write方法添加代码以收集输出,以便您可以在测试代码中对其进行断言。
答案 2 :(得分:2)
您可以使用Moles用您自己的方法替换Console.ReadLine
,而无需更改代码(设计和实现抽象控制台,支持依赖注入完全没必要)。< / p>
答案 3 :(得分:2)
为什么不为stdin和stdout创建新的流(文件/内存),然后在调用方法之前将输入/输出重定向到新的流?然后,您可以在方法完成后检查流的内容。
答案 4 :(得分:1)
我宁愿创建一个组件来封装这个逻辑,并测试这个组件,并在控制台应用程序中使用它,而不是抽象Console。
答案 5 :(得分:1)
public void SignInScoreBoard(int steps, Func<String> nameProvider)
{
...
string name = nameProvider();
...
}
在您的测试用例中,您可以将其称为
SignInScoreBoard(val, () => "TestName");
在正常实施中,将其称为
SignInScoreBoard(val, Console.ReadLine);
如果您使用的是C#4.0,则可以通过说
使Console.ReadLine成为默认值public void SignInScoreBoard(int steps, Func<String> nameProvider=null)
{
nameProvider = nameProvider ?? Console.ReadLine;
...
答案 6 :(得分:0)
我无法相信有多少人在没有正确看待问题的情况下回答了问题。问题是所讨论的方法不止一件事,即询问名称并插入最高分。任何对控制台的引用都可以从这个方法中取出,而名称应该传入:
public void SignInScoreBoard(int steps, string nameOfTopScorer)
对于其他测试,您可能希望抽象出其他答案中建议的控制台输出读数。
答案 7 :(得分:0)
几天前,我遇到了类似的问题。封装控制台类对我来说似乎过于矫kill过正。基于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"));
}
并使用默认标准(以Console.In
为TextReader
和以Console.Out
为TextWriter
的复制标准方式实现您的实现
IConfirmationProvider cp = new ConfirmationProvider();
仅此而已-一个具有字段初始化功能的附加ctor。