Юнит-тесты многопоточки
От: 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
Кодом людям нужно помогать!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.