Вызов delivery_case(agent) говорит о том, что будет проверяться проверка того, как агент agent будет реагировать на доставку сообщения. Этот вызов возвращает специальный объект-билдере, который позволяет настроить тестовый случай.
Метод .on_message<T> говорит, что если агент получает сообщение типа T, то агент должен:
среагировать на это сообщение (т.е. обработать его);
после обработки сообщения должна быть вызвана заданная в on_message лямбда, в которой выполняются тестовые проверки.
В принципе, методов .on_message на один тестовый случай можно навесить несколько. Это может потребоваться, если агент в каком-то своем состоянии обрабатывает несколько разных сообщений.
Метод time_limit указывает, что тестовый случай должен отработать за 1 секунду. Если в это время не уложились, то тестовый случай будет объявлен проваленным.
Метод impact<Msg> указывает, что перед проверкой тестового случая нужно отослать указанное сообщение.
Метод perform заставляет отработать то, что было задано в объекте-билдере. Т.е., на агента вешаются специальные перехватчики сообщений, которые будут контролировать обработку сообщений, заданных в on_message. Затем, если использовался метод impact, будет отослано стартовое сообщение. После чего perform будет ждать не более time_limit пока агент не получит одно из указанных в on_message сообщений.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
А проверить есть ли в текущем состоянии у агента подписка на "сообщение" возможно? Была например so_has_subscription(..)?
В общем случае подразумевается, что агенты должны быть "тестируемыми".
Т.е. разработчик должен позаботиться о наличии обратной связи, которую можно в тестах проверить. Например заложить специальную public функцию, которую можно проверить..
Last edit: Pavel 2018-12-03
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ну и не всегда можно полагаться на white box тестирование, иногда может быть полезно написать тесты и на чужого агента, доступа к потрохам которого нет.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Я не уверен, что для should_ignore вообще есть смысл делать какие-то дополнительные проверки. Т.е. повесить лямбду с проверками на on_message -- в этом я смысл вижу. А вот в такой же лямбде для should_ignore пока не вижу.
Но если на should_ignore таки навешивать лямбду, то в этой лямбде можно будет вызвать любой публичный метод у агента.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Видимо это я не совсем понял цель should_ignore. Я так понимал, что это проверк что указанное сообщение "не пришло" (т.е. тут даже обработчик не навешивается, т.к. нет как такогого события)
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Это проверка для случая, когда у агента, скажем, два состояния. В одном он сообщение обрабатывает, в другом нет. Проверка should_ignore проверяет, что когда агент во втором состоянии, то он действительно не обрабатывает сообщение.
Вот возьмем тот же пример с агентом fork. Изначально он находится в st_free. Следовательно, сообщение msg_put (положить) должно быть проигнорировано. Затем он переходит в st_taken. В этом состоянии на msg_put агент должен среагировать и перейти в st_free. Если затем еще раз отослать msg_put, то агент должен его вновь проигнорировать.
Такое поведение мы можем протестировать следующей последовательностью:
Кстати на тему проверок "не поменял состояние", "не послал сообщение" и т.п. проверка вида "НЕ сделал что-то" возможно требует своего отдельного time_limit, а точнее time_hold. Время в течение которого "не происходит проверерямое событие". Чтобы не было так: послали агенту сообщение проверили, что он "НЕ послал в ответ" какое-то сообщение
* а в следующее мгновенье после нашей проверки, он его послал (ведь у нас тут "асинхронщина")
или "проверили, что он не перешёл в новое состояние", а в следющее мгновенье, он перешёл.
А если мы говорим should_ignore<msg_put>(...).time_hold(2s) то избавляемся от этого.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Когда нужно проверить, что агент среагировал "НЕ раньше чем" через N секунд. Например, мы посылаем агенту какое-то сообщение и должны проверить, что агент посылает ответное "Не раньше" чем через 2 секунды. Т.е. это проверка, как агент реализует какую-то "задержку".
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ну возможно для акторной архитектуры это просто редкий (а может действительно ненужный case).
Предположим агент по своей логике получив сообщение X должен подождать 5 сек и послать сообщение Y (или сменить состояние). Т.е. по приходу сообщения он скорее всего сделает so_5::send_delayed на 5 сек, а потом send<Y>(..). Вот чтобы проверить это, мы можем (если можем) либо проверить что он заказал таймер, либо подождать 5 сек (time_hold) и убедиться, что в течение этого времени агент не посылал сообщение Y.
Как то так )
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Другой более конкретный (близкий мне) пример из АСУ. Предположим происходит какое-то событие, если в течение заданного времени оператор не реагирует, то мы должны сформировать какое-то сообщение и выполнить какие-то действия. hold_time позволяет нам провести проверку. что агент действительно подождал положенное время и только после этого предпринял какие-то действия.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Мне кажется, что здесь какой-то другой сценарий уже рассматривается. Пока что речь шла о том, чтобы, грубо говоря, ткнуть в агента палкой и посмотреть, как он ответит.
А здесь явно другая ситуация. Агент в какой-то момент решает что-то сделать. И мы должны проверить и тот факт, что агент это что-то сделал. И тот факт, что агент решил сделать это вовремя.
До этого руки пока не дошли. И, есть сомнение, что это достижимо с помощью delivery_check.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Мне думается, что здесь можно рассмотреть две ситуации.
Первая. Мы отсылаем сообщение агенту, от выставляет сам себе таймерную заявку и нам нужно убедится, что когда таймерная заявка сработает, то агент должным образом на нее среагирует. Тогда с использованием delivery_check может получится что-то вроде:
tests::delivery_check(agent)// Ничего не делаем при получении сообщения, хотя могли бы// проверить отсылку таймерного сообщения..on_message<do_something>(agent.so_direct_mbox(),[]{}).impact<do_something>(agent,...).check();// Теперь ждем того, что через N секунд агент должен среагировать// на отложенное сообщение.tests::delivery_check(agent).on_message<delayed_msg>(agent.so_direct_mbox(),[&]{...}).time_limit(std::chrono::seconds{N}).check();// Здесь нет impact, т.к. delayed_msg должно// прилететь от таймера.
Вторая. Мы воздействуем на какого-то агента, тот еще на кого-то, тот еще на кого-то и, в конце-концов, через какое-то время наш агент должен среагировать на отложенное сообщение. В этом случае мы, наверное, просто запишем вот так:
// Теперь ждем того, что через N секунд агент должен среагировать
// на отложенное сообщение.
Вот тут как раз "ошибка". Тут проверяется, что он среагирует не более чем за N секунд time_limit, а я хотел проверить, что не ранее, чем через time_hold. Т.е., что в течение этого времени он гарантированно НЕ будет ничего делать (например не сменит состояние или не пошлёт определённое сообщение). И для такой проверки требуется отдельный "синтаксис" или название.. Например check_hold<msg>(...).time_hold(..) или check_freeze<msg>() ну или что-то такое..
Last edit: Pavel 2018-12-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Разве что, тут подразумевается, что должно в итоге придти some_initial_msg (если я правильно понял). А хочется иметь возможность проверить, что не приходило some_msg, не обязательно что оно в итоге придёт.
Или второй случай, проверка что не менялось состояние агента в течение заданного времени. Т.е. может всё-таки какой-то особы синтаксис
Суть в том, чтобы внутри unit-теста определить "сценарий", который затем "проигрывается" и результат "проигрыша" сценария затем можно проверить отдельными конструкциями REQUIRE и/или ASSERT.
Сценарий состоит из шагов. Шаги должны вполняться последовательно и каждый следующий шаг должен произойти только после исполнения предыдущего шага.
Предполагается, что каждый шаг -- это действие с реакцией на него. В результате реакции могут возникать другие действия, вот эти другие действия и будут выполняться на последующих шагах.
В двух последующих комментариях будут показаны наброски, как посредством сценариев можно протестировать агентов fork_t и philosopher_t из примера dining_philosophers.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Сперва создается тестовая кооперация, в которую входит нормальный агент fork_t и имитация агента-философа. Эта имитация необходима для того, чтобы
a) иметь mbox, на который будут отсылаться ответные сообщения и
b) можно было контролировать сам факт пришествия ответных сообщений.
Сценарий определяет несколько воздействий:
на сообщение msg_put в начальном состоянии агент-fork реагировать не должен;
на сообщение msg_take в начальном состоянии агент-fork должен среагировать изменением своего состояния и ответным сигналом msg_taken;
на повторое сообщение msg_take в занятом состоянии агент-fork должен среагировать ответным сигналом msg_busy;
на сообщение msg_put в занятом состоянии агент-fork должен среагировать изменением состояния.
Для того, чтобы проверить факты изменения состояния мы предписываем соответствующим шагам сохранить у себя имя текущего состояния агента (состояние определяется после того, как агент закончит обработку сообщения, указанного в receives).
Сценарий проигрывается и затем то, что было сохранено в сценарии можно проверить посредством REQUIRE. Т.е. все assertions выполняются уже после того, как сценарий отработал.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Вот так может выглядеть один тестовый случай для агента philosopher_t:
TEST_CASE("philosopher check [take both forks]"){tests::wrapped_env_tsobj;so_5::agent_t*philosopher{};so_5::agent_t*fork{};sobj.env().introduce_coop([](so_5::coop_t&coop){autoavailable_fork=coop.define_agent();available_fork.event(available_fork,[](so_5::mhood_t<msg_take>cmd){so_5::send<msg_taken>(cmd->m_who);});fork=available_fork.agent_ptr();philosopher=coop.make_agent<philosopher_t>("philosopher",available_fork.direct_mbox(),available_fork.direct_mbox());});autoscenario=tests::make_scenario();scenario.define_step("stop_thinking").not_before_than(250ms).receives<philosopher_t::msg_stop_thinking>(*philosopher).store_state_name(*philosopher,"philosopher");scenario.define_step("take_left").receives<msg_take>(*fork);scenario.define_step("left_taken").receives<msg_taken>(*philosopher).store_state_name(*philosopher,"philosopher");scenario.define_step("take_right").receives<msg_take>(*fork);scenario.define_step("right_taken").receives<msg_taken>(*philosopher).store_state_name(*philosopher,"philosopher");scenario.define_step("stop_eating").not_before_than(250ms).receives<philosopher_t::msg_stop_eating>(*philosopher).store_state_name(*philosopher,"philosopher");scenario.define_step("return_forks").receives<msg_put>(*fork).receives<msg_put>(*fork);autoresult=scenario.run_for(1s);REQUIRE(result.completed());REQUIRE("wait_left"==result.stored_state_name("stop_thinking","philosopher"));REQUIRE("wait_right"==result.stored_state_name("left_taken","philosopher"));REQUIRE("eating"==result.stored_state_name("right_taken","philosopher"));REQUIRE("thinking"==result.stored_state_name("stop_eating","philosopher"));}
Здесь проверяется сценарий, когда агент-философ получает в свое распоряжение обе вилки.
Для этого создается тестовая кооперация, в которую входит нормальный агент-философ и имитатор агента-fork. Этот имитатор всегда отсылает в ответ msg_taken, т.е. имитирует поведение свобойдной вилки.
В этом сценарии можно обратить внимание на следующие вещи.
Во-первых, конструкция not_before_than на шагах "stop_thinking" и "stop_eating". Эта конструкция указывает, что для нормального выполнения шага требуется, чтобы прошло указанное время после предыдущего шага (или после начала работы сценария для случая "stop_thinking").
Во-вторых, отсутствие конструкции impact в шагах сценария. Т.е. здесь сценарий только проверяет реакцию агента, но сам на агента не воздействует.
В-третьих, на последнем шаге ожидается поступление двух сообщений msg_put.
Last edit: Yauheni Akhotnikau 2018-12-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Результаты нескольких итераций были опубликованы в блоге eao197:
Первый набросок
Второй набросок
Но в комментариях блога неудобно приводить и обсуждать фрагменты кода. Поэтому обсуждение переносится сюда.
Новые варианты будут добавляться в комментарии к этой теме.
Вариант от 2018.12.03.
Пример теста:
Вызов
delivery_case(agent)
говорит о том, что будет проверяться проверка того, как агентagent
будет реагировать на доставку сообщения. Этот вызов возвращает специальный объект-билдере, который позволяет настроить тестовый случай.Метод
.on_message<T>
говорит, что если агент получает сообщение типаT
, то агент должен:on_message
лямбда, в которой выполняются тестовые проверки.В принципе, методов
.on_message
на один тестовый случай можно навесить несколько. Это может потребоваться, если агент в каком-то своем состоянии обрабатывает несколько разных сообщений.Метод
time_limit
указывает, что тестовый случай должен отработать за 1 секунду. Если в это время не уложились, то тестовый случай будет объявлен проваленным.Метод
impact<Msg>
указывает, что перед проверкой тестового случая нужно отослать указанное сообщение.Метод
perform
заставляет отработать то, что было задано в объекте-билдере. Т.е., на агента вешаются специальные перехватчики сообщений, которые будут контролировать обработку сообщений, заданных вon_message
. Затем, если использовался методimpact
, будет отослано стартовое сообщение. После чегоperform
будет ждать не болееtime_limit
пока агент не получит одно из указанных вon_message
сообщений.perform
может переименовать вrun
?Ну или в
check
. Это пока набросок. Менять можно в любую сторону.В этом подходе понятно, как ловить отправленные агенту сообщения. А вот как проверять то, что агент игнорирует сообщения... Вот это пока вопрос :(
А проверить есть ли в текущем состоянии у агента подписка на "сообщение" возможно? Была например so_has_subscription(..)?
В общем случае подразумевается, что агенты должны быть "тестируемыми".
Т.е. разработчик должен позаботиться о наличии обратной связи, которую можно в тестах проверить. Например заложить специальную public функцию, которую можно проверить..
Last edit: Pavel 2018-12-03
Да, можно использовать so_has_subscription().
Но хотелось бы иметь возможность записать как-то так, например:
Ну и не всегда можно полагаться на white box тестирование, иногда может быть полезно написать тесты и на чужого агента, доступа к потрохам которого нет.
Так а внутри
should_ignore
нельзя использоватьso_has_subscription
для проверки?Last edit: Pavel 2018-12-03
Я не уверен, что для
should_ignore
вообще есть смысл делать какие-то дополнительные проверки. Т.е. повесить лямбду с проверками наon_message
-- в этом я смысл вижу. А вот в такой же лямбде дляshould_ignore
пока не вижу.Но если на
should_ignore
таки навешивать лямбду, то в этой лямбде можно будет вызвать любой публичный метод у агента.Видимо это я не совсем понял цель
should_ignore
. Я так понимал, что это проверк что указанное сообщение "не пришло" (т.е. тут даже обработчик не навешивается, т.к. нет как такогого события)Это проверка для случая, когда у агента, скажем, два состояния. В одном он сообщение обрабатывает, в другом нет. Проверка
should_ignore
проверяет, что когда агент во втором состоянии, то он действительно не обрабатывает сообщение.Вот возьмем тот же пример с агентом fork. Изначально он находится в st_free. Следовательно, сообщение msg_put (положить) должно быть проигнорировано. Затем он переходит в st_taken. В этом состоянии на msg_put агент должен среагировать и перейти в st_free. Если затем еще раз отослать msg_put, то агент должен его вновь проигнорировать.
Такое поведение мы можем протестировать следующей последовательностью:
Кстати на тему проверок "не поменял состояние", "не послал сообщение" и т.п. проверка вида "НЕ сделал что-то" возможно требует своего отдельного
time_limit
, а точнееtime_hold
. Время в течение которого "не происходит проверерямое событие". Чтобы не было так:послали агенту сообщение
проверили, что он "НЕ послал в ответ" какое-то сообщение
* а в следующее мгновенье после нашей проверки, он его послал (ведь у нас тут "асинхронщина")
или "проверили, что он не перешёл в новое состояние", а в следющее мгновенье, он перешёл.
А если мы говорим
should_ignore<msg_put>(...).time_hold(2s)
то избавляемся от этого.Вот тут я не понимаю сценария, в котором это может быть. Можно пример, где
time_hold
был бы полезен?Когда нужно проверить, что агент среагировал "НЕ раньше чем" через N секунд. Например, мы посылаем агенту какое-то сообщение и должны проверить, что агент посылает ответное "Не раньше" чем через 2 секунды. Т.е. это проверка, как агент реализует какую-то "задержку".
Как-то все еще не догоняю :(
Ну возможно для акторной архитектуры это просто редкий (а может действительно ненужный case).
Предположим агент по своей логике получив сообщение X должен подождать 5 сек и послать сообщение Y (или сменить состояние). Т.е. по приходу сообщения он скорее всего сделает
so_5::send_delayed
на 5 сек, а потомsend<Y>(..)
. Вот чтобы проверить это, мы можем (если можем) либо проверить что он заказал таймер, либо подождать 5 сек (time_hold
) и убедиться, что в течение этого времени агент не посылал сообщение Y.Как то так )
Другой более конкретный (близкий мне) пример из АСУ. Предположим происходит какое-то событие, если в течение заданного времени оператор не реагирует, то мы должны сформировать какое-то сообщение и выполнить какие-то действия.
hold_time
позволяет нам провести проверку. что агент действительно подождал положенное время и только после этого предпринял какие-то действия.Мне кажется, что здесь какой-то другой сценарий уже рассматривается. Пока что речь шла о том, чтобы, грубо говоря, ткнуть в агента палкой и посмотреть, как он ответит.
А здесь явно другая ситуация. Агент в какой-то момент решает что-то сделать. И мы должны проверить и тот факт, что агент это что-то сделал. И тот факт, что агент решил сделать это вовремя.
До этого руки пока не дошли. И, есть сомнение, что это достижимо с помощью
delivery_check
.Мне думается, что здесь можно рассмотреть две ситуации.
Первая. Мы отсылаем сообщение агенту, от выставляет сам себе таймерную заявку и нам нужно убедится, что когда таймерная заявка сработает, то агент должным образом на нее среагирует. Тогда с использованием
delivery_check
может получится что-то вроде:Вторая. Мы воздействуем на какого-то агента, тот еще на кого-то, тот еще на кого-то и, в конце-концов, через какое-то время наш агент должен среагировать на отложенное сообщение. В этом случае мы, наверное, просто запишем вот так:
Вот тут как раз "ошибка". Тут проверяется, что он среагирует не более чем за N секунд
time_limit
, а я хотел проверить, что не ранее, чем черезtime_hold
. Т.е., что в течение этого времени он гарантированно НЕ будет ничего делать (например не сменит состояние или не пошлёт определённое сообщение). И для такой проверки требуется отдельный "синтаксис" или название.. Напримерcheck_hold<msg>(...).time_hold(..)
илиcheck_freeze<msg>()
ну или что-то такое..Last edit: Pavel 2018-12-04
Скорее всего, тут нужно ограничивать время и сверху, и снизу. Например:
Да. Хорошая мысль! Так понятнее замысел.
Разве что, тут подразумевается, что должно в итоге придти
some_initial_msg
(если я правильно понял). А хочется иметь возможность проверить, что не приходилоsome_msg
, не обязательно что оно в итоге придёт.Или второй случай, проверка что не менялось состояние агента в течение заданного времени. Т.е. может всё-таки какой-то особы синтаксис
Идея от 2018.12.04.
Суть в том, чтобы внутри unit-теста определить "сценарий", который затем "проигрывается" и результат "проигрыша" сценария затем можно проверить отдельными конструкциями REQUIRE и/или ASSERT.
Сценарий состоит из шагов. Шаги должны вполняться последовательно и каждый следующий шаг должен произойти только после исполнения предыдущего шага.
Предполагается, что каждый шаг -- это действие с реакцией на него. В результате реакции могут возникать другие действия, вот эти другие действия и будут выполняться на последующих шагах.
В двух последующих комментариях будут показаны наброски, как посредством сценариев можно протестировать агентов
fork_t
иphilosopher_t
из примера dining_philosophers.Вот так может выглядеть тестирование агента
fork_t
:Сперва создается тестовая кооперация, в которую входит нормальный агент
fork_t
и имитация агента-философа. Эта имитация необходима для того, чтобыa) иметь mbox, на который будут отсылаться ответные сообщения и
b) можно было контролировать сам факт пришествия ответных сообщений.
Сценарий определяет несколько воздействий:
Для того, чтобы проверить факты изменения состояния мы предписываем соответствующим шагам сохранить у себя имя текущего состояния агента (состояние определяется после того, как агент закончит обработку сообщения, указанного в
receives
).Сценарий проигрывается и затем то, что было сохранено в сценарии можно проверить посредством REQUIRE. Т.е. все assertions выполняются уже после того, как сценарий отработал.
Вот так может выглядеть один тестовый случай для агента
philosopher_t
:Здесь проверяется сценарий, когда агент-философ получает в свое распоряжение обе вилки.
Для этого создается тестовая кооперация, в которую входит нормальный агент-философ и имитатор агента-fork. Этот имитатор всегда отсылает в ответ
msg_taken
, т.е. имитирует поведение свобойдной вилки.В этом сценарии можно обратить внимание на следующие вещи.
Во-первых, конструкция
not_before_than
на шагах "stop_thinking" и "stop_eating". Эта конструкция указывает, что для нормального выполнения шага требуется, чтобы прошло указанное время после предыдущего шага (или после начала работы сценария для случая "stop_thinking").Во-вторых, отсутствие конструкции
impact
в шагах сценария. Т.е. здесь сценарий только проверяет реакцию агента, но сам на агента не воздействует.В-третьих, на последнем шаге ожидается поступление двух сообщений
msg_put
.Last edit: Yauheni Akhotnikau 2018-12-04