Макет повышения AsyncReadStream для модульных тестов

Я столкнулся с ситуацией, когда мы используем http::async_read(...) и http::async_write(...) внутри класса, который представляет сеанс HTTP. Однако, если мы хотим провести модульное тестирование этого класса, мы бы предпочли не открывать сетевое соединение с портом и сокетом для прослушивания и, соответственно, принятия первого вызова через acceptor.

Скорее, я надеялся «подделать» поведение socket с помощью другого AsyncReadStream. Я хотел написать свой собственный, но это кажется сложной задачей, и поиск не дал особого результата.

Какой пример AsyncReadStream может быть здесь полезен для простой потоковой передачи запроса HTTP и последующего ввода ответа в целях модульного тестирования? Если нет, есть ли какой-нибудь шаблон, который я мог бы использовать для написания простого шаблона с этой очень минимальной «фальшивой» функциональностью?

Вы можете использовать классическую инъекцию зависимостей и издеваться над своим интерфейсом. Чтобы быть более конкретным, вам нужно будет показать пример кода вашей системы и вызовов, которые вы пытаетесь имитировать. Но в принципе любую проблему такого рода можно жестоко решить, написав своего рода class INetworkIface { abstract methods here };.

pptaszni 18.04.2024 10:54

Я надеюсь не решать эту проблему жестоко @pptaszni. Решение, очевидно, состоит в том, чтобы сделать то, что вы говорите, и вызвать их через какой-то http_interface, который может быть либо макетом, либо prod., и prod. внизу вызывал бы описанные мной функции (в отличие от того, чтобы они были встроены и вызывались напрямую как есть), но я надеюсь на способ оставить упомянутые мной функции и просто вместо этого использовать «макет» AsyncReadStream, как я описал.

user129393192 18.04.2024 11:02

@pptaszni, за исключением того, что это совсем не работает для статического параметрического полиморфизма

sehe 18.04.2024 17:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Смоделировать концепции AsyncReadStream/AsyncWriteStream почти тривиально.

Единственная сложность связана с инициированием асинхронных операций с использованием шаблонов CompletionToken:

Например.:

Прямой эфир на Колиру

#include <boost/asio.hpp>
#include <iostream>
#include <fmt/ranges.h>
namespace asio = boost::asio;
using namespace std::literals;

struct MockStream {
    using error_code    = boost::system::error_code;
    using executor_type = asio::any_io_executor;
    MockStream(asio::any_io_executor ex) : ex_(std::move(ex)) {}
    executor_type get_executor() const { return ex_; }

    template <typename Token> auto async_write_some(asio::const_buffer buf, Token&& token) {
        return asio::async_initiate<Token, void(error_code, size_t)>( //
            [fallback = ex_](auto h, auto buf) {
                auto ex = asio::get_associated_executor(h, fallback);
                asio::dispatch(ex, [=, h = std::move(h)]() mutable {
                    fmt::print("Simulated write of {} completed\n", buf.size());
                    std::move(h)({}, asio::buffer_size(buf));
                });
            },
            token, buf);
    }

    template <typename Token> auto async_read_some(asio::mutable_buffer buf, Token&& token) {
        return asio::async_initiate<Token, void(error_code, size_t)>( //
            [fallback = ex_, ch = mock_data_++](auto h, auto buf) {
                auto ex = asio::get_associated_executor(h, fallback);



                std::fill(asio::buffers_begin(buf), asio::buffers_end(buf), ch);

                asio::dispatch(ex, [=, h = std::move(h)]() mutable {
                    if (ch == 'e') {
                        auto partial = (rand() % buf.size()) / 4;
                        fmt::print("Simulated EOF with {} partial read\n", partial);
                        std::move(h)(asio::error::eof, partial);
                    } else {
                        fmt::print("Simulated read of {} completed\n", buf.size());
                        std::move(h)({}, asio::buffer_size(buf));
                    }
                });
            },
            token, buf);
    }

    asio::any_io_executor ex_;
    char                  mock_data_ = 'a';
};

int main() {
    srand(time(NULL));
    asio::io_context ioc;

    MockStream stream(asio::system_executor{});

    asio::async_write(stream, asio::buffer("Hello world"sv), [](auto ec, size_t n) {
        fmt::print("Write completed with {} and {} bytes written\n", ec.message(), n);
    });

    std::array<char, 10> buf;
    asio::async_read(stream, asio::buffer(buf), [&buf](auto ec, size_t n) {
        fmt::print("Read completed with {} and {} bytes read: {}\n", ec.message(), n, buf);
    });

    asio::streambuf sbuf;
    asio::async_read_until(stream, sbuf, "d", [&sbuf](auto ec, size_t n) {
        fmt::print("Read-until completed with {} and {} bytes read\n", ec.message(), n);
        // std::cout << "Streambuf: " << &sbuf << std::endl;
        sbuf.consume(sbuf.size());
    });

    asio::async_read_until(stream, sbuf, "z", [&sbuf](auto ec, size_t n) {
        fmt::print("Read-until completed with {} and {} bytes read\n", ec.message(), n);
        std::cout << "Streambuf: " << &sbuf << std::endl;
    });

    ioc.run();
}

Печать, например.

ССЫЛКИ

Похожие реализации здесь:

Кроме того, рассмотрите возможность простого запуска локального соединения. Возможно, через домен UNIX или каналы. Если вы действительно считаете, что транспорт, буферизация и режимы отказа не имеют значения, просто полностью отключите уровень связи. в конце концов, это UNIT-тест, почему он зависит от реализации ввода-вывода?

sehe 18.04.2024 18:27

Я не нахожу это тривиальным. Я пытался изменить ваш пример, чтобы он работал на меня. В двух чтениях, где он возвращает Success для вас, я получаю Undefined error.

user129393192 18.04.2024 21:51

Да, я имел в виду, что интерфейс тривиальный (3 функции). Поначалу асинхронные инициации не являются тривиальными. Можете ли вы поделиться тем, что у вас есть? Вы можете редактировать колиру.

sehe 18.04.2024 22:01

Можно ли поделиться тем, что у меня есть, приватно?

user129393192 18.04.2024 23:36

Конечно. И, вау. Ты тот парень , который спрашивает об ошибке ? Это имеет больше смысла. Я обновил ответ, добавив демо-версию EOF.

sehe 18.04.2024 23:49

Спасибо. Я очень близок к тому, чтобы это заработало, но я пытаюсь использовать io_service для своего варианта использования. Есть ли способ сделать это? Я получаю static assertion failed: AsyncReadStream type requirements not met.

user129393192 19.04.2024 01:33

Что ты имеешь в виду, @user129393192? io_service устарел, но не имеет ничего общего с концепцией потока. Если вы используете старые версии boost, обратитесь к документации вашей версии или обновления? [Я не думаю, что это «близко к тому, чтобы это работало», это все равно, что сказать, что ваш каяк близок к работе, когда он плывет вверх тормашками в потоке. Я предлагаю не слишком сильно идти против течения]

sehe 19.04.2024 01:34

Я был уже довольно давно. Как я могу написать вам, чтобы поделиться тем, что у меня есть?

user129393192 19.04.2024 01:38

Моя реализация принимает io_service в качестве параметра для конструкции socket, но похоже, что она не примет это вместо any_io_executor. Интересно, есть ли какое-то внутреннее несоответствие версий. Я изо всех сил пытаюсь понять это и заставить это работать.

user129393192 19.04.2024 01:50

Несовпадение версий, окей. io_service уже давно устарел. Как я уже сказал, посмотрите документацию для вашей версии boost или обновите ее.

sehe 19.04.2024 03:10

Я сделал. Я понял это. Спасибо за вашу помощь. Просто странно, что socket принимает io_service (там написано, что моя версия 1.73), тогда как в документации сказано, что она должна принимать только executor.

user129393192 19.04.2024 04:48

Обратите внимание, что даже 1.73.0. больше даже не документирует io_service, и он не будет компилироваться начиная с Boost 1.66(!!), если вы компилируете с BOOST_ASIO_NO_DEPRECATED. Это октябрь 2017 года.

sehe 19.04.2024 14:52

Итак, помимо перемещения стоек ворот (вопрос задается о минимальном объекте потока, а не о замене подмножества данного типа сокета): минимальная версия, поддерживающая этот конструктор: coliru.stacked-crooked.com/a /4345f1eebcd60e7f

sehe 19.04.2024 15:26

Другие вопросы по теме