Тестування Odoo

Є багато способів перевірити додаток. В Odoo ми маємо три види тестів

  • Модульні тести Python (див. Тестування коду Python): корисні для тестування бізнес-логіки моделі

  • Модульні тести JS (див. Тестування коду JS): корисні для ізольованого тестування коду javascript

  • Тури (див. Інтеграційне тестування): тури імітують реальну ситуацію. Вони гарантують, що частини python і javascript належним чином спілкуються одна з одною.

Тестування коду Python

Odoo надає підтримку для тестування модулів за допомогою unittest.

Щоб написати тести, просто визначте підпакет 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.common.TransactionCase(methodName='runTest')[source]

TestCase, у якому кожен тестовий метод виконується у власній транзакції та з власним курсором. Транзакція відкочується, а курсор закривається після кожного тесту.

browse_ref(xid)[source]

Повертає об’єкт запису для наданого зовнішнього ідентифікатора

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

BaseModel

ref(xid)[source]

Повертає ID бази даних для наданого зовнішнього ідентифікатора, ярлик для get_object_reference

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

зареєстрований id

class odoo.tests.common.SingleTransactionCase(methodName='runTest')[source]

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

browse_ref(xid)[source]

Повертає об’єкт запису для наданого зовнішнього ідентифікатора

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

BaseModel

ref(xid)[source]

Повертає ID бази даних для наданого зовнішнього ідентифікатора, ярлик для get_object_reference

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

зареєстрований id

class odoo.tests.common.SavepointCase(methodName='runTest')[source]

Подібно до SingleTransactionCase тим, що всі методи тестування виконуються в одній транзакції але кожен тестовий приклад виконується всередині точки збереження з відкатом (суб-транзакція).

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

class odoo.tests.common.HttpCase(methodName='runTest')[source]

Транзакційний HTTP TestCase з url_open і безголовими помічниками Chrome.

browse_ref(xid)[source]

Повертає об’єкт запису для наданого зовнішнього ідентифікатора

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

BaseModel

browser_js(url_path, code, ready='', login=None, timeout=60, **kw)[source]

Перевірте код js, запущений у браузері - необов’язково увійдіть як „увійти“ - завантажте сторінку, надану url_path - зачекайте, поки готовий об’єкт стане доступним - eval(code) всередині сторінки

Щоб повідомити про успішний тест, виконайте: console.log(„test успішно“) Щоб повідомити про збій тесту, створіть виняток або викличте console.error

ref(xid)[source]

Повертає ID бази даних для наданого зовнішнього ідентифікатора, ярлик для get_object_reference

Параметри

xid – повний зовнішній ідентифікатор у формі module.identifier

Raise

ValueError, якщо не знайдено

Повертає

зареєстрований id

odoo.tests.common.tagged(*tags)[source]

