Модульный тест C# на метод, который называет Консоль. ReadLine ()

Я хочу создать модульный тест на функцию членства названного класса 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));
        }
    }
}

Существует ли способ вставить как десять пользователей, таким образом, я могу проверить, хранятся ли пять с меньшим количеством перемещений (шаги)?

11
задан alex 9 December 2016 в 18:04
поделиться

7 ответов

Вам нужно будет преобразовать строки кода, вызывающие 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++];
     }
}

При тестировании замените реализацию тестовой реализацией.

Конечно, я бы лично использовал фреймворк, чтобы упростить эту задачу, и использовать чистый интерфейс вместо этих реализаций, но, надеюсь, вышеизложенного достаточно, чтобы дать вам правильное представление ...

24
ответ дан 3 December 2019 в 01:12
поделиться

Почему бы не создать новый поток (файл / память) как для стандартного ввода, так и для стандартного вывода, а затем перенаправить ввод / вывод в новые потоки перед вызовом метода? Затем вы можете проверить содержимое потоков после завершения метода.

2
ответ дан 3 December 2019 в 01:12
поделиться

Вместо того, чтобы абстрагироваться от консоли, я бы предпочел создать компонент для инкапсуляции этой логики, протестировать этот компонент и использовать его в консольном приложении.

1
ответ дан 3 December 2019 в 01:12
поделиться
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;
    ...
1
ответ дан 3 December 2019 в 01:12
поделиться

Вы можете использовать Moles, чтобы заменить Console.ReadLine своим собственным методом без необходимости изменять код вообще (разработка и реализация абстрактной консоли с поддержкой инъекции зависимостей - все это совершенно не нужно).

2
ответ дан 3 December 2019 в 01:12
поделиться

Вам следует рефакторить свой код, чтобы убрать зависимость от консоли из этого кода.

Например, вы можете сделать так:

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
}

Чтобы запустить его в production, передайте ему экземпляр этого класса:

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 для сбора вывода, чтобы вы могли утверждать об этом в вашем тестовом коде.

14
ответ дан 3 December 2019 в 01:12
поделиться

Не могу поверить, сколько людей ответили, не посмотрев на вопрос должным образом. Проблема в том, что рассматриваемый метод делает более чем одну вещь, т.е. запрашивает имя и вставляет высший балл. Любая ссылка на консоль может быть удалена из этого метода, а вместо нее должно быть передано имя:

public void SignInScoreBoard(int steps, string nameOfTopScorer)

Для других тестов вы, вероятно, захотите абстрагироваться от чтения вывода консоли, как было предложено в других ответах.

0
ответ дан 3 December 2019 в 01:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: