Юнит-тесты многопоточки
От: BlackEric http://black-eric.lj.ru
Дата: 07.11.21 14:37
Оценка:
На собеседовании была задача.
C#.
В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
Ну сам класс то я реализовал через lock.

А потом меня попросили написать юнит-тест на это.

Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.
Да и что проверять в тесте?
https://github.com/BlackEric001
Re: Юнит-тесты многопоточки
От: rosencrantz США  
Дата: 07.11.21 15:19
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>А потом меня попросили написать юнит-тест на это.


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

BE>Да и что проверять в тесте?

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

Ничего страшного нет в том, чтобы стартовать потоки в тестах. Есть "юнит" — минимально осмысленная подсистема, поведение которой можно протестировать. Есть "фиксча" — тестовый стенд для этой системы. В вашем случае "фиксча" — это будут вот эти вот потоки, синхронизованные определенным образом.
Re: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 07.11.21 22:37
Оценка: +2
Здравствуйте, BlackEric, Вы писали:

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

Мне решительно кажется, что нет способа не дать вызвать метод, когда есть ссылка на объект. Речь, вероятно, все-таки о "не дать выполнить метод". Т.к. можно ограничить выполнение, но нельзя ограничить вызов.

BE>Ну сам класс то я реализовал через lock.


BE>А потом меня попросили написать юнит-тест на это.


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

Да, стартовать потоки и с помощью синхронизации гарантировать определенную тестом последовательность вызовов.
BE>Да и что проверять в тесте?
То, что правильная последовательность позволяет выполнить все методы, а неправильная приводит к неудаче.
Re: Юнит-тесты многопоточки
От: SkyDance Земля  
Дата: 08.11.21 04:24
Оценка: 4 (1) +1
BE>Как такое тестировать?

Property-based testing — то что вам нужно, ибо у вас уже есть определенное property ("все методы могут быть вызваны только по очереди").
Re[2]: Юнит-тесты многопоточки
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 08.11.21 06:54
Оценка:
Здравствуйте, samius, Вы писали:

S>Мне решительно кажется, что нет способа не дать вызвать метод, когда есть ссылка на объект. Речь, вероятно, все-таки о "не дать выполнить метод". Т.к. можно ограничить выполнение, но нельзя ограничить вызов.


Да вроде есть — прокси-объект который владеет реальным объектом, реализующим RunFirst, RunSecond, RunThird. Я не большой специалист в C#, но мне думается что до кучи это будет единственный способ покрыть нетагивными тестами подобный сценарий поведения. То есть реализовать мок для реального объекта и проверить что прокси гарантирует очерёдность вызовов.
Re[3]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 08:19
Оценка:
Здравствуйте, kaa.python, Вы писали:

KP>Да вроде есть — прокси-объект который владеет реальным объектом, реализующим RunFirst, RunSecond, RunThird. Я не большой специалист в C#, но мне думается что до кучи это будет единственный способ покрыть нетагивными тестами подобный сценарий поведения. То есть реализовать мок для реального объекта и проверить что прокси гарантирует очерёдность вызовов.


Если бы ссылки на объект у вызывающего не было, то да, прокси не позволит вызвать метод объекта. Но по условию у вызывающего есть ссылка на сам объект.
Re: Юнит-тесты многопоточки
От: gyraboo  
Дата: 08.11.21 08:41
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>На собеседовании была задача.

BE>C#.
BE>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
BE>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
BE>Ну сам класс то я реализовал через lock.

BE>А потом меня попросили написать юнит-тест на это.


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

BE>Да и что проверять в тесте?

Имхо, поскольку многопоточка — это случайный процесс, то нужны специальные средства тестирования, которые бы гарантированно проверили все взаимные варианты, чтобы отловить рейс кондишны и прочие многопоточыне глюки.
В джаве для таких целей есть несколько библиотек, например https://github.com/openjdk/jcstress
Но я на практике её не применял, т.к. она очень долго выполняет проверки. Есть коммерческие варианты, вроде как более удобные для реального применения.
Re[4]: Юнит-тесты многопоточки
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 08.11.21 08:51
Оценка:
Здравствуйте, samius, Вы писали:

S>Если бы ссылки на объект у вызывающего не было, то да, прокси не позволит вызвать метод объекта. Но по условию у вызывающего есть ссылка на сам объект.


Что-то похожее никак не противоречит условиям задачи (это C++, но на C# будет похоже кмк)

class Proxy
{
    class Impl
    {
    public:
        void RunFirst() {}
        void RunSecond() {}
        void RunThird() {}
    };

public:
    void RunFirst() { impl_.RunFirst(); }
    void RunSecond() { impl_.RunSecond(); }
    void RunThird() { impl_.RunThird(); }

protected:
    Impl impl_;
};


А потом просто для Impl делаешь мок и тестируешь поведение оболочки.
Отредактировано 08.11.2021 9:07 kaa.python . Предыдущая версия .
Re: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 10:39
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>На собеседовании была задача.

BE>C#.
BE>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
BE>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
BE>Ну сам класс то я реализовал через lock.

Ну да, конечный автомат (т.е. некое общее состояние, возможно enum) + lock. Хотя можно было бы и через waithandl'ы
как-то сделать -- т.е. runfirst будит handle для runsecond и т.д. Но тут реально можно больше проблем огрести,
чем через lock. Хотя логика возможно будет более прямолинейная.

BE>А потом меня попросили написать юнит-тест на это.

BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.
BE>Да и что проверять в тесте?

Да тут вообще можно перебором (3!) все варианты запустить через соотв. thread.sleep. Т.е. завести массив целых
типа sleep = int[] {1000,2000,3000} и все перестановки для thread.sleep для каждого потока.
Т.е. генерируйте тройки перестановок и засыпайте потоки на соотв. время перед запуском соотв. метода.
Кодом людям нужно помогать!
Re[5]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 12:31
Оценка:
Здравствуйте, kaa.python, Вы писали:

KP>Здравствуйте, samius, Вы писали:


S>>Если бы ссылки на объект у вызывающего не было, то да, прокси не позволит вызвать метод объекта. Но по условию у вызывающего есть ссылка на сам объект.


KP>Что-то похожее никак не противоречит условиям задачи (это C++, но на C# будет похоже кмк)


Если внимательно почитать условие, то у клиента есть ссылка на объект, чьи методы он вызывает. И если этот объект будет Proxy, то придотвратить вызов Proxy::RunThird() мы не можем. А вызывать Impl::RunThird() клиент не должен, т.к. у него нет на него ссылки.
Если же слово "вызвать" заменить на "выполнить", то да, прокси подойдет, безусловно. Так же как и lock внутри реализации методов объекта.
Я не утверждаю, что прокси — плохое решение. Я утверждаю, что прокси — это решение задачи с формулировкой "выполнить" методы.

KP>А потом просто для Impl делаешь мок и тестируешь поведение оболочки.
Re[2]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 12:54
Оценка: 4 (1)
Здравствуйте, Sharov, Вы писали:

S>Да тут вообще можно перебором (3!) все варианты запустить через соотв. thread.sleep. Т.е. завести массив целых

S>типа sleep = int[] {1000,2000,3000} и все перестановки для thread.sleep для каждого потока.
S>Т.е. генерируйте тройки перестановок и засыпайте потоки на соотв. время перед запуском соотв. метода.

Достаточно будет просто из теста запускать поток с соответствующим ThreadStart-ом.
факториал тут явно избыточен, т.к. проверить, что методы 2 и 3 не запускаются без предварительного вызова метода 1 можно и не создавая дополнительных потоков вовсе.
т.е.
1) в потоке теста вызываем методы 2 и 3, что бы убедиться, что они не отработают.
2) в левом потоке вызываем метод 1, после Join пытаемся в потоке теста вызвать 3 (должен сфейлиться) и 2 (должен выполниться).
3) в левом потоке вызваем 1, после Join в другом левом 2, после Join в потоке теста вызываем 3 и проверяем, что он выполнился.

Так мы проверим то, что методы не вызываются в неверной последовательности. Такая совокупность тестов не потребует синхронизации внутри реализации. По хорошему нужно еще проверить на щели в реализации, что бы исключить потенциальные гонки, но условие напрямую не требует этим заниматься. В том виде, как я его понял. Но что бы проверять потенциальные гонки, надо знать дополнительно, считается ли корректным вызов метода 2 более одного раза, если 1 уже выполнен, если 3 уже выполнен, и т.п.? Без ответа на эти вопросы организовать исчерпывающее тестирование не удастся.
Re[3]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 13:44
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>Да тут вообще можно перебором (3!) все варианты запустить через соотв. thread.sleep. Т.е. завести массив целых

S>>типа sleep = int[] {1000,2000,3000} и все перестановки для thread.sleep для каждого потока.
S>>Т.е. генерируйте тройки перестановок и засыпайте потоки на соотв. время перед запуском соотв. метода.

S>Достаточно будет просто из теста запускать поток с соответствующим ThreadStart-ом.

S>факториал тут явно избыточен, т.к. проверить, что методы 2 и 3 не запускаются без предварительного вызова метода 1 можно и не создавая дополнительных потоков вовсе.
S>т.е.
S>1) в потоке теста вызываем методы 2 и 3, что бы убедиться, что они не отработают.
S>2) в левом потоке вызываем метод 1, после Join пытаемся в потоке теста вызвать 3 (должен сфейлиться) и 2 (должен выполниться).
S>3) в левом потоке вызваем 1, после Join в другом левом 2, после Join в потоке теста вызываем 3 и проверяем, что он выполнился.


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

Во варианте выше каждый случай 1)2)3) я бы сделал в виде отдельного теста, раз уж мы явно проверяем некоторые
условия. Что может быть и неплохо.

S>Так мы проверим то, что методы не вызываются в неверной последовательности. Такая совокупность тестов не потребует синхронизации внутри реализации.


Синхронизация где, в тесте или в реализации объетка? В объекте по любому нужна, т.к. до окончания метода1 может начать
работу метод2 и поломать все инварианты.
Кодом людям нужно помогать!
Re[4]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 14:04
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>Я думал одним тестом протестировать все возможные манипуляции с объектом, благо вариантов немного.

S>Т.е. по результатам каждой возможной посл-ти вызовов объект будет в корректном состоянии. Возможно это не совсем то.
Вероятно, нам ведь надо проверить невозможность вызова/выполнения, а не состояние объекта.

S>Во варианте выше каждый случай 1)2)3) я бы сделал в виде отдельного теста, раз уж мы явно проверяем некоторые

S>условия. Что может быть и неплохо.
Это уже детали оформления.

S>>Так мы проверим то, что методы не вызываются в неверной последовательности. Такая совокупность тестов не потребует синхронизации внутри реализации.


S>Синхронизация где, в тесте или в реализации объетка? В объекте по любому нужна, т.к. до окончания метода1 может начать

S>работу метод2 и поломать все инварианты.
В объекте синхронизация нужна, я о том, что предложенный мной подход с потоками и Join-ами не выявит отсутствия синхронизации. Подход со слипами по несколько секунд — тоже. Тест, который потребует синхронизации, должен кэшировать состояние объекта, например, один поток в цикле пытается вызвать метод 2, но фейлится (видимо, ловит исключение) и продолжает цикл. Второй поток параллельно вызыват 1. При наличии синхронизации первый поток должен выйти из цикла после успешного вызова 2. Но этот подход хорош в теории, ведь методы объекта могут быть достаточно противные, что бы состояние объекта выпадало из кэша. Тогда и этот подход не потребует синхронизации.
Re[2]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 08.11.21 14:32
Оценка: 4 (1) +4
Здравствуйте, Sharov, Вы писали:

S>Да тут вообще можно перебором (3!) все варианты запустить через соотв. thread.sleep. Т.е. завести массив целых

S>типа sleep = int[] {1000,2000,3000} и все перестановки для thread.sleep для каждого потока.
S>Т.е. генерируйте тройки перестановок и засыпайте потоки на соотв. время перед запуском соотв. метода.
За thread.sleep в авто-тестах я бы сразу no hire делал. Оно не только работает ужасно долго делая билд тормозным солидным, но и иногда ломается, когда вдруг где-то что-то засвопит при очередном тестовом прогоне и завалит билд.

Надо просто блокировать|разблокировать треды в нужном порядке. Это я исхожу из моего понимания задания — мол, second должен лочиться до тех пор пока не вызван first. Иначе не очень ясно что должен делать second в момент когда first уже работает.
Если же фейлы нужны, то непонятно зачем с тредами тестировать, всё гораздо проще. Использовать какой-нибудь потокобезопасный примитив для изменения state, да и всё:
class
{ 
    volatile bool firstDone;


    void doFirst() {
        doFirstStuff();
        firstDone = true;
    }

    void doSecond() {
        if(!firstDone) throw new IllegalState();
        doSecondStuff();
    }
  
}

Неужели действительно так _надо_ писать тест на наличие volatile?

Честно говоря, я себе слабо представяю как написать _надёжный_ тест, который тестирует что переменная помечена volatile.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 08.11.2021 14:36 · . Предыдущая версия .
Re[3]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 17:47
Оценка:
Здравствуйте, ·, Вы писали:

S>>Да тут вообще можно перебором (3!) все варианты запустить через соотв. thread.sleep. Т.е. завести массив целых

S>>типа sleep = int[] {1000,2000,3000} и все перестановки для thread.sleep для каждого потока.
S>>Т.е. генерируйте тройки перестановок и засыпайте потоки на соотв. время перед запуском соотв. метода.
·>За thread.sleep в авто-тестах я бы сразу no hire делал. Оно не только работает ужасно долго делая билд тормозным солидным, но и иногда ломается, когда вдруг где-то что-то засвопит при очередном тестовом прогоне и завалит билд.

Справедливо, ибо недетерминированность конечно присутствует, т.е. зависит от окружения -- что там еще на билд машине может быть.
Насчет времени выполнения -- можно уменьшить в 10 или даже 100 раз. Главное, чтобы по очереди.
Также не ясно как своп может что-то поломать в этом сценарии ?


·>Надо просто блокировать|разблокировать треды в нужном порядке. Это я исхожу из моего понимания задания — мол, second должен лочиться до тех пор пока не вызван first. Иначе не очень ясно что должен делать second в момент когда first уже работает.



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

·>Честно говоря, я себе слабо представяю как написать _надёжный_ тест, который тестирует что переменная помечена volatile.


Тут едва ли тест поможет, скорее cr.
Кодом людям нужно помогать!
Re[5]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 18:06
Оценка:
Здравствуйте, samius, Вы писали:



S>>Синхронизация где, в тесте или в реализации объетка? В объекте по любому нужна, т.к. до окончания метода1 может начать

S>>работу метод2 и поломать все инварианты.
S>В объекте синхронизация нужна, я о том, что предложенный мной подход с потоками и Join-ами не выявит отсутствия синхронизации. Подход со слипами по несколько секунд — тоже. Тест, который потребует синхронизации, должен кэшировать состояние объекта, например, один поток в цикле пытается вызвать метод 2, но фейлится (видимо, ловит исключение) и продолжает цикл. Второй поток параллельно вызыват 1. При наличии синхронизации первый поток должен выйти из цикла после успешного вызова 2. Но этот подход хорош в теории, ведь методы объекта могут быть достаточно противные, что бы состояние объекта выпадало из кэша. Тогда и этот подход не потребует синхронизации.

Не понял зачем парится с кэшированием, если у нас есть lock?

А как на практике тестируют lock-free структуры данных и соотв. код? Просто в данном случае, если ТС выбрал lock,
то про потоки можно забыть, т.е. просто тестировать соотв. состояние. А если volatile -- то
Кодом людям нужно помогать!
Re: Юнит-тесты многопоточки
От: _NN_ www.nemerleweb.com
Дата: 08.11.21 18:25
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>На собеседовании была задача.

BE>C#.
BE>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
BE>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
BE>Ну сам класс то я реализовал через lock.
Тут по заданию не ясно, что должно произойти если вызываем неверный метод ?
Кидаем исключение, зависаем пока не вызовем нужный метод, программа падает или что-нибудь другое ?
Ну а после этого и будет ясно какой тест писать.

BE>А потом меня попросили написать юнит-тест на это.


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

BE>Да и что проверять в тесте?
Тут зависит от того, что мы решили делать.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 08.11.21 19:24
Оценка:
Здравствуйте, Sharov, Вы писали:

S> Справедливо, ибо недетерминированность конечно присутствует, т.е. зависит от окружения -- что там еще на билд машине может быть.

S> Насчет времени выполнения -- можно уменьшить в 10 или даже 100 раз. Главное, чтобы по очереди.
А кто тебе обещал очередность sleep-ов?

S> Также не ясно как своп может что-то поломать в этом сценарии ?

Тред может просыпаться позднее указанного времени. И по большому счёту в любом порядке. Особенно, если ты уменьшишь в 100 раз.

S> ·>Надо просто блокировать|разблокировать треды в нужном порядке. Это я исхожу из моего понимания задания — мол, second должен лочиться до тех пор пока не вызван first. Иначе не очень ясно что должен делать second в момент когда first уже работает.

S> Тогда не понятно, зачем вообще потоки нужны, тем более если исп. lock? Последовательно вызываем методы и проверяем ожидаемый результат.
Ну я вижу такой сценарий. first это некий init, который записывает какие-то нужные данные, а second — это запрос, которому нужны результаты init и он их может ожидать из другого треда.

S> Тут едва ли тест поможет, скорее cr.

"cr"?
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: Юнит-тесты многопоточки
От: Teolog  
Дата: 08.11.21 19:39
Оценка:
S>А как на практике тестируют lock-free структуры данных и соотв. код? Просто в данном случае, если ТС выбрал lock,
S>то про потоки можно забыть, т.е. просто тестировать соотв. состояние. А если volatile -- то

Это если все гарантированно правильно работает, но если все основано на вере, то и тест не нужен, если не ставиться цель написать тест который всегда зеленый.
А так- пустить 100 групп потоков по 3 с рандомным слипом, если до финиша добралась хотя бы одна группа с неправильной последовательностью срабатывания, не добралась хотя бы одна группа с правильной, и выскочили любые исключения кроме специально добавленных, то тест провален.
Ах да, еще это надо сделать на x86,x64,ARM , в разных средах.
Многопоточность.
Re[5]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 20:23
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, Sharov, Вы писали:


S>> Справедливо, ибо недетерминированность конечно присутствует, т.е. зависит от окружения -- что там еще на билд машине может быть.

S>> Насчет времени выполнения -- можно уменьшить в 10 или даже 100 раз. Главное, чтобы по очереди.
·>А кто тебе обещал очередность sleep-ов?

Формально -- никто, но с большой вероятность, тот что 1000мс спал проснется раньше, чем тот что 3000мс. Что и нужно.

S>> Также не ясно как своп может что-то поломать в этом сценарии ?

·>Тред может просыпаться позднее указанного времени. И по большому счёту в любом порядке. Особенно, если ты уменьшишь в 100 раз.

Скорее всего они все проснутся позднее, т.е. будет сдвиг у всех.

Я согласен, что идея так себе, но для наколенного теста сойдет. А для посерьезнее надо думать.


S>> Тут едва ли тест поможет, скорее cr.

·>"cr"?

code review
Кодом людям нужно помогать!
Re[7]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 20:25
Оценка: +1
Здравствуйте, Teolog, Вы писали:


T>А так- пустить 100 групп потоков по 3 с рандомным слипом, если до финиша добралась хотя бы одна группа с неправильной последовательностью срабатывания, не


Выше сказали, что sleep это no hire. Просто это по сути вероятностный тест, ибо в нем заложена некая недетерминированность.
Кодом людям нужно помогать!
Re[6]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 20:48
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, samius, Вы писали:




S>>>Синхронизация где, в тесте или в реализации объетка? В объекте по любому нужна, т.к. до окончания метода1 может начать

S>>>работу метод2 и поломать все инварианты.
S>>В объекте синхронизация нужна, я о том, что предложенный мной подход с потоками и Join-ами не выявит отсутствия синхронизации. Подход со слипами по несколько секунд — тоже. Тест, который потребует синхронизации, должен кэшировать состояние объекта, например, один поток в цикле пытается вызвать метод 2, но фейлится (видимо, ловит исключение) и продолжает цикл. Второй поток параллельно вызыват 1. При наличии синхронизации первый поток должен выйти из цикла после успешного вызова 2. Но этот подход хорош в теории, ведь методы объекта могут быть достаточно противные, что бы состояние объекта выпадало из кэша. Тогда и этот подход не потребует синхронизации.

S>Не понял зачем парится с кэшированием, если у нас есть lock?


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

S>А как на практике тестируют lock-free структуры данных и соотв. код?

Если честно, то не занимался разработками таких структур в промышленных масштабах, а те, что разрабатывал — исходил из того, что они построены на более простых примитивах со своими гарантиями, например, CAS-update. Оно работало и мои тесты походили на то, как если бы я использовал бы обычную структуру, обернутую локами.

S>Просто в данном случае, если ТС выбрал lock,

S>то про потоки можно забыть, т.е. просто тестировать соотв. состояние. А если volatile -- то
про volatile — отдельный разговор. Мы не знаем полных условий, могут ли методы выполняться повторно, например, можно ли 1 выполнять после 2. Не зная этого, сложно спрогнозировать, что именно будем писать в volatile. И очень может быть, что вызванные параллельно методы 1 и 2 приведут к непредсказуемому результату, т.е. можем стереть запись о вызове 2, а можем не стереть ее. От этого будет зависеть, пройдет ли 3. Я бы тут volatile-у не доверился. Но CAS-update выглядит убедительным.

Можно ли забывать про потоки, если автор выбрал lock... — здесь все просто. Нельзя. Автор реализации мог выбрать lock и забыть вставить его в каком-то месте, например, по невнимательности. Хороший тест тестирует реализацию, а не идею о реализации. Идея может быть хороша и давать гарантии, а код — не обязательно.
Re[6]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 08.11.21 20:49
Оценка:
Здравствуйте, Sharov, Вы писали:

S>·>А кто тебе обещал очередность sleep-ов?

S>Формально -- никто, но с большой вероятность, тот что 1000мс спал проснется раньше, чем тот что 3000мс. Что и нужно.
Зачем писать код "с большой (с какой именно, кстати?) вероятностью", когда можно писать код со 100% вероятностью?
Ведь если билд падает с маленькой вероятностью, это создаёт большие проблемы. А ещё представь у тебя есть много таких тестов — и каждый из них падает пусть и с маленькой вероятностью...

S>>> Также не ясно как своп может что-то поломать в этом сценарии ?

S>·>Тред может просыпаться позднее указанного времени. И по большому счёту в любом порядке. Особенно, если ты уменьшишь в 100 раз.
S>Скорее всего они все проснутся позднее, т.е. будет сдвиг у всех.
Мамой клянёшься?

S>Я согласен, что идея так себе, но для наколенного теста сойдет. А для посерьезнее надо думать.

Да что тут думать? Тестовый код синхронизируются ровно теми же механизмами... Запускаешь три тестовых треда, которые ждут отлочиваешь их в нужном порядке.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[3]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 20:59
Оценка:
Здравствуйте, ·, Вы писали:

·>Неужели действительно так _надо_ писать тест на наличие volatile?

в каких-то случаях надо, в каких-то — нет. Один написал volatile и не написал тест, второй увидел volatile и стер его. Тесты идут? Идут. "Я же говорил, что volatile не нужен!". И никто не хватится этой volatile, особенно, если проект большой.

·>Честно говоря, я себе слабо представяю как написать _надёжный_ тест, который тестирует что переменная помечена volatile.

А и не нужно писать тест о том, что переменная помечена. Тестировать надо контракт, а не реализацию. Вполне вероятно, что volatile поменяют на lock и тест должен работать.
Но да, соглашусь, нахождение гонок в реализации очень сложно, по сравнению с обычными юнитами. Потому, в большинстве случаев будет достаточно code review.
Re[4]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 08.11.21 21:32
Оценка: +1
Здравствуйте, samius, Вы писали:

s> ·>Неужели действительно так _надо_ писать тест на наличие volatile?

s> в каких-то случаях надо, в каких-то — нет. Один написал volatile и не написал тест, второй увидел volatile и стер его. Тесты идут? Идут.
В этом и беда... Что очень тяжело добиться того, чтобы тесты таки перестали идти. Случайные sleep вообще никак не помогут, т.к. будут создавать membar и без volatile.

s> ·>Честно говоря, я себе слабо представяю как написать _надёжный_ тест, который тестирует что переменная помечена volatile.

s> А и не нужно писать тест о том, что переменная помечена. Тестировать надо контракт, а не реализацию. Вполне вероятно, что volatile поменяют на lock и тест должен работать.
Это понятно. Но во-первых, в сабже таки _юнит_ тесты, во-вторых, всё равно не ясно как контракт многопоточки тестировать надёжно.

s> достаточно code review.

+1
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.21 22:05
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, samius, Вы писали:


·>В этом и беда... Что очень тяжело добиться того, чтобы тесты таки перестали идти. Случайные sleep вообще никак не помогут, т.к. будут создавать membar и без volatile.

Вот поэтому я и предположил, что "протестируйте это" означает ни что инное, как последовательность вызовов. Но не тесты на гонки.

s>> А и не нужно писать тест о том, что переменная помечена. Тестировать надо контракт, а не реализацию. Вполне вероятно, что volatile поменяют на lock и тест должен работать.

·>Это понятно. Но во-первых, в сабже таки _юнит_ тесты, во-вторых, всё равно не ясно как контракт многопоточки тестировать надёжно.
Когда у меня потек гонками один виндовый проект, было все настолько неприятно (вешал проводник и даже местами винду), что я предпринял следующее:
обернул штатный Monitor своей утилиткой, которая опционально (в DEBUG, например) писала информацию в Thread.Context о том, что нечто залочено. Таким образом, я для одних интерфейсов мог проверять, что к ним обращается код не из под захвата, для других, что под захватом, стал иметь возможность в ратнайме проверять конкретные последовательности захватов и при обнаружении циклов, которые могли бы дать дедлоки, кидать исключение и писать в лог стеки участников цикла.

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

С Volatile и CASUpdate-ами все сложнее, т.е. не поддается инструментальной диагностике такого уровня.
Re[7]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 22:34
Оценка:
Здравствуйте, samius, Вы писали:

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


Не понял мысль про реализацию -- тест должен тестировать реализацию. Вроде ниже, в самом конце, об этом и написано.
Т.е. почему тест должен работать, если мы подменили реализацию? Если суть теста именно в тестировании реализации?
Меняем реализацию, меняем тест. Иначе смысл в этом тесте?

