Тестирование в Odoo

Существует множество способов протестировать приложение. В Odoo у мы используем три вида тестов

  • python unit tests: useful for testing model business logic
  • js unit tests: this is necessary to test the javascript code in isolation
  • tours: this is a form of integration testing. The tours ensure that the python and the javascript parts properly talk to each other.

Тестирование кода 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

При тестировании будет запускаться любой тест, как описано в официальной unittest documentation, но Odoo предоставляет ряд утилит и помощников, связанных с тестированием контента Odoo (главным образом модулей):

class odoo.tests.common.TransactionCase(methodName='runTest')[исходный код]

TestCase, в котором каждый тестовый метод запускается в своей транзакции и с собственным курсором. Откат транзакции и закрытие курсора после каждого теста.

browse_ref(xid)[исходный код]

Возвращает объект записи для предоставленного external identifier

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
BaseModel
ref(xid)[исходный код]

Возвращает ID базы данных для предоставленного external identifier, ярлык для get_object_reference

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
Зарегистрированный id
class odoo.tests.common.SingleTransactionCase(methodName='runTest')[исходный код]

TestCase, в которой все тестовые методы запускаются в одной транзакции, транзакция запускается с помощью первого метода тестирования и откатывается в конце последнего.

browse_ref(xid)[исходный код]

Возвращает объект записи для предоставленного external identifier

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
BaseModel
ref(xid)[исходный код]

Возвращает ID базы данных для предоставленного external identifier, ярлык для get_object_reference

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
Зарегистрированный id
class odoo.tests.common.SavepointCase(methodName='runTest')[исходный код]

Аналогичен SingleTransactionCase в том, что все методы тестирования выполняются в одной транзакции, но каждый TestCase выполняется в откатанной точке сохранения (под-транзакции).

Полезно для тестовых случаев, содержащих быстрые тесты, но со значительной настройкой базы данных, общей для всех случаев (сложные тестовые данные в бд): :meth:`~.setUpClass`может использоваться для генерации тестовых данных БД один раз и тогда все TestCase будут использовать одни и те же данные, не влияя друг на друга, и без необходимости заново создавать тестовые данные.

class odoo.tests.common.HttpCase(methodName='runTest')[исходный код]

Транзакционный HTTP TestCase с url_open и Chrome headless инструментами.

browse_ref(xid)[исходный код]

Возвращает объект записи для предоставленного external identifier

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
BaseModel
phantom_js(url_path, code, ready='', login=None, timeout=60, **kw)[исходный код]

Протируетjs код запустив его в браузере - дополнительно авторизуется в системе как login, загрузит объявленную страницу url_path, дождется пока объект сможет быть выполнен и исполнит code внутри страницы

To signal success test do: console.log(„ok“)

To signal failure do: console.log(„error“)

If neither are done before timeout test fails.

ref(xid)[исходный код]

Возвращает ID базы данных для предоставленного external identifier, ярлык для get_object_reference

Параметры
xid – Полный external identifier, в форме module.identifier
Raise
ValueError если не найден
Результат
Зарегистрированный id
odoo.tests.common.tagged(*tags)[исходный код]

Декоратор для тегирования объектов BaseCase, Теги хранятся в сете, доступ к которому можно получить из атрибута «test_tags». Тег с префиксом «-» удалит тег, например, удалить тег standard. По умолчанию все классы тестов из odoo.tests.common имеют атрибут test_tags, который по умолчанию равен standard, а также техническое имя модуля. При использовании наследования классов теги НЕ наследуются.

По умолчанию тесты запускаются сразу после установки соответствующего модуля. Но можно настроить, чтобы тестирование запускалось после установки всех модулей, а не после установки одного модуля:

odoo.tests.common.at_install(flag)[исходный код]

Устанавливает состояние at-install для теста, флаг является логическим значением, указывающим, должен ли тест (True) или не должен (False) выполняться во время установки модуля.

По умолчанию тесты запускаются сразу после установки модуля перед началом установки следующего модуля.

