Как написать модульный тест для небезопасной коллекции потоков

Я пишу двусвязный список, используя подход TDD. Этот тип коллекции не является потокобезопасным. Чтобы реализовать интерфейс ICollection, мой класс списка должен иметь несколько общедоступных свойств (включая IsSynchronized и SyncRoot, которые используются для обеспечения потокобезопасного способа использования коллекции). Код этих двух свойств довольно прост:

public bool IsSynchronized { get { return false; } }

private readonly object _syncRoot = new object();
public object SyncRoot { get { return _syncRoot; } }

Вопрос в том, как написать для него правильный юнит-тест. Этот тест также должен проверять правильное и неправильное использование.


person Igor Soloydenko    schedule 23.01.2011    source источник


Ответы (2)


мой класс списка должен иметь несколько общедоступных свойств (включая IsSynchronized и SyncRoot

Это не так. Это восходит к .NET версии 1 и широко считается гигантской ошибкой. Это создало ложное ощущение потокобезопасности, из-за которого у многих программистов возникли серьезные проблемы. Такой класс на самом деле не был потокобезопасным во всех случаях, итерация для одного из них не была. Он был полностью удален из универсальных классов коллекций .NET 2.0. И вы не должны реализовывать ICollection, современный класс коллекции должен быть универсальным и реализовывать ICollection<T>. Это, к сожалению, по-прежнему требует реализации IEnumerable, от которого мы, вероятно, никогда не избавимся. Вы реализуете его с явной реализацией, такие методы не являются публичными.

Второстепенное соображение заключается в том, является ли реализация ICollection‹> хорошей идеей. Связанный список делает такой член, как Count, дорогим, по своей природе он O (n). Вам нужно отслеживать количество элементов в списке отдельно, чтобы сделать его O (1). Это делает класс .NET LinkedList‹>, также являющийся классом коллекции двусвязных списков.

person Hans Passant    schedule 23.01.2011
comment
Я не думаю, что реализация ICollection<T> на самом деле требует реализации ICollection; общая версия наследует только от IEnumerable<T>. - person Dan Tao; 23.01.2011
comment
Спасибо @Dan, ты совершенно прав. Сбит с толку LinkedList‹›, который это реализует. Обновлено. - person Hans Passant; 23.01.2011
comment
› Это создало ложное ощущение потокобезопасности, из-за чего у многих программистов возникли серьезные проблемы. Итак, есть ли какой-либо общий способ (шаблон) сделать коллекцию потокобезопасной или нет? › Класс .NET LinkedList‹›, также являющийся классом коллекции двусвязных списков, делает это. Да, я знаю его. Я только что попытался реализовать собственную коллекцию такого рода с некоторыми дополнительными функциями. - person Igor Soloydenko; 23.01.2011
comment
оффтоп. Извините, я новичок в stackoverflow и пока не знаю, как применить разметку к комментарию. Извините также за мой плохой английский. - person Igor Soloydenko; 23.01.2011
comment
В .net 4 есть потокобезопасные параллельные коллекции, но они обычно имеют API, специально разработанный для потокобезопасных атомарных операций. Но во многих сценариях вы хотите сделать не потокобезопасной саму коллекцию, а класс, построенный поверх нее. - person CodesInChaos; 23.01.2011
comment
@key: еще одна штука — это класс Queue: if (q.Count › 0) obj = q.Dequeue(). Это принципиально небезопасно для потоков, класс Queue не может сделать это безопасным. Кроме полной переделки класса (например, TryDequeue). Не существует общего способа, обычно клиентский код должен использовать класс потокобезопасным способом. Это то, что отстойно в многопоточности: программист, который меньше всего знает о вашем классе, должен выполнять самую тяжелую работу. - person Hans Passant; 23.01.2011
comment
Спасибо за ваши ответы, ребята. @ Ганс, я полностью согласен с тобой по поводу ответственности классов за безопасность потоков. Объект-владелец коллекции отвечает за его потокобезопасное использование. А как насчет более абстрактной ситуации? Как написать модульный тест, чтобы проверить, что какой-то класс можно безопасно использовать в многопоточной среде? Я никогда не видел ничего подобного. Возможно, вы видели или делали такие вещи. - person Igor Soloydenko; 24.01.2011
comment
Очевидно, что вы должны протестировать использование класса клиентским кодом. Если вы хотите проверить, возможно ли вообще сделать это потокобезопасным способом, напишите что-нибудь и протестируйте это. Я не могу вспомнить ни одного случая, когда это было бы совершенно невозможно, поэтому не уверен, что вы действительно доказали что-то стоящее. - person Hans Passant; 24.01.2011
comment
Спасибо за ответ, кстати, такие ответы «плохие новости» обычно не радуют. По-моему, вы обеими ногами твердо стоите на ногах в реальности, мои комплименты. - person Hans Passant; 24.01.2011
comment
Я думаю, что все, что вы сказали в ответ на мой первоначальный вопрос, было хорошим ответом. Более общая проблема тестирования многопоточного класса — это отдельный вопрос, который я должен «затронуть», прежде чем опубликовать его в stackoverflow. (Я просто спросил о некоторых распространенных практиках.) Так что не скромничайте. ;) - person Igor Soloydenko; 24.01.2011

Я бы сказал, что это относится к категории не ваша ответственность. Свойство SyncRoot существует на тот случай, если кто-то, использующий класс вашей коллекции, захочет синхронизировать чтение/запись, предоставляя удобный объект для блокировки — не более того. (На самом деле цель этого свойства была неправильно понята; само свойство часто считалось довольно бесполезным, как Ганс указал.)

Если вы чувствуете, что у вас есть достаточные основания для реализации этого интерфейса, синхронизация все еще полностью выходит за рамки самого вашего типа (как вы указали, задав свойство IsSynchronized, возвращающее false); поэтому вам не нужно проводить модульное тестирование с вашей стороны.

На самом деле, я считаю, что разработчики интерфейса ICollection довольно часто просто игнорируют свойство SyncRoot (устанавливая его в null). Это должно вам кое-что сказать.

Для модульного теста вы можете просто убедиться, что свойство SyncRoot не равно null.

person Dan Tao    schedule 23.01.2011