Як зробити так, щоб тести на одиницях швидко працювали?


40

Ми досягли точки в нашому проекті, де у нас майже тисяча тестів, і люди перестали заважати виконувати їх, перш ніж робити реєстрацію, оскільки це займає так багато часу. У кращому випадку вони виконують тести, що стосуються фрагменту коду, який вони змінили, а в гіршому - просто перевіряють його без тестування.

Я вважаю, що ця проблема пов'язана з тим, що рішення виріс до 120 проектів (ми зазвичай робимо набагато менші проекти, і це лише другий раз, коли ми робимо TDD належним чином), і час складання + тестування виріс приблизно до двох-трьох хвилин на менших машинах.

Як ми скорочуємо час виконання тестів? Чи є методи? Бажаєте більше? Недоліки менше? Можливо, більші інтеграційні тести не повинні запускатися автоматично під час запуску всіх тестів?

Редагувати: як відповідь на кілька відповідей, ми вже використовуємо CI та сервер збірки, ось як я знаю, тести не виходять. Проблема (насправді симптом) полягає в тому, що ми постійно отримуємо повідомлення про невдалі збірки. Запуск часткових тестів - це те, що робить більшість людей, але не всі. що стосується тестів, вони насправді дуже добре зроблені, вони використовують підробки для всього, і взагалі немає IO.


8
Отримати краще обладнання? Обладнання дешеве порівняно з програмістським часом.
Брайан Оуклі

18
Ви вже мали на увазі рішення у вашому запитанні: виконуйте лише тести, які стосуються фрагмента коду, який було змінено. Періодично виконуйте весь тестовий набір у рамках циклу QA / Release. При цьому 2–3 хвилини не схожі на багато часу, тому можливо, що ваша команда розробників занадто часто перевіряє речі.
Роберт Харві

3
Перший орієнтир, щоб з’ясувати, звідки береться вартість продуктивності. Чи є кілька дорогих тестів чи це велика кількість тестів? Чи дорогі певні установки?
CodesInChaos

13
Чорт, я хотів би, щоб наші тести були лише 2-3 хвилини. Щоб виконати всі наші тестові одиниці, потрібно 25 хвилин - і ми ще не маємо жодних інтеграційних тестів.
Ізката

4
2 - 3 хвилини? Jeez. Наші можуть працювати години ...
Родді з замороженого гороху

Відповіді:


51

Можливим рішенням буде перенести тестувальну частину з машин для розробки на безперервну програму інтеграції (наприклад, Дженкінс ), використовуючи програмне забезпечення управління версіями певного смаку ( git , svn тощо).

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

Коли дана функція, виправлення помилок або інше, над чим вони працюють, завершено, гілку можна об'єднати назад у стовбур (або як би ви вважали за краще це зробити), де виконуються всі одиничні тести. Якщо тест не вдається, злиття відхиляється і розробник отримує сповіщення, щоб вони могли виправити помилки.

Ви також можете змусити ваш сервер CI запускати одиничні тести для кожної гілки функцій, коли здійснюються комісії. Таким чином розробник може внести деякі зміни, ввести код і дозволити серверу виконувати тести у фоновому режимі, поки вони продовжують працювати над додатковими змінами або іншими проектами.

Чудовий посібник щодо одного способу здійснення таких налаштувань можна знайти тут (специфічний для git, але він повинен працювати в інших системах управління версіями): http://nvie.com/posts/a-successful-git-branching-model/


15
Це. Якщо розробники "перестали заважати запускати їх (одиничні тести) перед тим, як робити реєстрацію", тоді ви хочете, щоб ваша установка CI запускала їх після реєстрації.
Carson63000,

+1: Подальше вдосконалення могло б модулювати тести. Якщо конкретний модуль / файл не змінився з моменту останнього запуску, немає підстав повторно запускати тести, які відповідають за його тестування. Начебто makefile, який не перекомпілює все лише тому, що один файл змінився. Це може зажадати певної роботи, але, ймовірно, також дасть вам більш чисті тести.
Лев

Чи буде методологія розгалуження працювати з TFS? Ми пишемо C # з TFS, а розгалуження в TFS менш дружні, ніж у git. Я вірю, що ця ідея навіть буде відхилена, оскільки ми ніколи не робимо розгалуження.
Ziv

