#java #unit-testing #junit #concurrentmodification
Вопрос:
Цель состоит в том, чтобы провести модульный тест для проверки проблемы исключения одновременного изменения ArrayList в методе customerservice.registry(). Я должен запустить этот метод одновременно, чтобы смоделировать это.
Фактический метод в классе CustomerService :
List<Customer> customerData = Lists.newArrayList();
public synchronized void registry(){
....
}
Модульный тест :
public class concurrencyTest {
......
@Test
public void concurrency() throws InterruptedException {
int numberOfThreads = 100;
ExecutorService service = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
AtomicBoolean terminate = new AtomicBoolean(false);
for (int i = 0; i < numberOfThreads; i ) {
service.execute(() -> {
while (!terminate.get()) {
try {
customerservice.registry(CustomerData.class);
} catch (ConcurrentModificationException e) {
endExecution.set(true);
}
latch.countDown();
}
});
}
latch.await();
assertEquals(terminate.get, false);
}
}
Модульный тест, как упоминалось выше, является правильным подходом для этого ?
Ответ №1:
Цель состоит в том, чтобы провести модульный тест для проверки проблемы исключения одновременного изменения ArrayList в методе customerservice.registry(). Я должен запустить этот метод одновременно, чтобы смоделировать это.
Это неверно. Нет необходимости задействовать более одного потока. Параллельный вход ConcurrentModificationException
не означает того, что вы думаете. На самом деле, вовлечение нескольких потоков-один из немногих способов, с помощью которых можно избежать исключения ConcurrentModificationException. Это мешает его проверить.
Вот, запустите это. Он будет выбрасывать комодекс каждый раз, когда:
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.of("A", "B", "C"));
for (String y : list) if (y.equals("A")) list.add("Found!");
}
Тривиальный код, не так ли?
Исключение ConcurrentModificationException просто означает, что произошла такая последовательность событий:
- Какой-то код создал итератор из
collection
(и обратите внимание, чтоfor (Type t : col)
делает это) - Некоторый код изменяет коллекцию, например, добавляя что-то или удаляя что-то.
- Вы каким-либо образом взаимодействуете с итератором.
Для этого нет необходимости привлекать потоки — см. Приведенный выше код, где мы просто изменяем базовый список во время итерации.
ArrayList определяет свое поведение при попытке взаимодействия с одним из нескольких потоков как неопределенное. ArrayList может свободно использовать CoModEx или нет, и вы на самом деле обнаружите, что его поведение зависит от стольких факторов, с таким же успехом это может быть связано с фазой Луны: Это будет зависеть от того, какая песня воспроизводится в вашем музыкальном проигрывателе, в какой операционной системе вы находитесь, был ли код изменен или нет, произошло ли это добавление, чтобы увеличить пропускную способность, и многое другое. Если вы пишете код, который каким-либо образом или в какой-либо форме зависит от поведения типа, такого как ArrayList, при взаимодействии с ним из нескольких потоков, этот код по определению является ошибочным, и все же ошибочным таким образом, что его нельзя надежно проверить. т. Е. ужасно, это превращает ваше программное обеспечение в беспорядок, который все время терпит неудачу и все же не может быть протестирован; ваша команда разработчиков потратит целые недели на поиски диких гусей, чтобы найти эти ошибки.
Нет способа модульного тестирования поведения типов при многопоточном взаимодействии. В лучшем случае модульный тест может привести к сбою. Это не может гарантировать этого, и на самом деле нетрудно написать код, который каждый раз работает на аппаратной платформе A и довольно часто выходит из строя на платформе B.
Не существует хорошего прямого решения для работы с кодом, который нарушил многопоточный код. Просто емкое и в основном бесполезное «ну, не пишите такой код». Есть причина, по которой люди используют БД в качестве единственного средства одновременного состояния (потому что в ней есть абстракции, такие как СЕРИАЛИЗУЕМЫЙ уровень транзакций, повторные попытки и транзакции для работы с ней), а также шины сообщений.
Комментарии:
1. Я согласен с тем, что «Для этого нет необходимости привлекать потоки» . Но проблема в том, что исключение ConcurrentException встречается очень редко в существующей логике, поскольку метод, который обрабатывает/обрабатывает список массивов, не синхронизирован, и несколько потоков одновременно обращаются к нему (одновременно читают и обновляют список). Чтобы смоделировать этот конкретный сценарий, я должен использовать многопоточные модульные тесты
2. Как я уже сказал, вы не можете исправить состояние гонки, написав для него тест. Ты просто.. надо это исправить, слепой. Любой тест на это, скорее всего, НЕ сообщит об ошибке тестирования, даже если в тестируемом коде есть эта ошибка.