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条回答
forever°为你锁心
2楼-- · 2020-02-08 18:53

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.

We can assume simple confirmation provider defined by interface 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 TextWriter and TextReader (in this example StreamReader as 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.In as TextReader and Console.Out as TextWriter)

IConfirmationProvider cp = new ConfirmationProvider();

That's all - one additional ctor with fields initialization.

查看更多
Juvenile、少年°
3楼-- · 2020-02-08 18:54

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.

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...

查看更多
登录 后发表回答