Не рекомендуется, начиная с версии 12.0: at_install еперь флаг, вы можете использовать tagged() чтобы добавить/удалить его, более того tagged работает только на классах тестов

odoo.tests.common.post_install(flag)[исходный код]

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

По умолчанию тесты не запускаются после установки всех модулей в текущем наборе для.

Не рекомендуется, начиная с версии 12.0: post_install теперь флаг, вы можете использовать tagged() для того, чтобы добавить/удалить его, а так же tagged работает только на классах тестов

Наиболее распространенная ситуация заключается в использовании 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...
class odoo.tests.common.Form(recordp, view=None)[исходный код]

Реализация представления Form на стороне сервера (частично)

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

  • вызовите default_get и соответствующие изменения на «создание»
  • вызовите соответствующие onchanges к настройкам полей
  • правильно обработайте defaults и onchanges для x2many полей

Сохранение формы возвращает созданную запись в режиме «creation».

Регулярные поля могут быть просто назначены непосредственно в форме, для полей Many2one назначается одноэлементный набор записей

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

При редактировании записи используйте Form в качестве диспетчера контекста, чтобы автоматически сохранить ее в конце области:

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 существует только через своего родителя, он более непосредственно управляется путем создания sub-forms с помощью 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) – пустой или одиночный набор записей. Пустой набор записей переведет представление в режим «creation» и вызовет вызовы default_get и on-load onchanges, синглтон переведет его в режим «редактирования» и загрузит только данные представления.
  • view (int | str | odoo.model.Model) – id, xmlid или фактический объект представления, чтобы использовать для onchanges и ограничений представления. Если ничего не указано, просто загружается представление по умолчанию для модели.

Добавлено в версии 12.0.

save()[исходный код]

Сохраняет форму, возвращает созданную запись, если применимо

  • не сохраняет поля readonly
  • не сохраняет неизмененные поля (во время редактирования) - любое присвоение или возвращение onchange помечает поле как измененное, даже если установлено его текущее значение
Исключение
AssertionError – если в форме есть незаполненное обязательное поле
class odoo.tests.common.M2MProxy[исходный код]

Ведет себя как Sequence наборов записей, может быть проиндексирован или к нему может быть применен метод slice для получения актуальных наборов записей.

add(record)[исходный код]

Добавляет record к полю, запись должна уже существовать.

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

clear()[исходный код]

Удаляет все существующие записи m2m

remove(id=None, index=None)[исходный код]

Удаляет запись с определенным индексом или с указанным идентификатором из поля.

class odoo.tests.common.O2MProxy[исходный код]
edit(index)[исходный код]

Возвращает Form для редактирования существующей записи One2many.

Форма создается из представления списка, если он доступен для редактирования, или в противном случае из формы поля.

Исключение
AssertionError – если поле не редактируемое
new()[исходный код]

Возвращает класс Form для новой One2many ,правильно инициализированной,записи.

Форма создается из представления списка, если он доступен для редактирования, или в противном случае из формы поля.

Исключение
AssertionError – если поле не редактируемое
remove(index)[исходный код]

Удаляет запись в index из родительской формы.

Исключение
AssertionError – если поле не редактируемое

Выполнение тестов

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

Выбор теста

В Odoo тесты Python могут быть помечены тегом для облегчения выбора тестов при их запуске.

Подклассы odoo.tests.common.BaseCase (обычно через TransactionCase, SavepointCase или HttpCase) автоматически помечаются как standard, at_install и именем их исходного модуля по умолчанию.

Введение

--test-tags может использоваться для выбора/фильтрации тестов для запуска с помощью командной строки.

Эта опция по умолчанию имеет значение +standard что означает, что тесты с тегом standard (явно или неявно) будут запускаться по умолчанию при запуске Odoo с параметром --test-enable.

При написании тестов декоратор tagged() можно использовать в классах тестов для добавления или удаления тегов.

Аргументы декоратора - это имена тегов в виде строк.

Тэги могут иметь префикс со знаком минус (-) чтобы удалить их вместо добавления, например. если вы не хотите, чтобы ваш тест выполнялся по умолчанию, вы можете удалить тег standard:

from odoo.tests import TransactionCase, tagged

@tagged('-standard', 'nice')
class NiceTest(TransactionCase):
    ...

Этот тест не будет выбран по умолчанию, т.к. для его запуска соответствующий тег должен быть выбран явно:

$ odoo-bin --test-enable --test-tags nice

Обратите внимание, что будут выполняться только тесты с тегом nice. Для запуска обоих * ``nice`` и ``standard`` тестов укажите несколько значений для :option:`–test-tags <odoo-bin –test-tags>`: в командной строке, значения этого параметра являются *аддитивными (вы выбираете все тесты с любым из указанных тегов)

$ odoo-bin --test-enable --test-tags nice,standard

Параметр также принимает префиксы + `` и ``-. Префикс + `` подразумевается и, следовательно, его указывать не обязательно. Префикс ``- (минус) предназначен для отмены выбора тестов, помеченных префиксными тегами, даже если они выбраны другими указанными тегами, например, если есть standard тесты, которые также помечены как slow, вы можете запустить все стандартные тесты ,*за исключением* медленных:

$ odoo-bin --test-enable --test-tags 'standard,-slow'

Когда вы пишете тест, который не наследуется от BaseCase, этот тест не будет иметь тегов по умолчанию, вы должны добавить их явно, чтобы тест был включен в набор тестов по умолчанию , Это распространенная проблема при использовании простого unittest.TestCase, поскольку они не просто не запускаются:

import unittest
from odoo.tests import tagged

@tagged('standard', 'at_install')
class SmallTest(unittest.TestCase):
    ...

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

  • standard: Все тесты Odoo, которые наследуются от BaseCase неявно помечены как стандартные. --test-tags по умолчанию standard.

    Это означает, что тест без тегов будет выполняться по умолчанию, когда тесты включены.

  • at_install: Означает, что тест будет выполнен сразу после установки модуля и до установки других модулей. Это неявный тег по умолчанию.
  • post_install: Означает, что тест будет выполнен после установки всех модулей. Это то, что вы хотите для тестов HttpCase большую часть времени.

    Обратите внимание, что это такое поведение не эксклюзивно для at_install, однако, поскольку вы, как правило, не будете использовать оба параметра, при это post_install обычно сопряжен с -at_install при тегировании класса теста.

  • module_name: классы тестов Odoo расширяющие BaseCase еявно помечены тегом равным техническому имени их модуля. Это позволяет легко выбирать или исключать определенные модули при тестировании, например, если вы хотите запускать тесты только из модуля stock_account:

    $ odoo-bin --test-enable --test-tags stock_account
    

Примеры

Запускает тесты из модуля sale:

$ odoo-bin --test-enable --test-tags sale

Запускает тесты из модуля sale кроме тех, которые имеют тег slow:

$ odoo-bin --test-enable --test-tags 'sale,-slow'

Запускает тесты из модуля stock имеющие тег slow:

$ odoo-bin --test-enable --test-tags '-standard, slow, stock'

Тестирование JS кода

Инструмент для тестирования Qunit

Odoo Web includes means to unit-test both the core code of Odoo Web and your own javascript modules. On the javascript side, unit-testing is based on QUnit with a number of helpers and extensions for better integration with Odoo.

To see what the runner looks like, find (or start) an Odoo server with the web client enabled, and navigate to /web/tests This will show the runner selector, which lists all modules with javascript unit tests, and allows starting any of them (or all javascript tests in all modules at once).

Clicking any runner button will launch the corresponding tests in the bundled QUnit runner:

Writing a test case

This section will be updated as soon as possible.

Интеграционное тестирование

Тестирование кода Python и кода JS по отдельности очень полезно, но это не доказывает, что веб-клиент и сервер работают вместе. Чтобы сделать это, мы можем написать другой вид теста: туры. Тур - это мини-сценарий описывающи бизнес-процесс. Он объясняет последовательность шагов, которые должны быть выполнены. Затем организатор теста создаст браузер phantom_js, укажет ему правильный URL-адрес и смоделирует клики и ввод данных в соответствии со сценарием.