Модульный тест C # для метода, который вызывает Console.ReadLine () - PullRequest
11 голосов
/ 02 июля 2010

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

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

Ответы [ 8 ]

23 голосов
/ 02 июля 2010

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

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

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

12 голосов
/ 02 июля 2010

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

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

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;
    }
}

Это облегчает тестирование вашего кода.

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

2 голосов
/ 02 июля 2010

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

2 голосов
/ 02 июля 2010

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

1 голос
/ 02 июля 2010
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 голос
/ 02 июля 2010

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

0 голосов
/ 19 сентября 2018

У меня была похожая проблема несколько дней назад.Класс 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"));
}

И используете свою реализацию из стандартного способа применения с значениями по умолчанию (Console.In как TextReader и Console.Out как TextWriter)

IConfirmationProvider cp = new ConfirmationProvider();

Вот и все - один дополнительный ctor с инициализацией полей.

0 голосов
/ 15 июля 2010

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

public void SignInScoreBoard(int steps, string nameOfTopScorer)

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

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