Декоратор для позначення тегами об’єктів BaseCase. Теги зберігаються в наборі, доступ до якого можна отримати за допомогою атрибута test_tags. Тег із префіксом „-„ видаляє тег, наприклад. щоб видалити тег „standard“. За замовчуванням усі тестові класи з 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(common.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.common.Form(recordp, view=None)[source]

Реалізація представлення форми на стороні сервера (частково)

Реалізує більшу частину потоку маніпуляцій «представлення форми», щоб тести на стороні сервера могли більш правильно відображати поведінку, яка спостерігалася б під час маніпулювання інтерфейсом:

  • виклик default_get і відповідні зміни під час «створення»

  • виклик відповідних змін у полях налаштування

  • належним чином обробляти значення за замовчуванням і зміни навколо полів x2many

Збереження форми повертає створений запис у режимі створення.

Звичайні поля можна просто призначити безпосередньо до форми, для полів Many2one призначається єдиний набір записів:

# empty recordset => creation mode
f = Form(self.env['sale.order'])
f.partner_id = a_partner
so = f.save()

Під час редагування запису використання форми як менеджера контексту для автоматичного збереження в кінці області:

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:
    # 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
Параметри
  • recordp (odoo.models.Model) – порожній або єдиний набір записів. Порожній набір записів переведе подання в режим «створення» та ініціює виклики default_get і on-load onchanges, singleton переведе його в режим «редагування» та завантажить лише дані представлення.

  • view (int | str | odoo.model.Model) – id, xmlid або фактичний об’єкт перегляду для використання для змін і обмежень представлення. Якщо жодного не надано, просто завантажується типове представлення для моделі.

Нове в версії 12.0.

save()[source]

Зберігає форму, повертає створений запис, якщо це можливо

  • не зберігає поля лише для читання

  • не зберігає незмінені поля (під час редагування) — будь-яке призначення або повернення onchange позначає поле як змінене, навіть якщо встановлено його поточне значення

Викликає

AssertionError – якщо у формі є незаповнені обов’язкові поля

class odoo.tests.common.M2MProxy[source]

Поводиться як Sequence наборів записів, які можна індексувати або нарізати, щоб отримати фактичні основні набори записів.

add(record)[source]

Додає record до поля, запис уже має існувати.

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

clear()[source]

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

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

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

class odoo.tests.common.O2MProxy[source]
edit(index)[source]

Повертає Форму для редагування вже існуючого запису One2many.

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

Викликає

AssertionError – якщо поле не редагується

new()[source]

Повертає Форму для нового запису One2many, правильно ініціалізованого.

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

Викликає

AssertionError – якщо поле не редагується

remove(index)[source]

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

Викликає

AssertionError – якщо поле не редагується

Запуск тестів

Тести запускаються автоматично під час встановлення або оновлення модулів, якщо --test-enable було ввімкнено під час запуску сервера Odoo.

Вибір тесту

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

Підкласи odoo.tests.common.BaseCase (зазвичай через TransactionCase, SavepointCase або 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``, так і ``standard`` тести, надайте кілька значень :option:`–test-tags <odoo-bin –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
|   └── src
|       └── js
|           └── tours
|               └── your_tour.js
├── tests
|   ├── __init__.py
|   └── test_calling_the_tour.py
├── views
|   └── your_module_views.xml
└── __manifest__.py

Тоді ви можете:

  • додати your_tour.js як актив у your_module_views.xml.

  • оновіть __manifest__.py, щоб завантажити your_module_views.xml.

  • оновіть __init__.py в папці tests, щоб імпортувати test_calling_the_tour.

Javascript

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

    odoo.define('your_module.your_tour_name', function (require) {
        'use strict';
        var tour = require('web_tour.tour');
        tour.register('your_tour_name', {
            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: селектор/елемент/jQuery, який потрібно активувати

  • extra-trigger: додатковий селектор/елемент/jQuery, який має бути присутнім перед початком наступного кроку. Це особливо корисно, коли туру потрібно чекати, поки відкриється майстер, рядок, доданий до списку…

  • запуск: необов’язкова дія для запуску, за замовчуванням клацнути або перевірити тест, якщо ви запускаєте введення. Можлива безліч дій. Ось деякі з них: click, dbclick, tripleclick, text Example, drag_and_drop selector1 selector2

  • edition: необов’язково,

    • Якщо ви не вкажете випуск, крок буде активним і в community, і в enterprise.

    • Іноді на enterprise чи в community крок буде іншим. Потім ви можете написати два кроки: один для enterprise версії та інший для community.

    • Як правило, ви хочете вказати випуск для кроків, які використовують головне меню, оскільки головні меню відрізняються в community та на enterprise.

  • edition: необов’язково

  • id: необов’язковий

  • auto: необов’язковий

  • in_modal: необов’язковий

Порада

Інструменти розробника вашого веб-переглядача - найкращий інструмент для пошуку елемента, який ваш тур має використовувати як тригер/додатковий тригер.

Перегляньте також

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

Поради щодо налагодження

Запуск туру в режимі налагодження

Спочатку активуйте режим розробника за допомогою ?debug=tests. Потім відкрийте меню налагодження та натисніть Почати огляд. Тепер ви можете запустити свій тур звідти, натиснувши кнопку Тестувати.

Ви також можете додати цей крок у свій тур, щоб зупинити його там, де ви хочете:

{
    trigger: "body",
    run: () => {debugger}
}

Застереження

Майте на увазі, що під час запуску огляду будь-які дані, додані до налаштувань вашого тесту python, не будуть присутні в огляді, якщо ви не запустили тест, викликаючи огляд із точкою зупину.

Скріншоти та скрінкасти під час тестування browser_js

Під час запуску тестів, які використовують HttpCase.browser_js з командного рядка, браузер Chrome використовується в безголовому режимі. За замовчуванням, якщо тест не вдається, знімок екрана у форматі PNG робиться в момент невдачі та записується в нього

'/tmp/odoo_tests/{db_name}/screenshots/'

Починаючи з Odoo 13.0, було додано два нові аргументи командного рядка для керування цією поведінкою: --screenshots і --screencasts

Тестування продуктивності

Кількість запитів

Одним із способів перевірки продуктивності є вимірювання запитів до бази даних. Це можна перевірити вручну за допомогою параметра CLI --log-sql. Якщо ви хочете встановити максимальну кількість запитів для операції, ви можете використовувати метод assertQueryCount(), інтегрований у тестові класи Odoo.

with self.assertQueryCount(11):
    do_something()

Заповнення бази даних

Odoo CLI пропонує функцію заповнення бази даних.

odoo-bin populate

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

Щоб визначити цю функцію для даної моделі, можна визначити наступні методи та атрибути.

Model._populate_sizes

Повертає dict, що відображає символічні розміри ('small', 'medium', 'large') на цілі числа, надаючи мінімальну кількість записів, яку має створити _populate().

Стандартні розміри сукупності:

  • small : 10

  • medium : 100

  • large : 1000

Model._populate_dependencies

Return the list of models which have to be populated before the current one.

Тип повернення

list

Model._populate(size)[source]

Створіть записи для заповнення цієї моделі.

Параметри

size (str) – symbolic size for the number of records: 'small', 'medium' or 'large'

Model._populate_factories()[source]

Generates a factory for the different fields of the model.

factory is a generator of values (dict of field values).

Factory skeleton:

def generator(iterator, field_name, model_name):
    for counter, values in enumerate(iterator):
        # values.update(dict())
        yield values

See odoo.tools.populate for population tools and applications.

Повертає

list of pairs(field_name, factory) where factory is a generator function.

Тип повернення

list(tuple(str, generator))

Примітка

It is the responsibility of the generator to handle the field_name correctly. The generator could generate values for multiple fields together. In this case, the field_name should be more a «field_group» (should be begin by a «_»), covering the different fields updated by the generator (e.g. «_address» for a generator updating multiple address fields).

Примітка

Ви повинні визначити принаймні _populate() або _populate_factories() у моделі, щоб увімкнути заповнення бази даних.

Example model

from odoo.tools import populate

class CustomModel(models.Model)
    _inherit = "custom.some_model"
    _populate_sizes = {"small": 100, "medium": 2000, "large": 10000}
    _populate_dependencies = ["custom.some_other_model"]

    def _populate_factories(self):
        # Record ids of previously populated models are accessible in the registry
        some_other_ids = self.env.registry.populated_models["custom.some_other_model"]

        def get_some_field(values=None, random=None, **kwargs):
            """ Choose a value for some_field depending on other fields values.

                :param dict values:
                :param random: seeded :class:`random.Random` object
            """
            field_1 = values['field_1']
            if field_1 in [value2, value3]:
                return random.choice(some_field_values)
            return False

        return [
            ("field_1", populate.randomize([value1, value2, value3])),
            ("field_2", populate.randomize([value_a, value_b], [0.5, 0.5])),
            ("some_other_id", populate.randomize(some_other_ids)),
            ("some_field", populate.compute(get_some_field, seed="some_field")),
            ('active', populate.cartesian([True, False])),
        ]

    def _populate(self, size):
        records = super()._populate(size)

        # If you want to update the generated records
        # E.g setting the parent-child relationships
        records.do_something()

        return records

Інструменти наповнення

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

odoo.tools.populate.cartesian(vals, weights=None, seed=False, formatter=<function format_str>, then=None)[source]

Return a factory for an iterator of values dicts that combines all vals for the field with the other field values in input.

Параметри
  • vals (list) – list in which a value will be chosen, depending on weights

  • weights (list) – list of probabilistic weights

  • seed – optional initialization of the random number generator

  • formatter (function) – (val, counter, values) –> formatted_value

  • then (function) – if defined, factory used when vals has been consumed.

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict

odoo.tools.populate.compute(function, seed=None)[source]

Return a factory for an iterator of values dicts that computes the field value as function(values, counter, random), where values is the other field values, counter is an integer, and random is a pseudo-random number generator.

Параметри
  • function (function) – (values, counter, random) –> field_values

  • seed – optional initialization of the random number generator

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict

odoo.tools.populate.constant(val, formatter=<function format_str>)[source]

Return a factory for an iterator of values dicts that sets the field to the given value in each input dict.

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict

odoo.tools.populate.iterate(vals, weights=None, seed=False, formatter=<function format_str>, then=None)[source]

Return a factory for an iterator of values dicts that picks a value among vals for each input. Once all vals have been used once, resume as then or as a randomize generator.

Параметри
  • vals (list) – list in which a value will be chosen, depending on weights

  • weights (list) – list of probabilistic weights

  • seed – optional initialization of the random number generator

  • formatter (function) – (val, counter, values) –> formatted_value

  • then (function) – if defined, factory used when vals has been consumed.

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict

odoo.tools.populate.randint(a, b, seed=None)[source]

Return a factory for an iterator of values dicts that sets the field to the random integer between a and b included in each input dict.

Параметри
  • a (int) – minimal random value

  • b (int) – maximal random value

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict

odoo.tools.populate.randomize(vals, weights=None, seed=False, formatter=<function format_str>, counter_offset=0)[source]

Return a factory for an iterator of values dicts with pseudo-randomly chosen values (among vals) for a field.

Параметри
  • vals (list) – list in which a value will be chosen, depending on weights

  • weights (list) – list of probabilistic weights

  • seed – optional initialization of the random number generator

  • formatter (function) – (val, counter, values) –> formatted_value

  • counter_offset (int) –

Повертає

function of the form (iterator, field_name, model_name) -> values

Тип повернення

function (iterator, str, str) -> dict