Тестування 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, якщо не знайдено

Повертає

BaseModel

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, якщо не знайдено

Повертає

BaseModel

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, якщо не знайдено

Повертає

BaseModel

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 до поля, запис уже має існувати.

Додавання буде завершено лише після збереження батьківського запису.

remove(id=None, index=None)[source]

Видаляє запис із певним індексом або з наданим ідентифікатором із поля.

clear()[source]

Видаляє всі існуючі записи в m2m

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

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

Спеціальні теги

  • standard: Усі тести Odoo, що успадковуються від BaseCase, неявно позначені тегом standard. --test-tags також за замовчуванням має значення standard.

    Це означає, що тест без тегів виконуватиметься за умовчанням, коли тести ввімкнено.

  • at_install: означає, що тест буде виконано одразу після встановлення модуля та перед встановленням інших модулів. Це неявний тег за умовчанням.

  • post_install: означає, що тест буде виконано після встановлення всіх модулів. Це те, що вам потрібно для тестів HttpCase більшість часу.

    Зауважте, що це не винятково для at_install, однак, оскільки ви, як правило, не бажаєте, щоб обидва post_install зазвичай поєднувалися з -at_install під час позначення тестового класу.

Приклади

Важливо

Тести будуть виконуватися тільки в установлених модулях. Якщо ви починаєте з чистої бази даних, вам потрібно принаймні один раз встановити модулі з перемикачем -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 веб-браузера.

../../../_images/tests.png

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

Попередження

Під час роботи набору тестів переконайтеся, що:

  • вікно браузера сфокусовано,

  • він не збільшений/зменшений. Він повинен мати рівно 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). Процес додавання нового тестового прикладу такий:

  1. створити новий файл 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');
    
    });
    });
    
  2. У 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>
    
  3. Перезапустіть сервер і оновіть my_addon або зробіть це з інтерфейсу (щоб переконатися, що новий тестовий файл завантажено)

  4. Додайте тестовий приклад після визначення набору підтестів 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);
    });
    
  5. Відвідайте /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

  1. Налаштуйте свій тур, зареєструвавши його.

    /** @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
    ]);
    
  2. Додайте будь-який крок, який хочете.

Кожен крок містить щонайменше один тригер. Ви можете скористатися попередньо визначеними кроками або написати свій власний персоналізований крок.

Ось кілька прикладів кроків:

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-адресі, а потім вибравши Почати тур у меню налагодження та вибравши тур:

../../../_images/tours.png
Переваги
  • тільки запуск

  • можна використовувати на виробничих або тестових сайтах, а не лише на локальних екземплярах

  • дозволяє роботу в режимі «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()