Тестування Odoo¶
Є багато способів перевірити додаток. В Odoo ми маємо три види тестів
Модульні тести Python (див. Тестування коду Python): корисні для тестування бізнес-логіки моделі
Модульні тести JS (див. Тестування коду JS): корисні для ізольованого тестування коду javascript
Тури (див. Інтеграційне тестування): тури імітують реальну ситуацію. Вони гарантують, що частини python і javascript належним чином спілкуються одна з одною.
Тестування коду Python¶
Odoo надає підтримку для тестування модулів за допомогою Python’s unittest library.
Щоб написати тести, просто визначте підпакет tests у своєму модулі, він автоматично перевірятиметься на тестові модулі. Тестові модулі повинні мати назву, яка починатиметься з test_, і їх слід імпортувати з tests/__init__.py, напр.
your_module
├── ...
├── tests
| ├── __init__.py
| ├── test_bar.py
| └── test_foo.py
і __init__.py містить:
from . import test_foo, test_bar
Попередження
тестові модулі, які не імпортовані з tests/__init__.py, не запускатимуться
Програма тестування просто запустить будь-який тестовий приклад, як описано в офіційній документації unittest, але Odoo надає ряд утиліт і помічників, пов’язаних з тестуванням вмісту Odoo (головним чином модулів):
- class odoo.tests.TransactionCase(methodName='runTest')[source]¶
Тестовий клас, у якому всі методи тестування виконуються в одній транзакції, але кожен метод тестування виконується в підтранзакції, що керується точкою збереження. Курсор транзакції завжди закривається без фіксації змін.
Налаштування даних, спільне для всіх методів, слід виконувати в методі класу
setUpClass, щоб це було зроблено один раз для всіх методів тестування. Це корисно для тестових випадків, що містять швидкі тести, але зі значним налаштуванням бази даних, спільним для всіх випадків (складні тестові дані в базі даних).Після запуску кожен тестовий метод очищає кеш записів і кеш реєстру. Однак очищення моделей і полів реєстру не виконується. Якщо тест змінює реєстр (користувацькі моделі та/або поля), він повинен підготувати необхідне очищення (
self.registry.reset_changes()).- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого external identifier
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Повертає ідентифікатор бази даних для наданого external identifier, скорочення для
_xmlid_lookup- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- class odoo.tests.SingleTransactionCase(methodName='runTest')[source]¶
TestCase, у якому всі тестові методи виконуються в одній транзакції, транзакція починається з першого тестового методу та відкочується в кінці останнього.
- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого external identifier
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Повертає ідентифікатор бази даних для наданого external identifier, скорочення для
_xmlid_lookup- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- class odoo.tests.HttpCase(methodName='runTest')[source]¶
Транзакційний HTTP TestCase з url_open і безголовими помічниками Chrome.
- browser_js(url_path, code, ready='', login=None, timeout=60, cookies=None, error_checker=None, watch=False, cpu_throttling=None, **kw)[source]¶
Перевірте JavaScript-код, що працює у браузері.
Щоб сигналізувати про успішне перевірку, виконайте команду:
console.log('test successful'). Щоб сигналізувати про невдачу перевірку, викличіть виняток абоconsole.errorз повідомленням. Тест зупиниться, коли станеться помилка, якщоerror_checkerне визначено або повернеTrueдля цього повідомлення.- Параметри
url_path (string) – URL-шлях для завантаження сторінки браузера
code (string) – Код JavaScript, який буде виконано
ready (string) – Об’єкт JavaScript, який потрібно очікувати перед продовженням тесту
login (string) – зареєстрований користувач, який виконає тест. наприклад, „адміністратор“, „демо“
timeout (int) – максимальний час очікування завершення тесту (у секундах). За замовчуванням – 60 секунд
cookies (dict) – словник файлів cookie, які потрібно встановити перед завантаженням сторінки
error_checker – функція для фільтрації помилок. Якщо надано, функція викликається з повідомленням журналу помилок, а якщо вона повертає
False, журнал ігнорується, і тест продовжується. Якщо не надано, кожен журнал помилок запускає помилку.watch (bool) – відкрийте нове вікно браузера, щоб переглянути виконання тесту
cpu_throttling (int) – Швидкість троттлінгу процесора як фактор уповільнення (1 – немає троттлінгу, 2 – подвійне уповільнення тощо)
- browse_ref(xid)[source]¶
Повертає об’єкт запису для наданого external identifier
- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
- ref(xid)[source]¶
Повертає ідентифікатор бази даних для наданого external identifier, скорочення для
_xmlid_lookup- Параметри
xid – повний зовнішній ідентифікатор у формі
module.identifier- Raise
ValueError, якщо не знайдено
- Повертає
зареєстрований id
- odoo.tests.tagged(*tags)[source]¶
Декоратор для позначення об’єктів BaseCase тегами.
Теги зберігаються в наборі, доступ до якого можна отримати з атрибута „test_tags“.
Тег із префіксом „-“ видаляє цей тег, наприклад, щоб видалити тег „standard“.
За замовчуванням усі класи Test з odoo.tests.common мають атрибут test_tags, який за замовчуванням має значення „standard“ та „at_install“.
Під час використання успадкування класів теги успадковуються.
За замовчуванням тести запускаються один раз відразу після встановлення відповідного модуля. Тестові приклади також можна налаштувати для запуску після встановлення всіх модулів, а не для запуску одразу після встановлення модуля:
# coding: utf-8
from odoo.tests import HttpCase, tagged
# This test should only be executed after all modules have been installed.
@tagged('-at_install', 'post_install')
class WebsiteVisitorTests(HttpCase):
def test_create_visitor_on_tracked_page(self):
Page = self.env['website.page']
Найпоширеніша ситуація - використовувати TransactionCase та перевіряти властивість моделі в кожному методі:
class TestModelA(TransactionCase):
def test_some_action(self):
record = self.env['model.a'].create({'field': 'value'})
record.some_action()
self.assertEqual(
record.field,
expected_field_value)
# other tests...
Примітка
Методи тестування мають починатися з test_
- class odoo.tests.Form(record, view=None)[source]¶
Реалізація представлення форми на стороні сервера (частково)
Реалізує більшу частину потоку маніпуляцій «представлення форми», щоб тести на стороні сервера могли більш правильно відображати поведінку, яка спостерігалася б під час маніпулювання інтерфейсом:
викликати відповідні зміни на «creation»;
викликати відповідні зміни в полях налаштувань;
належним чином обробляти значення за замовчуванням та зміни onchanges навколо полів x2many.
Збереження форми повертає поточний запис (тобто створений запис, якщо форма знаходиться в режимі створення). До нього також можна отримати доступ як
form.record, але лише тоді, коли у формі немає змін, що очікують на розгляд.Звичайні поля можна просто призначити безпосередньо формі. У випадку полів
Many2oneможна призначити набір записів:# empty recordset => creation mode f = Form(self.env['sale.order']) f.partner_id = a_partner so = f.save()
Також можна використовувати форму як менеджер контексту для створення або редагування запису. Зміни автоматично зберігаються в кінці області дії:
with Form(self.env['sale.order']) as f1: f1.partner_id = a_partner # f1 is saved here # retrieve the created record so = f1.record # call Form on record => edition mode with Form(so) as f2: f2.payment_term_id = env.ref('account.account_payment_term_15days') # f2 is saved here
Для полів
Many2manyсаме поле єM2MProxyі може бути змінено додаванням або видаленням записів:with Form(user) as u: u.groups_id.add(env.ref('account.group_account_manager')) u.groups_id.remove(id=env.ref('base.group_portal').id)
Зрештою,
One2manyматеріалізуються якO2MProxy.Оскільки
One2manyіснує лише через свого батьківського об’єкта, ним можна маніпулювати безпосередньо шляхом створення «підформ» за допомогою методівnew()таedit(). Зазвичай вони використовуються як менеджери контексту, оскільки зберігаються в батьківському записі:with Form(so) as f3: f.partner_id = a_partner # add support with f3.order_line.new() as line: line.product_id = env.ref('product.product_product_2') # add a computer with f3.order_line.new() as line: line.product_id = env.ref('product.product_product_3') # we actually want 5 computers with f3.order_line.edit(1) as line: line.product_uom_qty = 5 # remove support f3.order_line.remove(index=0) # SO is saved here
- Параметри
record (odoo.models.Model) – empty або singleton набір записів. Порожній набір записів переведе представлення в режим «створення» зі значень за замовчуванням, тоді як singleton набір записів переведе його в режим «редагування» та завантажить лише дані представлення.
view (int | str | odoo.model.Model) – id, xmlid або фактичний об’єкт перегляду для використання для змін і обмежень представлення. Якщо жодного не надано, просто завантажується типове представлення для моделі.
Нове в версії 12.0.
- save()[source]¶
Збережіть форму (за потреби) та поверніть поточний запис:
не зберігає поля «тольки для читання»;
не зберігає незмінені поля (під час редагування) - будь-яке присвоєння або повернення onchange позначає поле як змінене, навіть якщо йому встановлено поточне значення.
Коли нічого не потрібно зберігати, функція просто повертає поточний запис.
- Викликає
AssertionError – якщо у формі є незаповнені обов’язкові поля
- property record¶
Повертає запис, який редагується формою. Цей атрибут доступний лише для читання, і до нього можна отримати доступ лише тоді, коли у формі немає змін, що очікують на внесення.
- class odoo.tests.M2MProxy(form, field_name)[source]¶
Проксі-об’єкт для редагування значення поля типу «many2many».
Поводиться як
Sequenceнаборів записів, які можна індексувати або нарізати, щоб отримати фактичні основні набори записів.- add(record)[source]¶
Додає
recordдо поля, запис уже має існувати.Додавання буде завершено лише після збереження батьківського запису.
- class odoo.tests.O2MProxy(form, field_name)[source]¶
Proxy-об’єкт для редагування значення поля типу «one2many».
- new()[source]¶
Повертає
Формудля нового записуOne2many, правильно ініціалізованого.Форма створюється з представлення списку, якщо його можна редагувати, або з представлення форми поля в іншому випадку.
- Викликає
AssertionError – якщо поле не редагується
- edit(index)[source]¶
Повертає
Формудля редагування вже існуючого записуOne2many.Форма створюється з представлення списку, якщо його можна редагувати, або з представлення форми поля в іншому випадку.
- Викликає
AssertionError – якщо поле не редагується
- remove(index)[source]¶
Видаляє запис в
indexз батьківської форми.- Викликає
AssertionError – якщо поле не редагується
Запуск тестів¶
Тести запускаються автоматично під час встановлення або оновлення модулів, якщо --test-enable було ввімкнено під час запуску сервера Odoo.
Вибір тесту¶
В Odoo тести Python можна позначати тегами, щоб полегшити вибір тестів під час виконання тестів.
Підкласи odoo.tests.BaseCase (зазвичай через TransactionCase або HttpCase) за замовчуванням автоматично позначаються тегами standard та at_install.
Виклик¶
--test-tags можна використовувати для вибору/фільтрування тестів для запуску в командному рядку. Це передбачає --test-enable, тому немає необхідності вказувати --test-enable, коли використовуючи --test-tags.
Цей параметр за замовчуванням має значення +standard, що означає, що тести з тегом standard (явно чи неявно) виконуватимуться за замовчуванням під час запуску Odoo за допомогою --test-enable.
Під час написання тестів декоратор tagged() можна використовувати на класах тестів для додавання або видалення тегів.
Аргументами декоратора є назви тегів у вигляді рядків.
Небезпека
tagged() - це декоратор класу, він не впливає на функції чи методи
Теги можна починати зі знака мінус (-), щоб видалити їх замість додавання або вибору, наприклад, якщо ви не хочете, щоб ваш тест виконувався за замовчуванням, ви можете видалити тег standard:
from odoo.tests import TransactionCase, tagged
@tagged('-standard', 'nice')
class NiceTest(TransactionCase):
...
Цей тест не буде вибрано за замовчуванням, щоб запустити відповідний тег потрібно буде вибрати явно:
$ odoo-bin --test-tags nice
Зауважте, що виконуватимуться лише тести з тегом nice. Щоб запустити both nice і standard тести, надайте кілька значень --test-tags: у командному рядку значення є additive (ви вибираєте всі тести з any із зазначених тегів)
$ odoo-bin --test-tags nice,standard
Параметр config switch також приймає префікси + і -. Префікс + мається на увазі, тому він абсолютно необов’язковий. Префікс - (мінус) призначений для скасування вибору тестів, позначених префіксованими тегами, навіть якщо вони вибрані іншими вказаними тегами, наприклад. якщо є standard тести, які також позначені як slow, ви можете запустити всі стандартні тести except повільних:
$ odoo-bin --test-tags 'standard,-slow'
Коли ви пишете тест, який не успадковується від BaseCase, цей тест не матиме тегів за замовчуванням, вам потрібно буде додати їх явно, щоб включити тест до набору тестів за замовчуванням. Це поширена проблема під час використання простого unittest.TestCase, оскільки вони не будуть запущені:
import unittest
from odoo.tests import tagged
@tagged('standard', 'at_install')
class SmallTest(unittest.TestCase):
...
Окрім тегів, ви також можете вказати конкретні модулі, класи або функції для тестування. Повний синтаксис формату, який приймає --test-tags є:
[-][tag][/module][:class][.method]
Отже, якщо ви хочете протестувати модуль stock_account, ви можете використовувати:
$ odoo-bin --test-tags /stock_account
Якщо ви хочете протестувати певну функцію з унікальною назвою, його можна вказати безпосередньо:
$ odoo-bin --test-tags .test_supplier_invoice_forwarded_by_internal_user_without_supplier
Це еквівалентно
$ odoo-bin --test-tags /account:TestAccountIncomingSupplierInvoice.test_supplier_invoice_forwarded_by_internal_user_without_supplier
якщо назва тесту однозначна. Кілька модулів, класів і функцій можна вказати одночасно, розділивши їх символом ,, як у звичайних тегах.
Приклади¶
Важливо
Тести будуть виконуватися тільки в установлених модулях. Якщо ви починаєте з чистої бази даних, вам потрібно принаймні один раз встановити модулі з перемикачем -i. Після цього він більше не потрібен, якщо тільки вам не потрібно оновити модуль, у такому випадку можна використовувати -u. Для простоти ці перемикачі не вказані в наведених нижче прикладах.
Запустіть лише тести з модуля продажу:
$ odoo-bin --test-tags /sale
Запустіть тести з модуля продажу, але не ті, які позначені як повільні:
$ odoo-bin --test-tags '/sale,-slow'
Виконуйте лише ті тести, які є на складі або позначені як повільні:
$ odoo-bin --test-tags '-standard, slow, /stock'
Примітка
-standard неявний (не обов’язковий) і представлений для ясності
Тестування коду JS¶
Тестування складної системи є важливим запобіжним заходом для запобігання регресії та гарантії, що деякі базові функції все ще працюють. Оскільки Odoo має нетривіальну кодову базу в Javascript, її необхідно перевірити. У цьому розділі ми обговоримо практику тестування JS-коду окремо: ці тести залишаються в браузері й не повинні досягати сервера.
Набір тестів Qunit¶
Фреймворк Odoo використовує фреймворк для тестування бібліотеки QUnit як засіб виконання тестів. QUnit визначає поняття tests і modules (набору пов’язаних тестів) і надає нам веб-інтерфейс для виконання тестів.
Наприклад, ось як може виглядати тест pyUtils:
QUnit.module('py_utils');
QUnit.test('simple arithmetic', function (assert) {
assert.expect(2);
var result = pyUtils.py_eval("1 + 2");
assert.strictEqual(result, 3, "should properly evaluate sum");
result = pyUtils.py_eval("42 % 5");
assert.strictEqual(result, 2, "should properly evaluate modulo operator");
});
Основний спосіб запустити набір тестів - мати запущений сервер Odoo, а потім перейти у веб-браузері до /web/tests. Потім набір тестів буде виконано механізмом Javascript веб-браузера.
Веб-інтерфейс користувача має багато корисних функцій: він може запускати лише деякі підмодулі або фільтрувати тести, які відповідають рядку. Він може показати всі твердження, невдалі чи пройдені, повторно запустити певні тести, …
Попередження
Під час роботи набору тестів переконайтеся, що:
вікно браузера сфокусовано,
він не збільшений/зменшений. Він повинен мати рівно 100% масштабу.
Якщо це не так, деякі тести будуть невдалими без належного пояснення.
Тестування інфраструктури¶
Ось короткий огляд найважливіших частин інфраструктури тестування:
є набір ресурсів під назвою web.qunit_suite. Цей набір містить основний код (загальні активи + бекенд активів), деякі бібліотеки, засіб виконання тестів QUnit і тестові пакети, перелічені нижче.
пакет під назвою web.tests_assets включає більшість активів і утиліт, необхідних для набору тестів: користувальницькі підтвердження QUnit, помічники тестування, активи з відкладеним завантаженням тощо.
інший комплект активів, web.qunit_suite_tests, містить усі тестові сценарії. Зазвичай це місце, де тестові файли додаються до набору.
у web є controller, зіставлений з маршрутом /web/tests. Цей контролер просто рендерить шаблон web.qunit_suite.
щоб виконати тести, можна просто вказати браузер на маршрут /web/tests. У цьому випадку браузер завантажить усі ресурси, а QUnit візьме на себе роботу.
є певний код у qunit_config.js, який записує в консоль певну інформацію, коли тест проходить чи не проходить.
ми хочемо, щоб runbot також запускав ці тести, тому існує тест (у test_js.py), який просто породжує браузер і спрямовує його на URL-адресу web/tests. Зауважте, що метод browser_js створює безголовий екземпляр Chrome.
Модульність і тестування¶
Завдяки тому, як Odoo розроблено, будь-який аддон може змінювати поведінку інших частин системи. Наприклад, аддон voip може змінити віджет FieldPhone для використання додаткових функцій. Це не дуже добре з точки зору системи тестування, оскільки це означає, що тест у веб-доповненні буде невдалим щоразу, коли встановлено доповнення VoIP (зверніть увагу, що Runbot запускає тести з усіма встановленими додатками).
У той же час наша система тестування хороша, оскільки вона може виявити, коли інший модуль порушує певну основну функціональність. Повного вирішення цієї проблеми немає. Наразі ми вирішуємо це в кожному окремому випадку.
Зазвичай не варто змінювати якусь іншу поведінку. Для нашого прикладу VoIP, безперечно, чистіше додати новий віджет FieldVOIPPhone і змінити декілька представлень, які його потребують. Таким чином, віджет FieldPhone не зазнає впливу, і обидва можна перевірити.
Додавання нового тесту¶
Припустімо, що ми підтримуємо аддон my_addon і хочемо додати тест для деякого коду javascript (наприклад, якусь службову функцію myFunction, розташовану в my_addon.utils). Процес додавання нового тестового прикладу такий:
створити новий файл my_addon/static/tests/utils_tests.js. Цей файл містить базовий код для додавання модуля QUnit my_addon > utils.
odoo.define('my_addon.utils_tests', function (require) { "use strict"; var utils = require('my_addon.utils'); QUnit.module('my_addon', {}, function () { QUnit.module('utils'); }); });
У my_addon/assets.xml додайте файл до основних тестових ресурсів:
<?xml version="1.0" encoding="utf-8"?> <odoo> <template id="qunit_suite_tests" name="my addon tests" inherit_id="web.qunit_suite_tests"> <xpath expr="//script[last()]" position="after"> <script type="text/javascript" src="/my_addon/static/tests/utils_tests.js"/> </xpath> </template> </odoo>
Перезапустіть сервер і оновіть my_addon або зробіть це з інтерфейсу (щоб переконатися, що новий тестовий файл завантажено)
Додайте тестовий приклад після визначення набору підтестів utils:
QUnit.test("some test case that we want to test", function (assert) { assert.expect(1); var result = utils.myFunction(someArgument); assert.strictEqual(result, expectedResult); });
Відвідайте /web/tests/, щоб переконатися, що тест виконано
Допоміжні функції та спеціальні твердження¶
Без сторонньої допомоги досить важко протестувати деякі частини Odoo. Зокрема, представлення є складними, оскільки вони взаємодіють із сервером і можуть виконувати багато rpc, які потрібно імітувати. Ось чому ми розробили кілька спеціалізованих допоміжних функцій, розташованих у test_utils.js.
Функції макетного тестування: ці функції допомагають налаштувати тестове середовище. Найважливішим випадком використання є висміювання відповідей, наданих сервером Odoo. Ці функції використовують макетний сервер. Це клас javascript, який симулює відповіді на найпоширеніші методи моделі: read, search_read, nameget, …
Помічники DOM: корисні для симуляції подій/дій щодо певної цілі. Наприклад, testUtils.dom.click виконує клацання по цілі. Зауважте, що це безпечніше, ніж робити це вручну, оскільки воно також перевіряє, чи ціль існує та чи є видимою.
створити помічники: це, ймовірно, найважливіші функції, експортовані test_utils.js. Ці помічники корисні для створення віджетів із макетом середовища та великою кількістю дрібних деталей, щоб якомога більше імітувати реальні умови. Найважливішим, безумовно, є createView.
qunit assertions: QUnit можна розширити за допомогою спеціальних тверджень. Для Odoo ми часто тестуємо деякі властивості DOM. Ось чому ми зробили деякі твердження, щоб допомогти з цим. Наприклад, твердження containsOnce приймає widget/jQuery/HtmlElement і селектор, а потім перевіряє, чи ціль містить точно один збіг для селектора css.
Наприклад, за допомогою цих помічників ось як може виглядати простий тест форми:
QUnit.test('simple group rendering', function (assert) {
assert.expect(1);
var form = testUtils.createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</form>',
res_id: 1,
});
assert.containsOnce(form, 'table.o_inner_group');
form.destroy();
});
Зверніть увагу на використання помічника testUtils.createView та твердження containsOnce. Крім того, контролер форми було належним чином знищено в кінці тесту.
Кращі практики¶
Без особливого порядку:
усі тестові файли слід додати в some_addon/static/tests/
для виправлень помилок переконайтеся, що тест не вдається без виправлення помилок і проходить разом із ним. Це гарантує, що він дійсно працює.
намагайтеся мати мінімальну кількість коду, необхідного для роботи тесту.
зазвичай два невеликих тести краще, ніж один великий. Менший тест легше зрозуміти та виправити.
завжди прибирання після тесту. Наприклад, якщо ваш тест створює екземпляр віджета, він має знищити його в кінці.
немає необхідності мати завершене і повне покриття коду. Але додавання кількох тестів дуже допомагає: це гарантує, що ваш код не зламано повністю, і щоразу, коли помилку виправлено, набагато простіше додати тест до існуючого набору тестів.
якщо ви хочете перевірити якесь негативне твердження (наприклад, що HtmlElement не має певного класу css), тоді спробуйте додати позитивне твердження в той самий тест (наприклад, виконавши дію, яка змінює стан). Це допоможе уникнути знищення тесту в майбутньому (наприклад, якщо буде змінено клас css).
Поради¶
запуск лише одного тесту: ви можете (тимчасово!) змінити визначення QUnit.test(…) на QUnit.only(…). Це корисно, щоб переконатися, що QUnit виконує лише цей конкретний тест.
прапор налагодження: більшість функцій утиліти create мають режим налагодження (активується параметром debug: true). У цьому випадку цільовий віджет буде розміщено в DOM замість прихованого специфічного фікстура qunit, і більше інформації буде зареєстровано. Наприклад, усі імітовані мережеві комунікації будуть доступні в консолі.
під час роботи над невдалим тестом зазвичай додають позначку налагодження, а потім коментують кінець тесту (зокрема, виклик знищення). Завдяки цьому можна безпосередньо бачити стан віджета, а ще краще – маніпулювати віджетом, натискаючи/взаємодіючи з ним.
Інтеграційне тестування¶
Тестування коду Python і коду JS окремо дуже корисно, але це не доводить, що веб-клієнт і сервер працюють разом. Щоб зробити це, ми можемо написати інший тип тесту: тури. Тур – це міні-сценарій якогось цікавого ділового потоку. Він пояснює послідовність кроків, яких слід виконати. Потім виконавець тесту створить браузер PhantomJs, направить його на відповідну URL-адресу та імітує клацання та введення відповідно до сценарію.
Написання тестового туру¶
Структура¶
Щоб написати тестовий тур для your_module, почніть зі створення необхідних файлів:
your_module
├── ...
├── static
| └── tests
| └── tours
| └── your_tour.js
├── tests
| ├── __init__.py
| └── test_calling_the_tour.py
└── __manifest__.py
Тоді ви можете:
Оновіть
__manifest__.py, щоб додатиyour_tour.jsдо ресурсів.'assets': { 'web.assets_tests': [ 'your_module/static/tests/tours/your_tour.js', ], },
оновіть
__init__.pyв папціtests, щоб імпортуватиtest_calling_the_tour.
Перегляньте також
Javascript¶
Налаштуйте свій тур, зареєструвавши його.
/** @odoo-module **/ import tour from 'web_tour.tour'; tour.register('rental_product_configurator_tour', { url: '/web', // Here, you can specify any other starting url test: true, }, [ // Your sequence of steps ]);
Додайте будь-який крок, який хочете.
Кожен крок містить щонайменше один тригер. Ви можете скористатися попередньо визначеними кроками або написати свій власний персоналізований крок.
Ось кілька прикладів кроків:
Example
// First step
tour.stepUtils.showAppsMenuItem(),
// Second step
{
trigger: '.o_app[data-menu-xmlid="your_module.maybe_your_module_menu_root"]',
edition: 'community' // Optional
}, {
// Third step
},
Example
{
trigger: '.js_product:has(strong:contains(Chair floor protection)) .js_add',
extra_trigger: '.oe_advanced_configurator_modal', // This ensure we are in the wizard
},
Example
{
trigger: 'a:contains("Add a product")',
// Extra-trigger to make sure a line is added before trying to add another one
extra_trigger: '.o_field_many2one[name="product_template_id"] .o_external_button',
},
Ось кілька можливих аргументів для ваших індивідуальних кроків:
trigger: Селектор/елемент для
runдії. Тур чекатиме, поки елемент існуватиме та стане видимим, перш ніжrunдію над ним.extra_trigger: Необов’язкова вторинна умова для
виконаннякроку. Очікується, як і елемент trigger, але дія не виконуватиметься на додатковому тригері.Корисно мати передумову або дві різні та непов’язані між собою умови.
run: Дія, яку потрібно виконати з елементом trigger.
За замовчуванням намагається встановити вміст trigger як
Text, якщо цеinput, інакшеclickна ньому.Дія також може бути:
Синхронна функція, що виконується з контекстом тригера
Tip(this) та допоміжними функціями дії як параметром.Назва одного з помічників дій, який буде запущено для елемента-тригера:
clickКлацає на елементі, виконуючи всі відповідні проміжні дії.
text contentКлацає (фокусує) на елементі, а потім встановлює
contentяк значення елемента (якщо це вхідні дані), опцію (якщо це вибір) або контент.dblclick,tripleclickТе саме, що й
clickз кількома повтореннями.clicknoleaveЗа замовчуванням,
click(та його варіанти) запускатиме події «exit» на елементі-тригері (mouseout, mouseleave). Цей помічник пригнічує їх (примітка: подальші кліки на інших елементах не запускатимуть ці події неявно).text_blurСхоже на
text, але після видання є подіїfocusoutтаblur.drag_and_drop targetІмітує перетягування елемента trigger до
target.
edition: Необов’язково,
Якщо ви не вкажете випуск, крок буде активним і в community, і в enterprise.
Іноді на enterprise чи в community крок буде іншим. Потім ви можете написати два кроки: один для enterprise версії та інший для community.
Як правило, ви хочете вказати випуск для кроків, які використовують головне меню, оскільки головні меню відрізняються в community та на enterprise.
position: Необов’язково,
"top","right","bottom", або"left". Де розташувати підказку відносно цілі під час запуску інтерактивних турів.edition: Необов’язково, але рекомендовано: вміст підказки в інтерактивних турах також записується в консоль, що дуже корисно для відстеження та налагодження автоматизованих турів.
auto: Чи повинен менеджер турів чекати на виконання користувачем дії, якщо тур інтерактивний, за замовчуванням значення
false.in_modal: Якщо встановлено, елемент trigger буде шукатися лише у верхньому модальному вікні, за замовчуванням має значення
false.timeout: Час очікування до моменту
runкроку, у мілісекундах, 10000 (10 секунд).
Важливо
Останній(і) крок(и) туру завжди повинні повертати клієнта до «stable» стану (наприклад, без поточних версій) та гарантувати, що всі побічні ефекти (мережеві запити) виконано, щоб уникнути умов змагання або помилок під час демонтажу.
Перегляньте також
Python¶
Щоб розпочати тур з тесту Python, зробіть клас успадковуваним від HTTPCase та викличте start_tour:
def test_your_test(self):
# Optional Setup
self.start_tour("/web", 'your_module.your_tour_name', login="admin")
# Optional verifications
Поради щодо налагодження¶
Спостереження за екскурсіями у браузері¶
Існує два способи з різними компромісами:
watch=True¶
Під час локального запуску туру через набір тестів, параметр watch=True можна додати до виклику browser_js або start_tour:
self.start_tour("/web", code, watch=True)
Це автоматично відкриє вікно Chrome, в якому буде запущено екскурсію.
- Переваги
завжди працює, якщо тур має налаштування Python / навколишній код або кілька кроків
працює повністю автоматично (просто виберіть тест, який запускає тур)
транзакційний (має завжди бути придатним для виконання кілька разів)
- Недоліки
працює лише локально
працює лише за умови, що тест/тур може коректно виконуватися локально
Запустити через браузер¶
Тури також можна запускати через інтерфейс браузера, або викликом
odoo.startTour(tour_name);
у консолі javascript або ввімкнувши tests mode, встановивши ?debug=tests в URL-адресі, а потім вибравши Почати тур у меню налагодження та вибравши тур:
- Переваги
тільки запуск
можна використовувати на виробничих або тестових сайтах, а не лише на локальних екземплярах
дозволяє роботу в режимі «Onboarding» (ручні кроки)
- Недоліки
складніше використовувати з тестовими турами, що включають налаштування Python
може не спрацювати кілька разів залежно від побічних ефектів туру
Порада
Цей метод можна використовувати для спостереження або взаємодії з турами, які потребують налаштування Python:
додати точку зупинки python перед початком відповідного туру (виклик
start_tourабоbrowser_js)коли досягається точка зупинки, відкрийте екземпляр у вашому браузері
запустити тур
На цьому етапі налаштування Python будуть видимими для браузера, і тур зможе запуститися.
Ви можете прокоментувати виклик start_tour або browser_js, якщо хочете, щоб тест продовжувався і після нього, залежно від побічних ефектів туру.
Скріншоти та скрінкасти під час тестування browser_js¶
Під час запуску тестів, що використовують HttpCase.browser_js з командного рядка, браузер Chrome використовується в режимі headless. За замовчуванням, якщо тест не вдається, у момент невдачі робиться скріншот у форматі PNG, який записується в
'/tmp/odoo_tests/{db_name}/screenshots/'
Починаючи з Odoo 13.0, було додано два нові аргументи командного рядка для керування цією поведінкою: --screenshots і --screencasts
Кроки самоаналізу / налагодження¶
Під час спроби виправити/налагодити тур, скріншотів (у разі помилки) не обов’язково достатньо. У такому разі може бути корисним побачити, що відбувається на певному або кожному кроці.
Хоча це досить просто під час «onboarding» (оскільки вони здебільшого безпосередньо керуються користувачем), це складніше під час запуску «test» турів або під час запуску турів через набір тестів. У цьому випадку є два основні хитрощі:
Майте крок з дією
run() { debugger; }.Це можна додати до існуючого кроку або створити новий спеціальний крок. Щойно тригер кроку буде збігатися, виконання зупинить усі JavaScript-записи.
- Переваги
дуже просто
тур перезапускається, щойно ви відновите виконання
- Недоліки
взаємодія зі сторінкою обмежена, оскільки весь javascript заблоковано
налагодження всередині менеджера турів не дуже корисне
Додайте крок із тригером, який ніколи не спрацьовує, та дуже довгим
timeout.Браузер чекатиме на тригер до закінчення
timeout, перш ніж завершити тур невдало. Це дозволяє перевіряти сторінку та взаємодіяти зі нею, доки розробник не буде готовий продовжити роботу, вручну ввімкнувши тригер (тут корисний безглуздий клас, оскільки його можна запустити, додавши клас до будь-якого видимого елемента сторінки).- Переваги
дозволяє взаємодіяти зі сторінкою
легко застосувати до кроку, час очікування якого вичерпується (просто додайте довгий
timeout, а потім озирніться)немає непотрібного (для цієї ситуації) інтерфейсу налагоджувача
- Недоліки
більше ручного режиму, особливо під час відновлення
Тестування продуктивності¶
Кількість запитів¶
Один із способів перевірки продуктивності – це вимірювання запитів до бази даних. Вручну це можна перевірити за допомогою параметра командного рядка --log-sql. Якщо ви хочете встановити максимальну кількість запитів для операції, ви можете використовувати метод assertQueryCount(), інтегрований у тестові класи Odoo.
with self.assertQueryCount(11):
do_something()