Size: a a a

testing_in_python

2021 April 30

ТЭ

Тачами Экстович... in testing_in_python
сегодня?
источник

ИС

Игорь Середа... in testing_in_python
Ну да. В комьюнити почитай.
источник

ТЭ

Тачами Экстович... in testing_in_python
Хорошая попытка, но нет)
источник

ИС

Игорь Середа... in testing_in_python
Можешь не вступать. :)
источник

J

Jeweller in testing_in_python
Привет! А как вы тестовые сущности создаёте? Такие как "пользователь", с нужными настройками. У меня через бд нету возможности, там какие-то страшные зависимости, о которых никто не помнит. Через requests с использованием dto на пидантике нормальная практика для питона?
источник

ТЭ

Тачами Экстович... in testing_in_python
источник

ТЭ

Тачами Экстович... in testing_in_python
Единственная сходка на которую я приду
источник

СС

Сказочный Сникерс... in testing_in_python
ну давайте попробуем, не знаю как насколько это будет читабельно в текстовом виде.

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



почти весь рабочий флоу в проде строится на том, что сервисы по сути соеденены в цепочку. пришло событие на первый сервис, он его обработал и выплюнул результат куда либо (а базу, файл на диск, еще куда то).

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

и так далее по цепочке. таких цепочек несколько, в них разные сервисы.




поднимать все это в целостную систему можно, но не имеет смысла, ибо довольно сложно будет организовать процесс тестирования, поэтому тестирование у нас исключительно модульное, то есть под каждый сервис свои тесты без участия других сервисов (за редким исключением, в некоторых случаях мы действительо тестируем связки, но это скорее исключение)



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

как это происходит? для начала рассмотрим запуск в 1 поток, без параллельности.

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

например первому сервису нужна база в mysql, база в кликхаусе, нужно 2 топика в кафке и ему нужна директория с файлами, к которой он сможет обращаться по http как к файловому серверу. пайтест на стадии инициализации видит что у нас будет тестироваться этот сервис и запускает этот класс.

класс знает имя сервиса, определяет номер потока пайтеста (в однопоточном режиме это 0) и составляет уникальный идентфиикатор этого сервиса (пусть будет service1_0). с этим идентификатором он создает рабочую директорию для сервиса, туда кладет его конфиг, при этом конфиг тоже генерируется под этот сервис и этот уникальный идентификатор, там же генерируется уникальный порт (или несколько), с которым сервис будет подниматься в будущем. с этим идентификатором в контейнере с базой создается база под этот сервис, эта же база прописывается в его конфиг. то же самое с КХ и кафкой. далее как я уже сказал если ему нужна какя то директория для файлов, он создает и ее внутри рабочей. на эту директорию он запускает nginx тоже на уникально сгенеренном порту, который будет файловым сервером. порт nginx тоже прописывается в конфиг сервиса.

после всех подобных операций у нас есть папка под конкретный сервис, уникально настроенный для будущей работы со всеми подготовленными глобальными зависимостями


то же самое происходит со всеми остальными сервисами.




сложность добавляет то, что просто так мы запустить сервис сразу нельзя, так как данные от будущих тестов должны уже быть во всех источниках (база, кх, кафка, диск, в моках итд) до старта сервиса, он читает их при старте разово, и не все сервисы потом могут в рантайме их дочитывать.

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

так как на стадии коллекта класс с тестом, в котором дергается метод подготовки это совсем не
источник

СС

Сказочный Сникерс... in testing_in_python
тот класс, который будет на момент исполнения теста, то все данные, которые надо будет из подготовки сохранить для теста - сохраняются в специальный глобальный объект пайтеста (в один большой словарь), где ключ - это уникальное имя теста (модуль, класс, функция, параметр), а значение - еще один словарь (с доступом к ключам через .) где все переменные которые потребуются тестам.

когда вся подготовка для всех тестов выполнена и все нужные данные от подготовки прикопаны - выполняется последовательный старт всех сервисов и ожидание пока они будут готовы к работе

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

по завершению - все сервисы останавливаются с проверками что все штатно завершается итд.






теперь про многопоточность.

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


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

когда сервисов было 5-10 это так и работало, да, немного оврехеда, но зато никакого гемора с распределением. но когда сервисов стало 30-40, это стало проблемой, так как в 10 потоков получалось 300-400 запущенных сервисов и с 4к тестов мы делали 10 копий всех подготовительных данных для всех сервисов.




именно поэтому от классического планировщика и классического распределения мы отказались. что пришло на замену?

на замену пришло ручное распределение, чтобы уложить максимальное количество тестов на один сервис на один поток, чтобы не создавать копии, и при этом жестко понимать какие тесты на этом потоке будут запущены, чтобы выполнять подготовку только для них. но как быть со временем? ведь мы не знаем сколько времени будут идти тесты, например на 1 сервис у нас 1000 тестов и идут они 10 минут, а на второй сервис у нас 20 тестов и идут они 20 секунд.

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


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


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


суммируем все тайминги от всех тестов и получаем общее планируемое время прогона. делим его на количество процессов пайтеста и получаем средннее планируемое время процесса. на всякий добавляем к нему 1% времени.

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

СС

Сказочный Сникерс... in testing_in_python
деление переходит на следующий процесс итд. Ну и остатки забиваем так чтобы максимально уместить тесты одного сервиса на 1 процессе.


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




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

СС

Сказочный Сникерс... in testing_in_python
Ы) много букак
источник

СС

Сказочный Сникерс... in testing_in_python
ну и вот пример нашего умного распределения по времени с максимальной оптимизацией по количеству поднимаемых сервисов и оптимизаций по подготовке данных
источник

СС

Сказочный Сникерс... in testing_in_python
и вот оно распределение
источник

СС

Сказочный Сникерс... in testing_in_python
как мы видим пересечений очень мало (точнее их нет), при этом на каждом процессе разное количество тестов
источник

ИС

Игорь Середа... in testing_in_python
Есть же telegra.ph.
источник

СС

Сказочный Сникерс... in testing_in_python
надо статью делать там, с картинками, графиками итд) или на хабре
источник

СС

Сказочный Сникерс... in testing_in_python
никто даже смотрю не комментирует никак, либо читать впадлу, либо совсем трешак написан)
источник

ИС

Игорь Середа... in testing_in_python
Портянки почти никогда не читают.
источник

V

Vit in testing_in_python
давай статью на хабре с картинками ) я читаю)
источник

V

Vit in testing_in_python
как прибить конкретный тест к конкретному потоку xdist?
источник