S>Поэтому, если тест не демонстрирует нам, что для качественной реализации требуется синхронизация, значит этот тест не качественный, или, как минимум, не полный. Он удовлетворяет условию задачи "протестируйте это", подразумевая что в неверной последовательности методы нельзя вызвать/выполнить. Но мы-то понимаем, что без синхронизации реализация потечет при достаточной нагрузке, либо после кибернейтов/слипов, как упоминали выше.


Почему тест должен диктовать реализацию, если речь не о TDD? Почему вопрос о синхронизации должен диктоваться тестом?
Тест должен проверять качество синхронизации, а не ее отсутствие.

S>>Просто в данном случае, если ТС выбрал lock,

S>>то про потоки можно забыть, т.е. просто тестировать соотв. состояние. А если volatile -- то
S>про volatile — отдельный разговор. Мы не знаем полных условий, могут ли методы выполняться повторно, например, можно ли 1 выполнять после 2. Не зная этого, сложно спрогнозировать, что именно будем писать в volatile. И очень может быть, что вызванные параллельно методы 1 и 2 приведут к непредсказуемому результату, т.е. можем стереть запись о вызове 2, а можем не стереть ее. От этого будет зависеть, пройдет ли 3. Я бы тут volatile-у не доверился. Но CAS-update выглядит убедительным.

Может и стоит тестировать состояние, чтобы отфильтровать непредсказуемое состояние? Конкретно для данной задачи я не знаю, мало вводных.

S>Можно ли забывать про потоки, если автор выбрал lock... — здесь все просто. Нельзя. Автор реализации мог выбрать lock и забыть вставить его в каком-то месте, например, по невнимательности. Хороший тест тестирует реализацию, а не идею о реализации. Идея может быть хороша и давать гарантии, а код — не обязательно.


Это да, зависит от гранулярности. Речь идет об одной секции lock, которая хранит инф-ию о вызвавщей ф-ии или
что-то вроде этого.

Вообще, интересная задача. Я тут другую задачку придумал -- как убедиться, не имея исходников и не читая msdn,
что вставка\удаление в List<T> не потоко безопасна? Т.е. получить тест, который всегда бы падал или
проходил. Наверное, все же лучше падал -- т.е. либо исключение, либо неправильное кол-во элементов (сломали инвариант).
Т.е. если бы в эту коллекцию добавили синхронизацию, то тест упал бы.

Ничего лучше чем загрузить потоками со случайной вставкой и удалением я не придумал...
Кодом людям нужно помогать!
Re[7]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 08.11.21 22:38
Оценка:
Здравствуйте, ·, Вы писали:

S>>Я согласен, что идея так себе, но для наколенного теста сойдет. А для посерьезнее надо думать.

·>Да что тут думать? Тестовый код синхронизируются ровно теми же механизмами... Запускаешь три тестовых треда, которые ждут отлочиваешь их в нужном порядке.

Потоки то зачем тогда, и чем время между отлочиваниями отличает от sleep? Разве что заранее известным порядком,
хотя тоже не факт.
Кодом людям нужно помогать!
Re[3]: Юнит-тесты многопоточки
От: SkyDance Земля  
Дата: 09.11.21 03:35
Оценка:
·>За thread.sleep в авто-тестах я бы сразу no hire делал. Оно не только работает ужасно долго делая билд тормозным солидным, но и иногда ломается, когда вдруг где-то что-то засвопит при очередном тестовом прогоне и завалит билд.

Золотые слова, не мальчика но мужа.

К сожалению, за процессом найма автоматически уследить невозможно. Поэтому множество (боюсь что большинство) нанятых таки лепит sleep (и ладно бы только в тесты, так ведь и в код тоже — "избегают race conditions"). Но здесь, к счастью, уже есть решение — lint'ер, который бьет по рукам таких писателей.

И я было уже расслабился, думая "молодец, решил проблему", чтобы через пару месяцев обнаружить в, так сказать, tips & trick guidelines слова типа "поскольку sleep нельзя, делайте waitFor с тем же таймаутом, это линтер не поймает, но результат будет тот же".
Re[3]: Юнит-тесты многопоточки
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 09.11.21 03:54
Оценка:
Здравствуйте, ·, Вы писали:


·>
·>class
·>{ 
·>    volatile bool firstDone;


·>    void doFirst() {
·>        doFirstStuff();
·>        firstDone = true;
·>    }

·>    void doSecond() {
·>        if(!firstDone) throw new IllegalState();
·>        doSecondStuff();
·>    }
  
·>}
·>


Выглядит как так себе идея в контексте задачи. А если после if, но перед throw был вызван doFirst? Всё ж сломается
Re[4]: Юнит-тесты многопоточки
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 09.11.21 04:00
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>К сожалению, за процессом найма автоматически уследить невозможно. Поэтому множество (боюсь что большинство) нанятых таки лепит sleep (и ладно бы только в тесты, так ведь и в код тоже — "избегают race conditions"). Но здесь, к счастью, уже есть решение — lint'ер, который бьет по рукам таких писателей.


В тестах без sleep иногда просто никуда, т.к. или слишком дорого, или слишком криво. Если где-то запускается поток, который должен успеть стартануть и начать что-то обрабатывать, то либо sleep с вероятностью что упадёт, либо ради тестов протягивать какие-то интерфейсы, что не всегда возможно и/или разумно.

Для таких случаев в запускалках тестов есть атрибуты типа flaky. Удобно.
Re[5]: Юнит-тесты многопоточки
От: SkyDance Земля  
Дата: 09.11.21 04:26
Оценка:
KP>В тестах без sleep иногда просто никуда, т.к. или слишком дорого, или слишком криво. Если где-то запускается поток, который должен успеть стартануть и начать что-то обрабатывать, то либо sleep с вероятностью что упадёт, либо ради тестов протягивать какие-то интерфейсы, что не всегда возможно и/или разумно.

Интерфейсы протягивать надо не ради тестов, а для observability/visibility. Чтобы потом, безо всяких тестов, просто знать, что тот самый поток просто еще не запустился, или уже запустился, но сдох на взлете, или подвис, или еще что-то с ним случилось.
Тесты просто кодифицируют это поведение. И вообще, если что-то нужно делать "для тестов", почти гарантированно это что-то нужно будет в какой-то еще момент, вне тестов.

KP>Для таких случаев в запускалках тестов есть атрибуты типа flaky. Удобно.


