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...
Пока на собственное сообщение не было ответов, его можно удалить.