Я не маю особистого досвіду роботи з TFS; однак, мені вдалося зіткнутися з цим посібником від Microsoft, який, схоже, демонструє подібну стратегію розгалуження до стратегії в публікації: msdn.microsoft.com/en-us/magazine/gg598921.aspx
Майк

33

Більшість тестів на одиницю повинна тривати менше 10 мілісекунд. Провести «майже тисячу тестів» - це нічого, і для запуску може знадобитися кілька секунд.

Якщо їх немає, то слід припинити писати сильно зв'язані тести інтеграції (якщо тільки цього не потрібен код) і почати писати хороші тести на одиницю (починаючи з добре роз'єднаного коду та правильного використання підробок / макетів / заглушок / тощо). Це з'єднання вплине на якість тесту і час, необхідний для їх написання - тому це не лише питання скорочення часу виконання тесту.


30
Ну, напевно, ви не повинні припиняти писати інтеграційні тести та інші автоматичні тести, що не є одиницею, оскільки вони самі корисні. Ви просто не повинні плутати їх з одиничними тестами та тримати їх окремо, частково тому, що вони повільніші.

2
Ваше право, що це здається інтеграційними тестами.
Tom Squires

9
Ця відповідь не є результативною. По-перше, це встановлює необгрунтоване очікування. У самих рамках тестування блоків є накладні витрати; що кожен тест займає менше мілісекунди, це не означає, що тисяча випробувань повинна тривати менше декількох секунд. Те, що весь тестовий набір ОП закінчується за 2-3 хвилини, є дуже хорошим знаком у більшості заходів.
rwong

6
@rwong - вибач, я називаю фігня. Метрика, яку я отримав, - це виконання двох доступних мені професійних проектів: один із ~ 300 тестами, один із ~ 30000 тестів та перегляд виконання тестів. Тестовий набір, який займає 2-3 хвилини для <1000 тестів, є жорстоким та ознакою того, що тести недостатньо ізольовані.
Теластин

2
@rwong У тому ж руслі, що і Теластин, тут є дані про мене: Навіть із досить великими, ніж ідеальними тестами, тестовий фреймворк ( py.test) робить тони магії у фоновому режимі, і все є чистим кодом Python ("100x повільніше, ніж на C "), запуск тестів близько 500 в моєму проекті займає менше 6 секунд на кілька років повільному нетбуку. Цей показник приблизно лінійний за кількістю тестів; хоча є деякий стартовий наклад, він амортизується на всіх тестах, а накладні накладні витрати O (1).

16

Існує кілька підходів, які я використовував для вирішення подібного питання:

  1. Перевірте час виконання та знайдіть усі найповільніші тести, а потім проаналізуйте, чому їм потрібно так багато часу для виконання .
  2. У вас 100 проектів, можливо, вам не потрібно їх будувати та перевіряти кожен раз? Чи можете ви запускати весь тест тільки в нічні побудови? Створіть кілька конфігурацій швидкої збірки для щоденного використання . Сервер CI буде виконувати лише обмежений набір UnitTests проектів , пов'язані з «гарячої» частиною поточного процесу розробки .
  3. Знущайтеся та ізолюйте все, що могли , уникайте дискового / мережевого вводу / виводу, коли це можливо
  4. Якщо такі операції неможливо ізолювати, можливо, у вас є інтеграційні тести? Може бути, ви могли запланувати інтеграційні тести лише на нічні побудови ?
  5. Перевірте всі випадкові синглтони, які зберігають посилання на екземпляри / ресурси та що споживають пам'ять, це може призвести до зниження продуктивності під час виконання всіх тестів.

Крім того, ви можете використовувати наступні інструменти, щоб полегшити життя і швидше провести тести

  1. Обрізана фіксація деяких серверів CI може бути налаштована на виконання збірки та тестування перед тим, як передавати код у сховище джерела. Якщо хтось зробить код, не запустивши заздалегідь усі тести, які також містять невдалі тести, він буде відхилений та повернений авторові.
  2. Налаштуйте сервер CI паралельно виконувати тести : за допомогою декількох машин або процесів. Прикладами є pnunitконфігурація CI з декількома вузлами.
  3. Плагін безперервного тестування для розробників, який автоматично запускає весь тест під час написання коду.

12

