S>Представьте масштабы Амазона, там это когда-нибудь будет выстреливать кучу раз в день. Не вариант.
Вполне представляю.
Я не представляю способа создать "любой возможный случай" с помощью не-случайного теста.
Если его написать линейно и предсказуемо, то он ничего кроме ожидаемого поведения не протестирует.
А потом кааак даст по голове на очередном arm у которого синхронизация оказываеться работает так как сказано в документации, а не так как помнил программист который поменяет тестриуемый код, чтобы он не упирался в lock
Здравствуйте, Sharov, Вы писали:
S> ·>Да что тут думать? Тестовый код синхронизируются ровно теми же механизмами... Запускаешь три тестовых треда, которые ждут отлочиваешь их в нужном порядке. S> Потоки то зачем тогда, и чем время между отлочиваниями отличает от sleep? Разве что заранее известным порядком, S> хотя тоже не факт.
sleep это не средство синхронизации, а для замера физического времени.
Потоки и sleep и нужны чтобы проверить не 3 комбинации о которых человуе подумал. а любые саме дикие.
Тот же принцип что и в определении площади методом монте-карло, пинаем обьект вслепую и смотрим статистику.
Если это внезапного фриза стистемы код упал- значит тест нашел то, для чего был создан- "блуждающий баг многопоточности"
Здравствуйте, Teolog, Вы писали:
T>Потоки и sleep и нужны чтобы проверить не 3 комбинации о которых человуе подумал. а любые саме дикие. T>Тот же принцип что и в определении площади методом монте-карло, пинаем обьект вслепую и смотрим статистику. T>Если это внезапного фриза стистемы код упал- значит тест нашел то, для чего был создан- "блуждающий баг многопоточности"
Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, 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. Это уже можно и проверить.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, 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. Это уже можно и проверить.
Ну тут еще как минимум нужно проверить, что отработал первый
Здравствуйте, BlackEric, Вы писали:
S>>Корректная реализация должна встать в deadlock. Это уже можно и проверить. BE>Ну тут еще как минимум нужно проверить, что отработал первый
Это можно проверить двумя независимыми тестами: один проверяет последовательность First/Second, второй — Second/Third.
Устройство тестов, очевидно, будет одинаковым.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, 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% собеседующих этого не заметят, так как Шипелева не читали
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
·>Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований.
Я тоже не понимаю, но требования к тесту предельно конкретны. >>В 3 разных потока будет передан один объект. В нем есть 3 метода. RunFirst, RunSecond, RunThird. >>Нужно сделать так, что бы методы могли быть вызваны только по очереди. Хотя потоки могут вызывать их как угодно.
Это и должен делать тест. Вызывать методы "как-угодно", в том числе одновременно, или несколько раз один и тот же.
Тест вероятностный, потому как даже предположить все возможные комбинации и сюрпризы не выйдет. Какая задача, такое и решение. Чтобы соорудить нечто более детерминированное, нужны более узкие граничные условия.
Здравствуйте, Teolog, Вы писали:
T>·>Не очень ясно как это относится к сабжу. Если такие баги искать, то это будут perf/load/soak тесты, это инструмент контроля качества нефункциональных требований. Т.е. можно запилить отдельный тестовый стенд и чтобы там постоянно 24/7 гонялась аппликуха. Юнит-тесты же обычно являются частью билда и являются инструментом разработки для фиксации функциональных требований. T>Я тоже не понимаю, но требования к тесту предельно конкретны.
Нет там конкретики. Мы тут всё гадаем что же должно произойти если вызваны не в порядке.
T>Это и должен делать тест. Вызывать методы "как-угодно", в том числе одновременно, или несколько раз один и тот же. T>Тест вероятностный, потому как даже предположить все возможные комбинации и сюрпризы не выйдет. Какая задача, такое и решение. Чтобы соорудить нечто более детерминированное, нужны более узкие граничные условия.
Вообще-то предполагается использовать какие-то многопоточные примитивы, значит комбинаций значительно меньше — три метода, три точки синхронизации — шесть комбинаций.
Именно поэтому сабж лучше делать как whitebox, т.е. юнит-тесты должны тестировать corner cases конкретной реализации, а не пытаться быть универсальными всемогутерами, доказывающими корректность произвольного кода.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, 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.
Можно создать три потока и поставить sleep в порядке уменьшения в следующем потоке на порядок и результат вызовов естественно должен выводиться в порядке 123 123 123 и т.l с задержкой самого большого sleep