вторник, 24 марта 2009 г.

Unit тестирование и boost::asio

Как известно, одно из главных требований к модульным тестам, это повторяемость. Очень сложно искать ошибку, которая воспроизводится через раз. А с кодом, использующим асинхронный ввод вывод, все обычно так и обстоит, как как обработчики различных событий, обычно, выполняются в неопределенном порядке, к тому-же из разных потоков. Но, если вы используете boost::asio, то все не так плохо. Эта библиотека спроектирована так, что-бы её можно было легко испоьзовать в модульных тестах. Представим, что у нас есть два класса, клиент и сервер, оба используют boost::asio для асинхронного IO. Для того, что-бы написать для них тест, нам нужно создать два io_service-a, так-как в они будут работать независимо. Далее, мы можем вызывать различные функции, которые начинают асинхронные операции, например запись данных в сокет, или ожидание подключения клиента. Но, вместо того, что-бы вызывать метод io_service::run, мы можем вызвать метод io_service::run_one, который выполняет обработчик только одного события. Благодаря этому, асинхронный код можно превратить в линейный и протестировать таким образом логику наших классов.
TEST(MyTest, test_async_connect)
{
    boost::io_service client_io, server_io;

    my_client client(client_io);
    my_server server(server_io);
    
    EXPECT_FALSE(client.connected());//connected возвращает false, если клиент не подключен к серверу
    EXPECT_EQ(server.clients_count(), 0);//к серверу не подключен ни один клиент
    server.start_accept();//начинаем принимать подключения
    client.async_connect("localhost", my_port);//метод async_connect, 
    //начинает асинхронную операцию подключения к серверу,
    //в коде my_client, это может быть реализовано примерно так:
    //  boost::asio::async_connect(thsi->socket_, &my_client::on_connect);
    //естественно, метод on_connect, должен изменить состояние клиента
    server_io.run_one();//во время этого вызова, сервер дожен принять подключение,
    //обработать сообветствующее событие и изменить свое состояние
    EXPECT_EQ(server.clients_count(), 1);//теперь клиент подключен к серверу
    EXPECT_FALSE(client.connected());//но сам еще не знает об этом
    client_io.run_one();//что-бы он об этом узнал, нужно что-бы выполнился обработчик собыитя on_connect
    EXPECT_TRUE(client.connected());//done
}

5 комментариев:

Анонимный комментирует...

Это я удачно зашел. Может книгу все же писать начнешь? ;)
Кстати сейчас встал вопрос ребром - думаю начать поднимать boost но ряд товарищей настаивают на ace. Ты можешь что нибудь сказать по этому вопросу?

Unknown комментирует...

ACE я не использовал, когда я делал свой выбор, для меня решающим фактором оказалась простота Asio, по сравнению с ACE. Начать очень просто, особенно если есть опыт работы с библиотекой boost. С ACE framework можно делать более продвинутые вещи, к примеру управлять скоростью работы producer-a в зависимости от скорости работы consumer-a. В Asio это нужно делать вручную.

roman комментирует...

выложи плиз код метода my_client::connected()... или ткни носом где в хелпе к boost.asio написано как узнать подключен клиент к серверу...

roman комментирует...

Покажи, плиз, код метода my_client::connected(). а то я никак в хелпе к boost.asio не могу найти как узнать подключен клиент к серверу или нет...

roman комментирует...

Покажи, плиз, код метода my_client::connected(). а то я никак в хелпе к boost.asio не могу найти как узнать подключен клиент к серверу или нет...