0. Слухайте своїх програмістів.

Якщо вони не виконують тести, це означає, що вони сприймають вартість (чекаючи запуску тестів, вирішення помилкових збоїв), що перевищує значення (негайне виявлення помилок). Зменшуйте витрати, збільшуйте вартість, і люди весь час будуть проводити тести.

1. Зробіть тести на 100% надійними.

Якщо у вас коли-небудь є тести, які не відповідають помилковим негативам, вирішуйте це негайно. Зафіксуйте їх, змініть, усуньте все, що потрібно, щоб гарантувати 100% надійність. (Добре мати набір ненадійних, але все-таки корисних тестів, які можна запустити окремо, але основна частина тестів повинна бути надійною.)

2. Змініть свої системи, щоб гарантувати, що всі тести проходять весь час.

Використовуйте системи безперервної інтеграції для того, щоб лише об'єкти, що проходять, об'єднувались у головну / офіційну / релізну / будь-яку галузь.

3. Змініть свою культуру на значення 100% проходження тестів.

Навчіть урок, що завдання не виконується, поки не пройде 100% тестів і воно не буде об'єднано в основну / офіційну / релізну / будь-яку галузь.

4. Зробіть тести швидко.

Я працював над проектами, де тести займають секунду, і над проектами, де вони займають цілий день. Існує сильна кореляція між часом, необхідним для виконання тестів, і моєю продуктивністю.

Чим довше потрібно запускати тести, тим рідше ви будете їх виконувати. Це означає, що ви будете тривати довше, не отримуючи відгуків про внесені вами зміни. Це також означає, що ви будете довше переходити між комісіями. Здійснення частіше означає менші кроки, які легше об'єднати; історію фіксації легше наслідувати; знайти помилку в історії простіше; відкат також простіше.

Уявіть тести, які працюють настільки швидко, що ви не заперечуєте автоматично виконувати їх щоразу, коли компілюєте.

Зробити тести швидко, може бути важко (ось що запитував ОП, правда!). Розв'язка є ключовою. Макети / підробки в порядку, але я думаю, що ви можете зробити краще, якщо рефакторинг зробити макети / підробки непотрібними. Дивіться блог Арло Белші, починаючи з http://arlobelshee.com/post/the-no-mocks-book .

5. Зробіть тести корисними.

Якщо тести не провалюються, коли ви накручуєтесь, то який сенс? Навчіть себе писати тести, які дозволять наздогнати помилок, які ви, ймовірно, створите. Це вміння для себе, і буде потрібно багато уваги.


2
ЧИСТО погоджуєтесь, особливо пункти 3 та 1. Якщо розробники не виконують тести, тести порушуються, середовище порушується або те і інше. Точка 1 - мінімум. Помилкові помилки гірші, ніж пропущені тести. Тому що люди вчаться сприймати невдачі. Після того, як невдача буде толерантною, вона поширюється, і вона потребує великих зусиль, щоб повернутися до 100% проходження та ОЧАКУЮЧИ 100% проходження. Почніть це виправляти вже сьогодні .
Білл IV

І як ти можеш не погодитися з №5?!? на додаток до 1 і 3, або чортів, 2 і 4 теж! У будь-якому випадку, чудова відповідь навколо.
чотирипаночі

4

Через пару хвилин все в порядку. Однак майте на увазі, що існує 3 основних типи тестів:

  1. Тестові одиниці - перевірити кожну «одиницю» (клас чи метод) незалежно від решти проекту
  2. Інтеграційні тести - протестуйте проект у цілому, як правило, здійснюючи дзвінки в програму. Деякі проекти, які я бачив, поєднують це з тестами регресії. Тут значно менше насмішок, ніж одиничні тести
  3. Регресійні тести - протестуйте завершений проект в цілому, оскільки тестовий набір є кінцевим користувачем. Якщо у вас є консольний додаток, ви використовуєте консоль для запуску та тестування програми. Ви ніколи не піддаєте внутрішніх даних цих тестів, і будь-який кінцевий користувач вашої програми повинен (теоретично) мати можливість запускати набір тестів регресії (хоча вони ніколи не будуть)

