Захист свого коду за допомогою модульних тестів¶
Важливо
Цей посібник є розширенням посібника Початок. Переконайтеся, що ви виконали його, і використовуйте модуль estate
, який ви створили, як основу для вправ у цьому посібнику.
Посилання: Test Framework Odoo: ознайомтеся з передовими методами (Odoo Experience 2020) на YouTube.
Написання тестів є необхідністю з кількох причин. Ось неповний список:
Переконайтеся, що він не буде зламаний у майбутньому
Визначте область дії вашого коду
Наведіть приклади випадків використання
Це один із способів технічно задокументувати код
Допомогти вам розвиватися, визначивши вашу мету, перш ніж працювати над її досягненням
Виконання тестів¶
Перш ніж знати, як писати тести, нам потрібно знати, як їх запускати.
$ odoo-bin -h
Usage: odoo-bin [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
[...]
Testing Configuration:
--test-file=TEST_FILE
Launch a python test file.
--test-enable Enable unit tests.
--test-tags=TEST_TAGS
Comma-separated list of specs to filter which tests to
execute. Enable unit tests if set. A filter spec has
the format: [-][tag][/module][:class][.method] The '-'
specifies if we want to include or exclude tests
matching this spec. The tag will match tags added on a
class with a @tagged decorator (all Test classes have
'standard' and 'at_install' tags until explicitly
removed, see the decorator documentation). '*' will
match all tags. If tag is omitted on include mode, its
value is 'standard'. If tag is omitted on exclude
mode, its value is '*'. The module, class, and method
will respectively match the module name, test class
name and test method name. Example: --test-tags
:TestClass.test_func,/test_module,external Filtering
and executing the tests happens twice: right after
each module installation/update and at the end of the
modules loading. At each stage tests are filtered by
--test-tags specs and additionally by dynamic specs
'at_install' and 'post_install' correspondingly.
--screencasts=DIR Screencasts will go in DIR/{db_name}/screencasts.
--screenshots=DIR Screenshots will go in DIR/{db_name}/screenshots.
Defaults to /tmp/odoo_tests.
$ # run all the tests of account, and modules installed by account
$ # the dependencies already installed are not tested
$ # this takes some time because you need to install the modules, but at_install
$ # and post_install are respected
$ odoo-bin -i account --test-enable
$ # run all the tests in this file
$ odoo-bin --test-file=addons/account/tests/test_account_move_entry.py
$ # test tags can help you filter quite easily
$ odoo-bin --test-tags=/account:TestAccountMove.test_custom_currency_on_account_1
Інтеграційні боти¶
Примітка
Цей розділ призначений лише для співробітників Odoo та людей, які роблять внесок у github.com/odoo
. Ми наполегливо рекомендуємо мати власну контактну особу, якщо це не так.
Під час написання тесту важливо переконатися, що він завжди проходить, коли до вихідного коду вносяться зміни. Щоб автоматизувати це завдання, ми використовуємо практику розробки під назвою Continuous Integration (CI). Ось чому у нас є боти, які виконують усі тести в різні моменти. Незалежно від того, працюєте ви в Odoo чи ні, якщо ви намагаєтесь об’єднати щось усередині odoo/odoo
, odoo/enterprise
, odoo/upgrade
або на odoo.sh, вам доведеться пройти через CI. Якщо ви працюєте над іншим проектом, вам варто подумати про додавання власного CI.
Runbot¶
Довідка: документацію, пов’язану з цією темою, можна знайти в розділі Поширені запитання щодо Runbot.
Більшість тестів виконується на Runbot кожного разу, коли на GitHub надсилається комміт.
Ви можете побачити стан коміту/розгалуження, відфільтрувавши на інформаційній панелі Runbot.
Для кожної гілки створюється бандл. Бандл складається з конфігурації та містить пакети.
Комплект - це набір збірок, залежно від параметрів комплекту. Комплект вважається зеленим (тобто проходить тести), якщо всі збірки зелені.
Збірка – це коли ми запускаємо сервер. Його можна розділити на саб-збірки. Зазвичай існують збірки для версії спільноти, корпоративної версії (тільки якщо є корпоративна філія, але ви можете примусово створити збірку) і міграція гілки. Збірка зелена, якщо кожна дочірня саб-збірка зелена.
Саб-збірка виконує лише деякі частини того, що виконує повна збірка. Він використовується для прискорення процесу CI. Зазвичай він використовується для розділення тестів після встановлення на 4 паралельні екземпляри. Допоміжна збірка зелена, якщо всі тести проходять успішно та не зареєстровано жодних помилок/попереджень.
Примітка
Всі тести виконуються незалежно від внесених змін. Виправлення помилки у повідомленні про помилку або рефакторинг цілого модуля запускає ті самі тести. Він встановить всі модулі. Це означає, що щось може не працювати, якщо Runbot зелений, але ваші зміни залежать від чогось, від чого ви не залежите.
Модулі локалізації (тобто модулі для конкретної країни) не встановлюються в Runbot (крім загального), деякі модулі із зовнішніми залежностями також можуть бути виключені.
Існує нічна збірка, яка виконує додаткові тести, такі як операції з модулями, локалізація, встановлення окремих модулів, мультизбірки для виявлення недетермінованих помилок тощо. Вони не зберігаються у стандартному CI, щоб скоротити час виконання.
Ви також можете увійти на збірці, зібраній за допомогою Runbot. Доступні 3 користувача: admin
, demo
і portal
. Пароль такий самий, як і логін. Це корисно для швидкого тестування різних версій без необхідності збирати локально. Також доступні повні логи, які використовуються для моніторингу.
Robodoo¶
Швидше за все, вам доведеться набратися трохи більше досвіду, перш ніж отримати права викликати robodoo, але все ж є кілька зауважень.
Robodoo - це хлопець, який розсилає статус CI у вигляді тегів у ваших PR, але він також є хлопцем, який люб’язно інтегрує ваші комміти в основні репозиторії.
Коли остання партія стане зеленою, рецензент може попросити robodoo об’єднати ваш PR (насправді це більше схоже на rebase
, ніж на merge
). Після цього він потрапить до mergebot.
Mergebot¶
Mergebot є останнім етапом тестування перед об’єднанням PR.
Він візьме коміти у вашій гілці, яких ще немає на цільовій платформі, ініціює це та повторно запустить тести ще раз, включаючи enterprise версію, навіть якщо ви змінюєте лише щось у community.
Цей крок може бути невдалим із повідомленням про помилку Staging failed
. Це може бути через
недетермінована помилка, яка вже є цільовою. Якщо ви є співробітником Odoo, ви можете перевірити їх тут: https://runbot.odoo.com/runbot/errors
недетерміновану помилку, яку ви представили, але не виявили раніше в CI
несумісність з іншим комітом, об’єднаним прямо перед тим, і тим, що ви намагаєтесь об’єднати
несумісність із enterprise репозиторієм, якщо ви внесли зміни лише в сховище community
Завжди перевіряйте, чи проблема не походить від вас, перш ніж попросити бота для злиття повторити спробу: перебазуйте свою гілку на ціль і повторно запустіть тести локально.
Модулі¶
Оскільки Odoo є модульним, тести також мають бути модульними. Це означає, що тести визначаються в модулі, який додає функціональність, яку ви додаєте, і тести не можуть залежати від функціональності, що надходить із модулів, від яких ваш модуль не залежить.
Довідка: документацію, пов’язану з цією темою, можна знайти в Спеціальні теги.
from odoo.tests.common import SavepointCase
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install') # add `post_install` and remove `at_install`
class PostInstallTestCase(SavepointCase):
def test_01(self):
...
@tagged('at_install') # this is the default
class AtInstallTestCase(SavepointCase):
def test_01(self):
...
Якщо поведінку, яку ви хочете перевірити, можна змінити встановленням іншого модуля, вам потрібно переконатися, що встановлено тег at_install
; інакше ви можете використати тег post_install
, щоб пришвидшити CI та переконатися, що він не змінюється, якщо це не повинно бути.
Написання тесту¶
Довідка: документацію, пов’язану з цією темою, можна знайти в Python unittest та Testing Odoo.
Ось кілька речей, які слід взяти до уваги перед написанням тесту
Тести повинні бути незалежними від даних, які зараз містяться в базі даних (включаючи демонстраційні дані)
Тести не повинні впливати на базу даних, залишаючи/змінюючи залишкові дані. Зазвичай це робить тестовий фреймворк, виконуючи відкат. Тому ви ніколи не повинні викликати
cr.commit
у тесті (ані будь-де в бізнес-коді).Для виправлення помилки тест має бути невдалим перед застосуванням виправлення та пройти після.
Не тестуйте те, що вже перевірено деінде; ви можете довіряти ORM. Більшість тестів у бізнес-модулях мають перевіряти лише бізнес-потоки.
Вам не потрібно скидати дані в базу даних.
Примітка
Пам’ятайте, що onchange
застосовується лише у представленнях форм, а не шляхом зміни атрибутів у python. Це також стосується і тестів. Якщо ви хочете емулювати представлення форми, ви можете використовувати odoo.tests.common.Form
.
Тести мають бути в папці tests
у корені вашого модуля. Ім’я кожного тестового файлу має починатися з test_
і імпортуватися в __init__.py
папки test. Ви не повинні імпортувати тестову папку/модуль у __init__.py
модуля.
estate
├── models
│ ├── *.py
│ └── __init__.py
├── tests
│ ├── test_*.py
│ └── __init__.py
├── __init__.py
└── __manifest__.py
Примітка
Деякі старі тести розширюють odoo.tests.common.TransactionCase
, але вони менш масштабовані. Різниця полягає в тому, що налаштування виконується для кожного тестового методу, а не для кожного тестового класу. Змінені дані відкочуються між кожним тестом у SavepointCase
, щоб мати таку ж поведінку, як і в TransactionCase
.
Всі тести повинні розширювати odoo.tests.common.SavepointCase
. Зазвичай ви визначаєте setUpClass
і тести. Після виконання setUpClass
ви отримуєте доступ до env
класу і можете починати взаємодіяти з ORM.
Ці тестові класи створено на основі модуля python unittest
.
from odoo.tests.common import SavepointCase
from odoo.exceptions import UserError
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install')
class EstateTestCase(SavepointCase):
@classmethod
def setUpClass(cls):
# add env on cls and many other things
super(EstateTestCase, cls).setUpClass()
# create the data for each tests. By doing it in the setUpClass instead
# of in a setUp or in each test case, we reduce the testing time and
# the duplication of code.
cls.properties = cls.env['estate.property'].create([...])
def test_creation_area(self):
"""Test that the total_area is computed like it should."""
self.properties.living_area = 20
self.assertRecordValues(self.properties, [
{'name': ..., 'total_area': ...},
{'name': ..., 'total_area': ...},
])
def test_action_sell(self):
"""Test that everything behaves like it should when selling a property."""
self.properties.action_sold()
self.assertRecordValues(self.properties, [
{'name': ..., 'state': ...},
{'name': ..., 'state': ...},
])
with self.assertRaises(UserError):
self.properties.forbidden_action_on_sold_property()
Примітка
Для кращої читабельності розбийте тести на декілька файлів залежно від обсягу тестів. Ви також можете створити загальний клас, від якого має успадковуватися більшість тестів; цей загальний клас може визначати всі налаштування модуля. Наприклад, у account.
Exercise
Переконайтеся, що ніхто не може створити пропозицію на проданий об’єкт, і створіть для цього тест.
Exercise
Хтось постійно порушує скидання Garden Area та Orientation, коли ви знімаєте прапорець у полі Garden. Слідкуйте, щоб це не повторилося.
Порада
Порада: запам’ятайте примітку про Форму
трохи вище.