C# unit test for a method which calls Console.Read

2020-02-08 18:12发布

I want to create a unit test for a member function of a class called ScoreBoard which is storing the top five players in a game.

The problem is that the method I created a test for (SignInScoreBoard) is calling Console.ReadLine() so the user can type their name:

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?

8条回答
我想做一个坏孩纸
2楼-- · 2020-02-08 18:32
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

public void SignInScoreBoard(int steps, Func<String> nameProvider=null)
{
    nameProvider = nameProvider ?? Console.ReadLine;
    ...
查看更多
Summer. ? 凉城
3楼-- · 2020-02-08 18:35

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.

查看更多
Rolldiameter
4楼-- · 2020-02-08 18:35

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.

查看更多
干净又极端
5楼-- · 2020-02-08 18:37

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.

查看更多
放我归山
6楼-- · 2020-02-08 18:47

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.

查看更多
兄弟一词,经得起流年.
7楼-- · 2020-02-08 18:49

You can use Moles to replace Console.ReadLine with your own method without having to change your code at all (designing and implementing an abstract console, with support for dependency injection is all completely unnecessary).

查看更多
登录 后发表回答