Вони перераховані в порядку швидкості. Одиничні випробування повинні бути швидкими. Вони не зможуть наздогнати кожної помилки, але вони встановлять, що програма гідно розумна. Експертні тести повинні працювати протягом 3 хвилин або менше або пристойного обладнання. Ви кажете, що у вас є лише 1000 одиничних тестів, а вони займають 2-3 хвилини? Ну, це, мабуть, добре.

Що потрібно перевірити:

  • Переконайтеся, що тести вашого модуля та тести інтеграції є окремими. Інтеграційні тести завжди будуть повільнішими.

  • Переконайтесь, що ваші тести блоку працюють паралельно. Для них немає причин, якщо вони справжні одиничні тести

  • Переконайтесь, що ваші одиничні тести "не залежні" Вони ніколи не повинні мати доступ до бази даних або файлової системи

Крім цього, ваші тести зараз не дуже погані. Однак, для довідки, один з моїх знайомих в команді Microsoft має 4000 тестів, які працюють на 2-х хвилинах на гідному обладнання (і це складний проект). Можливо пройти швидкі одиничні тести. Усунення залежностей (і знущаються лише стільки, скільки потрібно) - головне, щоб досягти швидкості.


3

Навчіть своїх розробників щодо персонального програмного забезпечення (PSP), допомагаючи їм зрозуміти та покращити їх ефективність, використовуючи більше дисципліни. Написання коду не має нічого спільного з лясканням пальцями по клавіатурі, після чого натисніть компіляцію та кнопку реєстрації.

PSP раніше користувався великою популярністю, коли компіляція коду була процесом, який забирав багато часу (години / дні на мейнфреймі, тому всім доводилося ділитися компілятором). Але коли особисті робочі станції стали більш потужними, ми всі прийняли процес:

  1. введіть код, не замислюючись
  2. хіт-збірка / компіляція
  3. виправте свій синтаксис, щоб він склав
  4. запустіть тести, щоб перевірити, чи є те, що ви написали, насправді має сенс

Якщо ви думаєте, перш ніж вводити, а потім після введення, переглядати те, що ви написали, ви можете зменшити кількість помилок, перш ніж запустити набір для складання та тестування. Навчіться не натискати збірку 50 разів на день, але, можливо, один чи два рази, тоді важливо менше, ніж час складання та тестування займає кілька хвилин більше.


2
Я сильно погоджуюся з вашим списком, але абсолютно не з тим, що "запускати збірку лише два рази на день краще, ніж у 50 разів".
Док Браун

3

Один з можливих способів: розділити своє рішення. Якщо рішення має 100 проектів, то це досить некеровано. Тільки тому, що два проекти (скажімо, A і B) використовують загальний код іншого проекту (скажімо, Lib), це не означає, що вони повинні знаходитись в одному і тому ж рішенні.

Натомість ви можете створити рішення A з проектами A і Lib, а також рішення B з проектами B і Lib.


2

Я в подібній ситуації. У мене є модульні тести, які перевіряють зв'язок із сервером. Вони перевіряють поведінку за допомогою таймаутів, скасовують з'єднання тощо. Весь набір тестів триває 7 хвилин.

7 хвилин - це порівняно короткий час, але це не те, що ви будете робити перед кожним вчиненням.

У нас також є набір автоматизованих тестів на користувальницький інтерфейс, їх час роботи 2 години. Це не те, що ви хочете щодня запускати на своєму комп’ютері.

Отже, що робити?

  1. Зміна тестів зазвичай не дуже ефективна.
  2. Запустіть лише відповідні тести до початку вчинення.
  3. Запускайте всі свої тести щодня (або кілька разів на день) на сервері збірки. Це також дасть вам можливість створювати приємні звіти про покриття коду та аналіз коду.

Важливим є те, що всі ваші тести потрібно проводити часто, тому що важливо знайти помилки. Однак не обов'язково їх знаходити до початку комісій.


1
Щодо тестів, які розмовляють із серверами: Якщо це розмова з сервером, це насправді не тестовий пристрій, це щось вище. Якби я був ти, я б відокремив одиничні тести (які повинні працювати швидко) і принаймні запускатиму їх перед кожним вчиненням. Таким чином, ви принаймні отримаєте швидкі речі (речі, з якими не потрібно говорити на сервері), перш ніж буде зроблено код.
Майкл Коне

