Весной перезапуск StateMachine, кажется, для кэширования последнего выполнения - PullRequest
1 голос
/ 10 апреля 2020

Я настроил Spring State Machine со следующими состояниями и переходами:

IDLE (начальный) -> STARTED -> PRE_SNAPSHOT (действие предварительного снимка) -> SNAPSHOT (действие снимка) -> IDLE

Вот моя конфигурация:

@TestConfiguration
@EnableStateMachineFactory
public class StateMachineConfiguration extends StateMachineConfigurerAdapter<String, String> {

    public static final String IDLE = "IDLE";
    public static final String STARTED = "STARTED";
    public static final String PRE_SNAPSHOT = "PRE_SNAPSHOT";
    public static final String SNAPSHOT = "SNAPSHOT";
    public static final String START_EVENT = "START_EVENT";

    @Autowired
    private PreSnapshotActionMock preSnapshotActionMock;

    @Autowired
    private SnapshotActionMock snapshotActionMock;

    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config.withConfiguration().autoStartup(true);
    }

    @Override
    public void configure(StateMachineStateConfigurer<String, String> stateConfigurer) throws Exception {
        stateConfigurer
                .withStates()
                .initial(IDLE)
                .state(STARTED)
                .stateDo(PRE_SNAPSHOT, preSnapshotActionMock)
                .stateDo(SNAPSHOT, snapshotActionMock)
                .end(IDLE);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitionConfigurer) throws Exception {
        transitionConfigurer
                .withExternal().source(IDLE).target(STARTED).event(START_EVENT)
                .and()
                .withExternal().source(STARTED).target(PRE_SNAPSHOT)
                .and()
                .withExternal().source(PRE_SNAPSHOT).target(SNAPSHOT)
                .and()
                .withExternal().source(SNAPSHOT).target(IDLE);
    }

    @Bean
    public StateMachine<String, String> testStateMachine(@Autowired StateMachineFactory<String, String> smFactory) throws Exception {
        return smFactory.getStateMachine();
    }
}

В моем случае я хочу издеваться над длительным выполнением действий, но в отдельных тестовых случаях для каждого. Для этого я использую Mockito AdditionalAnswers.answersWithDelay():

@SpringBootTest
@RunWith(SpringRunner.class)
@Import(StateMachineConfiguration.class)
@ActiveProfiles("local")
public class StateMachineTest {

    @MockBean
    private PreSnapshotActionMock preSnapshotActionMock;

    @MockBean
    private SnapshotActionMock snapshotActionMock;

    @Autowired
    private StateMachine<String, String> testStateMachine;

    @Before
    public void setUp() {

        Answer preSnapshotAnswer = AdditionalAnswers.answersWithDelay(10, invocation -> {
            System.out.println("default pre-snapshot action short execution");
            return null;
        });
        doAnswer(preSnapshotAnswer).when(preSnapshotActionMock).execute(any());

        Answer snapshotAnswer = AdditionalAnswers.answersWithDelay(10, invocation -> {
            System.out.println("default snapshot action short execution");
            return null;
        });
        doAnswer(snapshotAnswer).when(snapshotActionMock).execute(any());

        testStateMachine.start();
    }

    @After
    public void reset() throws InterruptedException {
        testStateMachine.stop();
        testStateMachine.getStateMachineAccessor().withRegion().resetStateMachine(
                new DefaultStateMachineContext<>(IDLE, null, null, null));
        Thread.sleep(250);
    }

    @Test
    //here I mock long execution for pre-snapshot action only
    public void test1() {

        Answer answerWithDelay = AdditionalAnswers.answersWithDelay(10, invocation -> {
            int i = 0;
            while (i < 10) {
                System.out.println("pre-snapshot action from test1 #" + i);
                i++;
                Thread.sleep(10_000);
            }
            return null;
        });
        doAnswer(answerWithDelay).when(preSnapshotActionMock).execute(any());

        StateMachineListenerAdapter<String, String> stateChangeListenerSpy = Mockito.spy(StateMachineListenerAdapter.class);
        testStateMachine.addStateListener(stateChangeListenerSpy);

        Assertions.assertThat(testStateMachine.sendEvent(START_EVENT)).isTrue();

        Mockito.verify(stateChangeListenerSpy, Mockito.timeout(10_000).times(2)).stateEntered(any());
        Mockito.verify(stateChangeListenerSpy, Mockito.timeout(10_000).times(2)).stateExited(any());

        testStateMachine.removeStateListener(stateChangeListenerSpy);
    }

    @Test
    //here I mock long execution for snapshot action only
    public void test2() {

        Answer answerWithDelay = AdditionalAnswers.answersWithDelay(10, invocation -> {
            int i = 0;
            while (i < 10) {
                System.out.println("snapshot action from test2 #" + i);
                i++;
                Thread.sleep(10_000);
            }
            return null;
        });
        doAnswer(answerWithDelay).when(snapshotActionMock).execute(any());

        StateMachineListenerAdapter<String, String> stateChangeListenerSpy = Mockito.spy(StateMachineListenerAdapter.class);
        testStateMachine.addStateListener(stateChangeListenerSpy);

        System.out.println("Sending start event now");
        Assertions.assertThat(testStateMachine.sendEvent(START_EVENT)).isTrue();

        Mockito.verify(stateChangeListenerSpy, Mockito.timeout(10000).times(3)).stateEntered(any());
        Mockito.verify(stateChangeListenerSpy, Mockito.timeout(10000).times(3)).stateExited(any());

        testStateMachine.removeStateListener(stateChangeListenerSpy);
    }
}

Первый тестовый пример проходит, потому что конечный автомат выполняет длительное выполнение действия предварительного снимка, что и ожидается. Второй тест, однако, не прошел, потому что снова выполняется предварительное создание снимка медленно. Что более интересно, если я изменю время ожидания в test1 с 10_000 на 100, вот журнал из оба теста:

test1:

pre-snapshot action from test1 #0
pre-snapshot action from test1 #1
pre-snapshot action from test1 #2
pre-snapshot action from test1 #3

test2:

Sending start event now
pre-snapshot action from test1 #4
pre-snapshot action from test1 #5
pre-snapshot action from test1 #6
pre-snapshot action from test1 #7
pre-snapshot action from test1 #8
pre-snapshot action from test1 #9
snapshot action from test2 #0

Кажется, что даже если я настроил поведение по умолчанию для действий в методе @Before , в test2 оно вообще не выполняется и вместо этого действие pre-snapshot продолжает свою работу с того места, где оно остановилось в test1 , только после этого конечный автомат переходит в состояние SNAPSHOT.

Если Я настраиваю в точности тот же конечный автомат и контрольные примеры, что и в модульных тестах, вот журнал из test2:

Sending start event now
default pre-snapshot action short execution
snapshot action from test2 #0
pre-snapshot action from test1 #4
pre-snapshot action from test1 #5
pre-snapshot action from test1 #6

, который показывает противоположное (и ожидаемое, на мой взгляд) поведение: сначала действие по умолчанию перед снимком выполняется.

Как вы думаете, это может быть ошибка в State Machine или я что-то здесь упускаю? Я согласен, что это может быть какой-то механизм восстановления, но если это так, то он не очень совместим с насмешками. Первоначально я думал, что причина может быть в Mockito's ResponersWithDelay, но, поскольку я тестировал его с другими объектами, похоже, это не так ...

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