Это обычно делают в обратную сторону, CI постоянно гоняет stress run, и flaky тесты автоматически помечает, а также требует от автора теста исправить, под угрозой отказа от запуска этого теста в CI.
Re[8]: Юнит-тесты многопоточки
От: Teolog  
Дата: 09.11.21 06:28
Оценка:
S>Выше сказали, что sleep это no hire. Просто это по сути вероятностный тест, ибо в нем заложена некая недетерминированность.
В том то и смысл что не-детерминированность, тогда есть некая вероятность что он все-же нащупает на какой-то итерации тайминг вызовов дающий неверный результат.
Детерминированный тест можно написать если четкая последовательность уже известна, и другой быть просто физически не может.
А тут оно и есть, произвольный вызов с трех разных потоков в произвольной последовательности с произвольным таймингом(хоть все одновременно). Если баг есть, когда нибудь оно словит.
Re[6]: Юнит-тесты многопоточки
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 09.11.21 06:56
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Интерфейсы протягивать надо не ради тестов, а для observability/visibility. Чтобы потом, безо всяких тестов, просто знать, что тот самый поток просто еще не запустился, или уже запустился, но сдох на взлете, или подвис, или еще что-то с ним случилось.


Подобная информация далеко не всегда полезна и в итоге ты будешь усложнять решение исключительно из за тестов. Иногда оно того стоит, иногда нет. Однозначно ответа, как ты это пытаешься представить, тут нет.

SD>Тесты просто кодифицируют это поведение. И вообще, если что-то нужно делать "для тестов", почти гарантированно это что-то нужно будет в какой-то еще момент, вне тестов.

SD>Это обычно делают в обратную сторону, CI постоянно гоняет stress run, и flaky тесты автоматически помечает, а также требует от автора теста исправить, под угрозой отказа от запуска этого теста в CI.

Ты очень любишь обобщать и выдавать своё мнение за единственно правильное
Re[4]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 09.11.21 07:44
Оценка:
Здравствуйте, kaa.python, Вы писали:

KP>Выглядит как так себе идея в контексте задачи. А если после if, но перед throw был вызван doFirst? Всё ж сломается

Ээ.. Суть в том, чтобы дождаться выхода из doFirst. Если doFirst вышел, то можно смело звать doSecond. Пока не вышел — нельзя. Или я не понял твоё возражение|вопрос.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[8]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.11.21 08:53
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>Не понял мысль про реализацию -- тест должен тестировать реализацию. Вроде ниже, в самом конце, об этом и написано.

S>Т.е. почему тест должен работать, если мы подменили реализацию? Если суть теста именно в тестировании реализации?
S>Меняем реализацию, меняем тест. Иначе смысл в этом тесте?
Смысл теста в том, что бы проверять потребительские качества кода, т.е. то, насколько он соответствует спецификации. А как именно выполнена реализация — не теста дело. Может быть там магия работает, главное, что бы поведение было ожидаемым. Да и способен ли тест отличить блокирующую реализацию от локфри, например? Если поведение ничем не отличается.

S>Почему тест должен диктовать реализацию, если речь не о TDD? Почему вопрос о синхронизации должен диктоваться тестом?

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

S>Тест должен проверять качество синхронизации, а не ее отсутствие.

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

S>Может и стоит тестировать состояние, чтобы отфильтровать непредсказуемое состояние? Конкретно для данной задачи я не знаю, мало вводных.

Это уже сильно зависит от реализации, которая зависит от требований, которых мы не знаем

S>Вообще, интересная задача. Я тут другую задачку придумал -- как убедиться, не имея исходников и не читая msdn,

S>что вставка\удаление в List<T> не потоко безопасна? Т.е. получить тест, который всегда бы падал или
S>проходил. Наверное, все же лучше падал -- т.е. либо исключение, либо неправильное кол-во элементов (сломали инвариант).
S>Т.е. если бы в эту коллекцию добавили синхронизацию, то тест упал бы.
Тут сложно то, что обнаружение потокоопасности носит вероятностный характер. Несложно написать тест, способный обнаружить гонку, сложно сделать его детерминированным за конечное время. И все равно можно будет влиять на тест окружением. Например, выполнение на однопоточной машине не даст результата.

S>Ничего лучше чем загрузить потоками со случайной вставкой и удалением я не придумал...

Вопрос в том, сколько элементов нужно вставить и удалить, что бы понять наверняка?
Re[9]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 09.11.21 10:22
Оценка:
Здравствуйте, Teolog, Вы писали:

T> Если баг есть, когда нибудь оно словит.


Представьте масштабы Амазона, там это когда-нибудь будет выстреливать кучу раз в день. Не вариант.
Кодом людям нужно помогать!
Re: Юнит-тесты многопоточки
От: student__  
Дата: 09.11.21 10:40
Оценка: +1
Юнит тесты не предназначены для борьбы с проблемами параллелизма. Если тебе предложили сделать именно это, то им надо обратно в школу, в первый класс.
Re[9]: Юнит-тесты многопоточки
От: Sharov Россия  
Дата: 09.11.21 11:50
Оценка:
Здравствуйте, samius, Вы писали:

S>>Почему тест должен диктовать реализацию, если речь не о TDD? Почему вопрос о синхронизации должен диктоваться тестом?

S>Тест должен проверять соответствие требованиям. Написанным, или подразумеваемым. И если мы считаем, что код должен быть потокобезопасным, то это заносится в требования и тест должен демонстрировать соответствие им. Но не наоборот, делаем код безопасным, т.к. тест показал необходимость. Можно и наоборот, но первичны требования, т.к. без требований нет смысла и в тесте на проверку этих требований.

Тест должен проверят корректность реализации, иначе простой mock возвращающий корректный результат
тоже будет соотв. требованиям и т.п. Но только проку от этого теста? Т.е. все-таки нельзя отделить
тест от реализации, хоть связь и должна быть неявной, т.е. подразумеваться.

S>>Может и стоит тестировать состояние, чтобы отфильтровать непредсказуемое состояние? Конкретно для данной задачи я не знаю, мало вводных.

S>Это уже сильно зависит от реализации, которая зависит от требований, которых мы не знаем

Ну да, все-таки хотелось бы от ТС услышать продолжение истории, что он ответил и как отреагировали собеседующие?

S>>Вообще, интересная задача. Я тут другую задачку придумал -- как убедиться, не имея исходников и не читая msdn,

S>>что вставка\удаление в List<T> не потоко безопасна? Т.е. получить тест, который всегда бы падал или
S>>проходил. Наверное, все же лучше падал -- т.е. либо исключение, либо неправильное кол-во элементов (сломали инвариант).
S>>Т.е. если бы в эту коллекцию добавили синхронизацию, то тест упал бы.
S>Тут сложно то, что обнаружение потокоопасности носит вероятностный характер. Несложно написать тест, способный обнаружить гонку, сложно сделать его детерминированным за конечное время. И все равно можно будет влиять на тест окружением. Например, выполнение на однопоточной машине не даст результата.

HT не поможет разве?

S>>Ничего лучше чем загрузить потоками со случайной вставкой и удалением я не придумал...

S>Вопрос в том, сколько элементов нужно вставить и удалить, что бы понять наверняка?

Т.е. я по-хорошему вообще не вижу, как тесты могут помочь в корректной реализации многопоточности.
Все носит вероятностный характер.
Кодом людям нужно помогать!
Re[10]: Юнит-тесты многопоточки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.11.21 14:31
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>Тест должен проверят корректность реализации, иначе простой mock возвращающий корректный результат