@MichaelKohne Я знав, що хтось це помітить. Я знаю, що це не зовсім одиничні тести, але вони служать одній і тій же цілі, це лише про те, як ти їх називаєш.
Султан

1
в основному мова йде про те, як ви їх називаєте, але добре пам’ятати про різницю (яке б ім’я ви не використовували). Якщо ви не розмежуєтесь, то (на мій досвід) розробки мають тенденцію просто писати тести вищого рівня. У цей момент ви не отримаєте тести, що змушують вас бути розумними у своїх абстракціях та зв'язках.
Майкл Коне

1

Хоча ваш опис проблеми не дає глибокого розуміння кодової бази, я думаю, що я сміливо можу сказати, що ваша проблема двояка.

Навчіться писати правильні тести.

Ви кажете, що у вас майже тисяча тестів, і у вас 120 проектів. Якщо припустити, що більшість половини цих проектів є тестовими проектами, у вас є 1000 тестів до 60 проектів з кодом виробництва. Це дає вам приблизно 16-17 тестів pr. проект !!!

Це, мабуть, кількість тестів, які мені доведеться охопити близько 1-2 класів у виробничій системі. Тож якщо у вас є лише 1-2 класи в кожному проекті (у цьому випадку ваша структура проекту є надто дрібнозернистою) ваші тести занадто великі, вони покривають занадто багато ґрунту. Ви кажете, що це перший проект, який ви робите TDD належним чином. Скажімо, наведені вами цифри вказують, що це не так, ви не володієте властивістю TDD.

Вам потрібно навчитися писати правильні тести, що, ймовірно, означає, що вам потрібно навчитися в першу чергу робити тест на код. Якщо ви не можете знайти досвід роботи всередині команди, я б запропонував найняти допомогу ззовні, наприклад, у формі одного або двох консультантів, які допомагають вашій команді протягом 2-3 місяців навчитися писати тестовий код, і мінімальні одиничні тести.

Для порівняння, у проекті .NET, над яким я зараз працюю, ми можемо виконати приблизно близько 500 одиниць тестів менш ніж за 10 секунд (а це навіть не вимірювалося на високоспецифічній машині). Якби це були ваші цифри, ви б не побоялися запускати їх локально кожен раз.

Навчіться керувати структурою проекту.

Ви розділили рішення на 120 проектів. Це, на мої стандарти, приголомшлива кількість проектів.

Тож якщо є сенс насправді мати таку кількість проектів (які, на мою думку, це не так, - але ваше запитання не дає достатньої інформації для того, щоб кваліфікувати це рішення), вам потрібно розділити проекти на більш дрібні компоненти, які можна будувати, переробляти та розгортати окремо. Отже, коли розробник запускає блок тестового набору, йому / їй потрібно лише запустити тести, що стосуються компонента, над яким він працює. Сервер збирання повинен подбати про те, щоб все інтегрувалося правильно.

Але поділ проекту на декілька складових складання, переосмислення та розгортання окремо вимагає, з мого досвіду, дуже зрілої команди розвитку, команди, яка є більш зрілою, ніж я відчуваю, що ваша команда.

Але в будь-якому випадку потрібно зробити щось щодо структури проекту. Або розділіть проекти на окремі компоненти, або розпочніть об'єднання проектів.

Запитайте себе, чи справді вам потрібно 120 проектів?

ps Ви можете перевірити NCrunch. Це плагін Visual Studio, який автоматично запускає ваш тест у фоновому режимі.


0

Тест JUnit, як правило, є швидким, але для деяких з них просто потрібно певний час.

Наприклад, тест бази даних, як правило, займає кілька разів, щоб ініціалізуватися та закінчити.

Якщо у вас є сотні тестів, навіть якщо вони швидкі, їм потрібно багато часу для запуску через їх кількість.

Що можна зробити:

1) Визначте вирішальні тести. Ці для найважливіших частин бібліотек і ті, які, швидше за все, не зможуть змінитись. Тільки тест слід виконувати завжди під час компіляції. Якщо якийсь код часто порушується, його тести повинні бути обов'язковими, навіть якщо вони потребують тривалого виконання, з іншого боку, якщо якась частина програмного забезпечення ніколи не спричиняла проблем, ви можете сміливо пропускати тести на кожній збірці.

