Возможно ли издеваться/фальсифицировать, чтобы сделать пропущенный замок причиной неудачного теста?

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

Теперь я пытаюсь unit test его. Одна важная вещь, которую я хотел бы использовать unit test, - это поведение блокировки, чтобы обеспечить ее правильность. Тем не менее, я никогда не видел, чтобы это было сделано, поэтому я не уверен, как это сделать. Кроме того, я знаю, что могу просто использовать кучу нитей, чтобы бросить вещи на стене, но с этим типом теста нет никакой гарантии, что он потерпит неудачу, когда это будет неправильно. Это зависит от ОС с поведением потока.

Какие существуют способы обеспечения правильности моего поведения блокировки с помощью модульных тестов?

Ответ 1

Я не думаю, что с помощью Dictionary вы можете достичь надежных тестов - ваша цель состоит в том, чтобы сделать 2 вызова для параллельной работы, что не произойдет надежно.

Вы можете попробовать создать пользовательский словарь (т.е. получить обычный) и добавить обратные вызовы для всех методов Get/Add. Затем вы сможете откладывать вызовы в 2-х потоках по мере необходимости... Вам потребуется отдельная синхронизация между двумя потоками, чтобы ваш тестовый код работал так, как вы хотите, а не в тупик все время.

Ответ 2

Блокировка - это просто деталь реализации. Вы должны макетировать само условие гонки и проверить, не потеряли ли данные данные в тесте.

Это будет полезно, если вы решите изменить реализацию своего словаря и использовать другие примитивы синхронизации.

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

Ответ 3

Я в основном закончил работу над тем, что сказал @Alexei. Чтобы быть более конкретным, это то, что я сделал:

  • Сделайте PausingDictionary, который в основном просто имеет обратный вызов для каждого метода, но в противном случае просто переходит в обычный словарь
  • Аннотация мой код (с использованием DI и т.д.), чтобы я мог использовать PausingDictionary вместо обычного словаря при тестировании
  • В моем unit test добавлено два ConcurrentDictionaries. Один называется "Доступ", а один называется "GoAhead". Ключом к ним является комбинация "action"+Thread.GetHashCode().ToString() (где действие различно для каждого обратного вызова)
  • Инициализировал все значение false и добавил некоторые методы расширения, чтобы упростить работу с ним.
  • Установите обратные вызовы словаря, чтобы установить Accessed для потока в true, но он будет ждать в обратном вызове, пока GoAhead не будет истинным.
  • Настроил два потока из unit test. Один поток будет иметь доступ к словарю, но поскольку GoAhead является ложным для этого потока, он будет сидеть там. Второй поток также попытался бы получить доступ к словарю
  • У меня было бы утверждение, что Accessed для этого потока ложно, потому что мой код должен блокировать его.

Там немного больше, чем это. Мне также нужно было бы издеваться над IList, но я не думаю, что буду. Эти модульные тесты, хотя и ценные, определенно не самые легкие в мире, чтобы писать. Помимо кода установки и реализации поддельного интерфейса и т.д., Каждый тест заканчивается примерно 25 строками неконфигурированного кода. Блокировка жесткая. Доказательство того, что ваша блокировка эффективна, еще сложнее. Удивительно, но такой шаблон может позволить вам протестировать практически любой сценарий. Но, это очень многословие и не делает для хороших тестов

Итак, несмотря на то, что трудно писать тесты, это работает отлично. Когда я удаляю блокировку, последовательно терпит неудачу, и когда я добавляю обратно блокировку, она последовательно проходит.

Edit:

Я думаю, что этот метод "управления чередованием" потоков также позволит проверить безопасность потоков, учитывая, что вы пишете тест для каждого возможного чередования. С некоторым кодом это было бы невозможно, но я просто хочу сказать, что это никоим образом не ограничивается только кодом блокировки. Вы можете сделать то же самое, чтобы последовательно дублировать потокобезопасный сбой, например foo.Contains(x), а затем var tmp=foo[x]