S>тоже будет соотв. требованиям и т.п. Но только проку от этого теста? Т.е. все-таки нельзя отделить
S>тест от реализации, хоть связь и должна быть неявной, т.е. подразумеваться.
Разумеется, тест проверяет корректность реализации. Но делает он это, наблюдая поведение реализации, а не наличие полей или признаков мока. А если поведение достойной реализации не отличается от поведения мока, то может быть и не нужна она, реализация?

S>Ну да, все-таки хотелось бы от ТС услышать продолжение истории, что он ответил и как отреагировали собеседующие?


S>>Тут сложно то, что обнаружение потокоопасности носит вероятностный характер. Несложно написать тест, способный обнаружить гонку, сложно сделать его детерминированным за конечное время. И все равно можно будет влиять на тест окружением. Например, выполнение на однопоточной машине не даст результата.


S>HT не поможет разве?

На сколько я понимаю, нет. Модель памяти дотнета требует писать данные сразу по месту, не кэшируя в регистрах. А кэши у логических ядер общие. Т.е. потокоНебезопасный код будет на HT работать корректно.

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

S>Все носит вероятностный характер.
Помочь могут, просто надо переходить от абсолютных гарантий к вероятностным. Тест, который будет сообщать что за 10 минут(часов) насилия код не дал сбоя — вполне себе полезен.
Re[10]: Юнит-тесты многопоточки
От: Teolog  
Дата: 09.11.21 15:14
Оценка:
S>Представьте масштабы Амазона, там это когда-нибудь будет выстреливать кучу раз в день. Не вариант.
Вполне представляю.
Я не представляю способа создать "любой возможный случай" с помощью не-случайного теста.
Если его написать линейно и предсказуемо, то он ничего кроме ожидаемого поведения не протестирует.
А потом кааак даст по голове на очередном arm у которого синхронизация оказываеться работает так как сказано в документации, а не так как помнил программист который поменяет тестриуемый код, чтобы он не упирался в lock
Отредактировано 09.11.2021 16:17 Teolog . Предыдущая версия .
Re[8]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 09.11.21 16:21
Оценка:
Здравствуйте, Sharov, Вы писали:

S> ·>Да что тут думать? Тестовый код синхронизируются ровно теми же механизмами... Запускаешь три тестовых треда, которые ждут отлочиваешь их в нужном порядке.

S> Потоки то зачем тогда, и чем время между отлочиваниями отличает от sleep? Разве что заранее известным порядком,
S> хотя тоже не факт.
sleep это не средство синхронизации, а для замера физического времени.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[4]: Юнит-тесты многопоточки
От: Teolog  
Дата: 09.11.21 16:25
Оценка:
Потоки и sleep и нужны чтобы проверить не 3 комбинации о которых человуе подумал. а любые саме дикие.
Тот же принцип что и в определении площади методом монте-карло, пинаем обьект вслепую и смотрим статистику.
Если это внезапного фриза стистемы код упал- значит тест нашел то, для чего был создан- "блуждающий баг многопоточности"
Re[5]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 10.11.21 12:17
Оценка: 4 (1)
Здравствуйте, Teolog, Вы писали:

T>Потоки и sleep и нужны чтобы проверить не 3 комбинации о которых человуе подумал. а любые саме дикие.

T>Тот же принцип что и в определении площади методом монте-карло, пинаем обьект вслепую и смотрим статистику.
T>Если это внезапного фриза стистемы код упал- значит тест нашел то, для чего был создан- "блуждающий баг многопоточности"
Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Юнит-тесты многопоточки
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.11.21 12:25
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>На собеседовании была задача.

BE>C#.
BE>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
BE>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
BE>Ну сам класс то я реализовал через lock.
А что именно реализовал?
То есть если я вызываю сразу RunThird, то мой поток блокируется в ожидании того, чтобы кто-то в другом потоке закончил выполнение RunSecond()?
Или метод сразу выбрасывает InvalidOperationException?

BE>А потом меня попросили написать юнит-тест на это.

BE>Как такое тестировать? Реально стартовать потоки в тесте?
Да, если поведение объекта описано с т.з. нескольких потоков, то при тестировании придётся запускать несколько потоков.
BE>Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.
Значит, нужно обеспечить гарантию того, что потоки вызовут методы в ненужном порядке.
BE>Да и что проверять в тесте?
Выполнение контракта.
Я бы попробовал делать так:
Поток 1 сначала запускает RunThird, затем сигналит евент ThirdIsDone.
Поток 2 сначала дожидается евента ThirdIsDone, затем запускает RunSecond.

Корректная реализация должна встать в deadlock. Это уже можно и проверить.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Юнит-тесты многопоточки
От: BlackEric http://black-eric.lj.ru
Дата: 10.11.21 14:50
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, BlackEric, Вы писали:


S>А что именно реализовал?

S>То есть если я вызываю сразу RunThird, то мой поток блокируется в ожидании того, чтобы кто-то в другом потоке закончил выполнение RunSecond()?
S>Или метод сразу выбрасывает InvalidOperationException?

Ждет

BE>>А потом меня попросили написать юнит-тест на это.

BE>>Как такое тестировать? Реально стартовать потоки в тесте?
S>Да, если поведение объекта описано с т.з. нескольких потоков, то при тестировании придётся запускать несколько потоков.
BE>>Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.
S>Значит, нужно обеспечить гарантию того, что потоки вызовут методы в ненужном порядке.
BE>>Да и что проверять в тесте?
S>Выполнение контракта.
S>Я бы попробовал делать так:
S>Поток 1 сначала запускает RunThird, затем сигналит евент ThirdIsDone.
S>Поток 2 сначала дожидается евента ThirdIsDone, затем запускает RunSecond.

S>Корректная реализация должна встать в deadlock. Это уже можно и проверить.

Ну тут еще как минимум нужно проверить, что отработал первый
https://github.com/BlackEric001
Re[3]: Юнит-тесты многопоточки
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.11.21 15:42
Оценка:
Здравствуйте, BlackEric, Вы писали:

S>>Корректная реализация должна встать в deadlock. Это уже можно и проверить.

BE>Ну тут еще как минимум нужно проверить, что отработал первый
Это можно проверить двумя независимыми тестами: один проверяет последовательность First/Second, второй — Second/Third.
Устройство тестов, очевидно, будет одинаковым.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Юнит-тесты многопоточки
От: StanislavK Великобритания  
Дата: 10.11.21 15:43
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.

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

BE>Как такое тестировать?

Многопоточный код по-определению тестировать бесконечно сложно и, в общем случае, это ни к чему хорошему это не приводит. Достачно посмотреть на единственную бибилотеку, которая это делает правильно — jcstress и почитать почему она это делает так и никак иначе: https://shipilev.net/talks/hydraconf-June2021-jcstress-workshop.pdf