2) Підготуйте сервер безперервної інтеграції, який буде виконувати всі тести у фоновому режимі. Ви вирішуєте будувати щогодини або будувати після кожної фіксації (друга має сенс лише в тому випадку, якщо ви хочете автоматично виявити, чиї вчинки спричинили проблеми).


0

Проблеми, які я бачив:

a) Використання IOC для складання тестових елементів. 70 секунд -> 7 секунд, вийнявши контейнер.

б) Не знущаються з усіх класів. Проводьте тести одиниці на одному елементі. Я бачив тести, які проходять через пару занять. Це не одиничні тести і набагато більше шансів зламатись.

в) Профілюйте їх, щоб з’ясувати, що відбувається. Я виявив, що конструктор будує речі, які мені не потрібні, тому я локалізував його і скоротив час роботи.

г) Профіль. можливо, код не такий хороший, і ви можете отримати деяку ефективність з огляду.

д) Усуньте залежності. Тримаючи тест у малому виконанні, ви скоротите час для завантаження. Використовуйте бібліотеку інтерфейсів та контейнери IOC для запуску остаточного рішення, але для ваших основних тестових проектів має бути визначено лише бібліотеку інтерфейсів. Це забезпечує розлуку, забезпечує простіше тестування, а також зменшує розмір друку на тестовій нозі.


0

Я відчуваю ваш біль, і я зіткнувся з кількома місцями, де швидкість складання можна значно поліпшити. Однак, число, що я рекомендую, полягає в тому, щоб виміряти детальну деталь, щоб визначити, де ваша конструкція займає найдовше. Наприклад, у мене складено близько 30 проектів, на що потрібно трохи більше хвилини. Однак це лише частина картини. Я також знаю, які проекти потребують найдовшого побудови, що допомагає зосередити мої зусилля.

Речі, які з'їдають час нарощування:

  • Завантаження пакунків (Nuget для C #, Maven для Java, Gem для Ruby тощо)
  • Копіювання великої кількості файлів у файловій системі (наприклад: файли підтримки GDAL)
  • Відкриття з'єднань з базою даних (деякі беруть на секунду за з'єднання переговори)
  • Код на основі рефлексії
  • Автогенерований код
  • Використання винятків для управління потоком програми

Бібліотеки макетів використовують або відображення, або введення коду, використовуючи бібліотеки байт-кодів, щоб генерувати макет для вас. Хоча це дуже зручно, він з'їдає час тестування. Якщо ви генеруєте макети всередині циклу у своєму тесті, це може додати вимірювану кількість часу до одиничних тестів.

Існують способи виправити проблеми:

  • Перенести тести, що включають базу даних, до інтеграції (тобто лише на сервері збирання CI)
  • Уникайте створення макетів у циклах у ваших тестах. Насправді просто уникайте циклів у ваших тестах. Можливо, ви можете отримати ті самі результати, використовуючи параметризований тест у цьому випадку.
  • Подумайте, як розділити ваше масове рішення на окремі рішення

Коли ваше рішення містить понад 100 проектів, у вас є комбінація коду бібліотеки, тестів та коду програми. Кожна з бібліотек може бути власним рішенням із пов'язаними тестами. Jet Brains Team City - це сервер збирання CI, який виступає як Nuget-сервер - і я впевнений, що це не єдиний. Це дає вам можливість переміщати ті бібліотеки, які, ймовірно, не змінюються часто до власних рішень / проектів та використовують Nuget для вирішення залежностей від коду програми. Менші рішення означають, що ви можете внести зміни до бібліотеки швидко і без суєти, і насолоджуватися перевагами головного рішення.


-1

Чи може ваше тестове середовище працювати де завгодно? Якщо це можливо, використовуйте хмарні обчислення для запуску тестів. Розподіліть тести між N віртуальними машинами. Якщо час для запуску тестів на одній машині становить T1 секунди, то час для їх запуску розділиться, T2, може наблизитися до T2 = T1 / N. (Якщо припустити, що кожен тестовий випадок займає приблизно стільки ж часу.) І вам потрібно платити за VM лише тоді, коли ви їх використовуєте. Таким чином, у вас немає куби тестових машин, які сидять десь 24/7 в якійсь лабораторії. (Мені б хотілося це зробити там, де я працюю, але ми прив’язані до конкретного обладнання. Немає VM для мене.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.