Как выполнить модульное тестирование класса ввода консоли?

В одном из моих приложений у меня есть класс, который отвечает за пользовательский ввод. Методом ввода по умолчанию является консоль (клавиатура), и я хочу написать для нее несколько модульных тестов, чтобы убедиться, что это правильно.

Я рассматриваю возможность использования структуры google-test для модульного тестирования, которая упрощает автоматизировать все испытания. Однако я не уверен, как я могу автоматизировать тестирование ввода консоли.

Есть ли способ имитировать пользовательский ввод на клавиатуре? Или мне нужно вручную ввести свой тестовый ввод? Или, возможно, перенаправить stdin (либо в коде, либо с помощью конвейера при запуске модульного теста)?

РЕДАКТИРОВАТЬ: я планирую использовать GNU readline для пользовательского ввода. На данный момент я не вижу способа перенаправить входной поток этой библиотеки - возможно, у кого-то есть опыт в этом?


person a_m0d    schedule 21.08.2009    source источник
comment
Что не так с имитацией стандартного ввода с помощью файлового фиктивного объекта?   -  person S.Lott    schedule 21.08.2009
comment
Я новичок в модульном тестировании, поэтому раньше не видел фиктивных объектов. Глядя на это сейчас.   -  person a_m0d    schedule 21.08.2009


Ответы (7)


Вы можете использовать expect.

person chotchki    schedule 21.08.2009
comment
Десять лет назад я ожидал дымового тестирования консольного ETL-инструмента. Работал отлично. - person sal; 21.08.2009
comment
Мог. Но тогда догматичные люди больше не будут называть ваши тесты модульными тестами, поэтому голосование будет отрицательным. Но да, ожидайте, что это отличный инструмент, и его стоит проверить. - person rasjani; 21.08.2009
comment
Хорошо, возможно, они больше не называются модульными тестами, но это, кажется, лучший способ проверить, что мой входной класс работает так, как я хочу - я могу убедиться, что он принимает ввод, используя readline, тогда как Я не мог сделать это с помощью насмешек (насколько я понимаю) - мне пришлось бы создать фальшивую версию readline, что противоположно тому, что я хочу сделать. - person a_m0d; 24.08.2009

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

После этого вы сможете просто создать фиктивный поток с вашими пользовательскими данными и смоделировать любой пользовательский ввод.

person Vanya    schedule 21.08.2009
comment
Я не могу изменить входной поток, потому что я использую строку чтения GNU - см. редактирование вопроса. - person a_m0d; 21.08.2009
comment
^ если вы жестко подключаете ввод к gnu readline, то вы тестируете readline + свой код. Опять же, вы должны реорганизовать свой код обработки ввода, чтобы вы могли издеваться над частью readline. - person rasjani; 21.08.2009
comment
И еще один комментарий: это одна из причин, по которой я предпочитаю использовать TDD, так как это заставляет вас писать и разрабатывать код, который можно протестировать. - person rasjani; 21.08.2009
comment
Независимо от того, какую библиотеку вы используете, ваше приложение должно иметь какой-то метод ‹b›dispatch_input_from_outer_world(string inputMessage)‹/b› в вашем коде. В принципе, это та точка, к которой вы должны прикрепить макет :-) - person Vanya; 22.08.2009
comment
Интерфейс класса допускает несколько реализаций, которые будут использовать разные механизмы ввода, но мне нужно проверить, что тот, который использует readline, работает так, как я хочу. Поэтому я не могу изменить код обработки ввода в этой реализации, так как тогда я больше не буду проверять, работает ли реализация так, как хотелось бы. - person a_m0d; 24.08.2009
comment
Не могли бы вы немного рассказать о том, как на самом деле добиться этого? Есть ли ресурсы в Интернете? Что такое случайный входной поток? Я не получаю ничего разумного, когда гуглю этот термин. - person Tobias Feil; 08.04.2019

Макет ввода.

person S.Lott    schedule 21.08.2009


Я планирую использовать GNU readline для пользовательского ввода. На данный момент я не вижу способа перенаправить входной поток этой библиотеки.

Создайте абстрактный класс с членами, соответствующими функциональным возможностям readline, которые вы хотите использовать. Программируйте для этого абстрактного класса, а не непосредственно для readline API. Используйте внедрение зависимостей, чтобы передать экземпляр этого класса коду, который в нем нуждается.

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

person Wim Coenen    schedule 21.08.2009

Для .NET/C# вы можете использовать класс Options или варианты, найденные в этом вопросе. Поскольку у вас есть все команды, сопоставленные с делегатами, вы можете затем протестировать каждый из методов в конце делегатов и легко найти неизвестные команды:

MyHandler handler = new MyHandler()
CommandOptions options = new CommandOptions();

// Put this in the test Setup
options.Add("show", handler.Show)
        .Add("connect", v => handler.Connect(v))
        .Add("dir", handler.Dir);

if (!options.Parse(args))
   Assert.Fail(string.Format("{0} was not recognised.",args))

Класс MyHandler будет похож на:

public class MyHandler
{
    public void Show() { }
    public void Connect(string[] args){}
    public void Dir() {}
}
person Chris S    schedule 21.08.2009

Для консоли я всегда просто оборачиваю ее своей собственной реализацией.

Использование оболочки и интерфейса для всех сторонних элементов управления, которые участвуют в модульных тестах, делает работу с изоляционной структурой (например, Rhino Mocks) очень простой. Это дает мне контроль над тестированием и явно определяет зависимости в моем коде. Поскольку мне нужны новые функции консоли, я могу просто добавить их в интерфейс обертки. У меня еще не было проблем с раздуванием интерфейса...

public interface IConsoleShim
{
    void WriteLine(string message);
    ConsoleKeyInfo ReadKey();
}
public class ConsoleShim : IConsoleShim
{
    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }
    public ConsoleKeyInfo ReadKey()
    {
        return Console.ReadKey();
    }
}

Вот тест в действии

[NUnit.Framework.Test]
public void Run_writes_to_console_100_times_waits_for_keypress()
{
    // arrange
    Rhino.Mocks.MockRepository mocks = new Rhino.Mocks.MockRepository();
    IConsoleShim consoleMock = mocks.StrictMock<IConsoleShim>();
    Program program = new Program(consoleMock);
    int expected = 100;

    // VerifyAll automatically called
    Rhino.Mocks.With.Mocks(mocks).Expecting(() =>
        {
            Rhino.Mocks.Expect.Call(() => consoleMock.WriteLine("")).IgnoreArguments().Repeat.Times(expected);
            Rhino.Mocks.Expect.Call(consoleMock.ReadKey()).Return(new ConsoleKeyInfo());
        });

    //act
    program.Run();
}
person MarcLawrence    schedule 21.08.2009