BE>Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

BE>Да и что проверять в тесте?
Если я правильно понял условие задачи, вызываться они могут как угодно, но выполняться они должны строго по-порядку. Соответвенно надо написать тест, который будет проверять что последовательность не нарушается когда 3 потока одновременно/в любом порядке вызывают эти методы.
В 99% случаев написанный тест все равно не будет корректным, но, к счастью 99% собеседующих этого не заметят, так как Шипелева не читали
Отредактировано 10.11.2021 16:05 StanislavK . Предыдущая версия . Еще …
Отредактировано 10.11.2021 15:51 StanislavK . Предыдущая версия .
Re: Юнит-тесты многопоточки
От: vaa  
Дата: 11.11.21 02:06
Оценка:
Здравствуйте, BlackEric, Вы писали:


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

BE>Да и что проверять в тесте?

Предлагаю так. Для пущей уверенности можно рандомно генерить массив tested;

using System.Diagnostics;
using static System.Console;

public class TestObject
{
    public void First() => WriteLine(1);
    public void Second() => WriteLine(2);
    public void Third() => WriteLine(3);
}

public class Test
{
    readonly int[] steps = new int[3];
    readonly TestObject obj = new();
    int step = 0;
    public void RunAll()
    {
    var tested = new Action[]
        {   () => { obj.First(); steps[step++] = 1; },
           () => { obj.Second(); steps[step++] = 2; },
           () => { obj.Third(); steps[step++] = 3;}
        };
        Parallel.ForEach( tested , action => action.Invoke());
    }

    public bool Validate()
        => steps[0] == 1 && steps[1] == 2 && steps[2] == 3;
}

public static class Program
{
    static void Main()
    {
        Enumerable.Range(1, 100).ToList().ForEach(
            _ =>
            {
                var test = new Test();
                test.RunAll();
                Debug.Assert(test.Validate(), "Нарушен порядок");
            }
        );
    }
}


PS Еще есть такие фрэмейфорки fscheck cscheck expecto
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 11.11.2021 2:19 Разраб . Предыдущая версия .
Re[6]: Юнит-тесты многопоточки
От: Teolog  
Дата: 11.11.21 09:31
Оценка:
·>Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований.

Я тоже не понимаю, но требования к тесту предельно конкретны.
>>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird.
>>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.

Это и должен делать тест. Вызывать методы "как-угодно", в том числе одновременно, или несколько раз один и тот же.
Тест вероятностный, потому как даже предположить все возможные комбинации и сюрпризы не выйдет. Какая задача, такое и решение. Чтобы соорудить нечто более детерминированное, нужны более узкие граничные условия.
Re[7]: Юнит-тесты многопоточки
От: · Великобритания  
Дата: 11.11.21 10:47
Оценка:
Здравствуйте, Teolog, Вы писали:

T>·>Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований.

T>Я тоже не понимаю, но требования к тесту предельно конкретны.
Нет там конкретики. Мы тут всё гадаем что же должно произойти если вызваны не в порядке.

T>Это и должен делать тест. Вызывать методы "как-угодно", в том числе одновременно, или несколько раз один и тот же.

T>Тест вероятностный, потому как даже предположить все возможные комбинации и сюрпризы не выйдет. Какая задача, такое и решение. Чтобы соорудить нечто более детерминированное, нужны более узкие граничные условия.
Вообще-то предполагается использовать какие-то многопоточные примитивы, значит комбинаций значительно меньше — три метода, три точки синхронизации — шесть комбинаций.

Именно поэтому сабж лучше делать как whitebox, т.е. юнит-тесты должны тестировать corner cases конкретной реализации, а не пытаться быть универсальными всемогутерами, доказывающими корректность произвольного кода.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 11.11.2021 10:48 · . Предыдущая версия .
Re: а вопрос с подковыркой
От: Quebecois Канада https://www.canada.ca/
Дата: 11.11.21 18:07
Оценка:
Здравствуйте, BlackEric, Вы писали:

BE>А потом меня попросили написать юнит-тест на это.


BE>Как такое тестировать? Реально стартовать потоки в тесте? Но где гарантия, что потоки случайно не вызовут методы в нужном порядке.

"в лоб" это протестировать нельзя, т.к. с точки зрения теста это выглядит так:

1. Тест вызвал метод
2. (возможное переключение контекста)
3. Метод сделал lock()
4. (возможное переключение контекста)
5. Метод сделал что-то полезное

Не зная, что внутри метода, тест не может отличить переключение контекста между моментами 2 и 4, соответственно, убедиться, что lock() был сделан. Это можно определить статистически, смотря на время вызова/возврата и считая относительные вероятности, но делать это для собственного кода — мазохизм.

Я бы это разбил на 2 задачи:
1. Проверяем последовательность методов в одном потоке
2. Проверяем, что методы делают lock() через вспомогательный метод

Вспомогательный метод должен делать lock() и ждать какого-то события. Тестирование тогда будет выглядеть так:

1. Запускаем второстепенный поток, который берет lock()
2. Запускаем еще один поток, который вызывает метод
3. Убеждаемся, что метод не вернул управление за N миллисекунд
4. Освобождаем lock
5. Убеждаемся, что метод вернул управление через M миллисекунд

Но это тоже полу-костыль. Не-костыльный вариант — это объявить свой ISynchronizationManager, который будет создавать mutex-ы. Тест может имплементироавть свою версию, считающую, сколько раз ее вызвали из каких потоков, и дающую задержки, где надо.

Но это в теории. На практике вероятность, что кто-то уберет из работающего метода lock() просто, чтобы поглумиться, стремится к нулю. Скорее, его зарефакторят, разделив на 2 метода, оставя lock во внешнем, а потом через год вызовут внутренний метод из свежедобавленного, где забудут lock(). Против таких вещей unit-тесты бесполезны, а вот хороший стресс-тест, жарящий пару реалистичных юзкейсов потоков так из 50, выявит это на лету.

Но это с точки зрения инженера. А в организационном плане, если насяльника сказал, что нужны 100% тесты, то надо делать большие голубые глаза и справшивать, с сахаром или без. Потому что если вы с умным видом изложите на собеседовании перечисленное выше, то вас не возьмут, как overqualified.
Отредактировано 11.11.2021 18:11 Quebecois . Предыдущая версия . Еще …
Отредактировано 11.11.2021 18:09 Quebecois . Предыдущая версия .
Re: Юнит-тесты многопоточки
От: _typhoon  
Дата: 15.11.21 09:15
Оценка:
Здравствуйте, BlackEric, Вы писали:

Можно создать три потока и поставить sleep в порядке уменьшения в следующем потоке на порядок и результат вызовов естественно должен выводиться в порядке 123 123 123 и т.l с задержкой самого большого sleep
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.