2016-01-28 5 views
1

У меня есть метод, который создает сущность в таблице базы данных. Я хочу протестировать сценарий, когда метод вызывается дважды в одно и то же время.Есть ли способ проверить способность метода обрабатывать одновременные вызовы?

Метод имеет логику, чтобы избежать такого сценария и не выполнить один из двух запросов, но у меня нет способа подтвердить, работает ли это правильно.

Есть ли способ проверить это, если тест не является шелушащимся?

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

+0

Почему вы не используете стандартные транзакции? – chrylis

+0

@chrylis Можете ли вы подробно рассказать? Вы говорите о спящих транзакциях? – Siddhartha

ответ

1

У меня недавно была аналогичная проблема. Вот точный код, который я написал для решения этой конкретной проблемы:

@Test 
    public void mapVertexToLazyObjectWithSameMapperOnMultipleThreadsAtTheSameTime() { 
    StackVertex vertex = new StackVertex(graph, 1L, "test", Optional.empty()); 
    mockVertex(vertex, NO_EDGES, NO_EDGES, properties("name", "foobar")); 

    VertexEntityMapper<NamedNode> mapper = objectMapper.getMapper(NamedNode.class); 
    NamedNode obj = mapper.mapToObject(vertex); 

    final Boolean[] flags = { false, false }; 
    Runnable run =() -> { 
     if (obj.getName() == null || !obj.getName().equals("foobar")) { 
     flags[0] = true; 
     } 
    }; 

    List<Thread> threads = IntStream.range(0, 10).boxed() 
     .map(i -> new Thread(run)).collect(toList()); 
    threads.forEach(Thread::start); 
    threads.forEach((thread) -> { 
     try { thread.join(); } catch (InterruptedException e) { 
     flags[1] = true; 
     } 
    }); 

    verify(vertexRepository, times(1)).findById(graph.tx(), 1L, Optional.empty()); 
    assertFalse("race condition",  flags[0]); 
    assertFalse("thread interrupted", flags[1]); 
    } 

Это довольно просто, но отлично работает. Если я сделал код небезопасным, этот тест легко нашел проблему с ним. Я немного поиграл с количеством потоков, но обнаружил, что с 10 потоками я всегда мог воспроизвести условия гонки или исключения, вызванные ненадежностью потоков.

+0

Спасибо Lodewijk, это довольно круто! Я сделаю попытку. – Siddhartha

+0

Это кажется излишне сложным способом сказать «вызов метода из кучи одновременных потоков», который не является надежным, если только проверенный код не содержит задержки (и даже тогда он все еще не является надежным). Тот факт, что он «работает», не означает, что он надежный. Он полагается на код, который никогда не заканчивается до того, как все потоки запустились и попытались выполнить их одновременные вызовы. – VGR

+0

@VGR Я думал, что дам другой ответ на этот раз. Вы можете ответить на ваш путь, если вам это нравится. Кроме того, я тестировал это в циклах сотни раз; каждый раз, когда он найдет мою проблему, если я делаю код небезопасным. Это может быть не доказательством, но для меня это достаточно хорошо. –

1

Проверьте тест.

Возьмите копию метода, который должен быть потокобезопасным, и сделайте копию небезопасной. Напишите unit-test, который запускает до дюжины потоков с возможностью выполнения:
1) Имеет общий CountDownLatch.
2) Ожидает, что CountDownLatch достигнет нуля.
3) Вызывает небезопасный метод.

С CountDownLatch вы можете инициировать все потоки, чтобы вызвать небезопасный метод нить более или менее в одно и то же время (все потоки готовы перейти от одной и той же точки в runnable, когда CountDownLatch достигает нуля, но в конечном итоге это вплоть до вашей операционной системы (и оборудования), чтобы решить, что будет выполняться, когда). Оцените результаты: теперь вы должны увидеть расхождение (например, 11 записей, вставленных вместо ожидаемых 12).

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

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

Здесь я не особо подробно остановился, но в вопросе также не так много деталей. В любом случае общая идея состоит в том, чтобы сначала настроить ситуацию, когда она должна идти не так, и использовать автоматическую грубую силу (многие потоки, вызывающие метод много раз в цикле) в сочетании с умным использованием параллельных инструментов, таких как CountDownLatch, чтобы показать тест всегда будет вызывать определенные проблемы. «Умное использование параллельных инструментов» займет некоторое время, понимание и практика (например, мне потребовалось некоторое время, чтобы понять, что после вызова «thread.start()» поток, возможно, еще не начался и что можно использовать CountDownLatch чтобы убедиться, что нить там, где я хочу, чтобы она была).

Отказ от ответственности: эти «проверенные» тесты не поймут все проблемы параллелизма (вы проверяете только то, что, по вашему мнению, может потерпеть неудачу, а также есть другие проблемы, например, сломанные Double checked locking), но они будут затвердеть ваш код и принести вероятный проблемы параллелизма на поверхность.

Смежные вопросы