测试 Odoo¶
测试应用程序的方法有很多。在 Odoo 中,我们有三种测试类型:
Python 单元测试(参见 Testing Python code ):用于测试模型的业务逻辑
JS 单元测试(参见 Testing JS code ):用于单独测试 JavaScript 代码
巡游测试(参见 Integration Testing ):模拟真实场景。它们确保 Python 和 JavaScript 部分能够正确交互。
测试 Python 代码¶
Odoo 提供了使用 Python 的 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 documentation 所述,但 Odoo 提供了许多与测试 Odoo 内容(主要是模块)相关的实用工具和助手:
- class odoo.tests.TransactionCase(methodName='runTest')[源代码]¶
测试类,其中所有测试方法都在单个事务中运行,但每个测试方法都在由保存点管理的子事务中运行。事务的游标始终在未提交的情况下关闭。
所有方法共用的数据设置应在类方法
setUpClass
中完成,以便对所有测试方法只执行一次。这对于包含快速测试但需要显著数据库设置的测试用例非常有用(复杂的数据库测试数据)。每个测试方法运行后,都会清理记录缓存和注册表缓存。但是,注册表中的模型和字段不会被清理。如果测试修改了注册表(自定义模型和/或字段),则应准备必要的清理操作(
self.registry.reset_changes()
)。- browse_ref(xid)[源代码]¶
为提供的 external identifier 返回一个记录对象
- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
- ref(xid)[源代码]¶
为提供的 external identifier 返回数据库 ID,是
_xmlid_lookup
的快捷方式- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
已注册的 ID
- class odoo.tests.SingleTransactionCase(methodName='runTest')[源代码]¶
所有测试方法都在同一事务中运行的 TestCase,事务在第一个测试方法开始时启动,并在最后一个测试方法结束时回滚。
- browse_ref(xid)[源代码]¶
为提供的 external identifier 返回一个记录对象
- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
- ref(xid)[源代码]¶
为提供的 external identifier 返回数据库 ID,是
_xmlid_lookup
的快捷方式- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
已注册的 ID
- class odoo.tests.HttpCase(methodName='runTest')[源代码]¶
带有 url_open 和 Chrome 无头助手的事务性 HTTP 测试用例。
- browser_js(url_path, code, ready='', login=None, timeout=60, cookies=None, error_checker=None, watch=False, success_signal='test successful', debug=False, cpu_throttling=None, **kw)[源代码]¶
测试在浏览器中运行的 JavaScript 代码。
要表示测试成功,请使用预期的
success_signal
调用console.log()
。默认值为 “test successful”。要表示测试失败,请抛出异常或使用消息调用console.error
。如果未定义error_checker
或对该消息返回True
,则测试将在发生失败时停止。- 参数
url_path (string) – 加载浏览器页面的 URL 路径
code (string) – 要执行的 JavaScript 代码
ready (string) – 在继续测试之前等待的 JavaScript 对象
login (string) – 将执行测试的登录用户,例如 ‘admin’、’demo’
timeout (int) – 等待测试完成的最大时间(以秒为单位)。默认值为 60 秒
cookies (dict) – 在加载页面之前要设置的 cookie 字典
error_checker – 用于过滤失败的函数。如果提供,则会用错误日志消息调用该函数,如果返回
False
,则忽略日志并继续测试。如果不提供,则每个错误日志都会触发失败。watch (bool) – 打开一个新浏览器窗口以观察测试执行
success_signal (string) – 等待的成功信号字符串,用于判断测试是否成功
debug (bool) – 自动打开一个全屏 Chrome 窗口,附带已打开的开发者工具,并在游览开始时设置调试断点。游览运行时会带有
debug=assets
查询参数。当抛出错误时,调试器会在异常处停止。cpu_throttling (int) – CPU 限制速率作为减速因子(1 表示无限制,2 表示 2 倍减速,依此类推)
- browse_ref(xid)[源代码]¶
为提供的 external identifier 返回一个记录对象
- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
- ref(xid)[源代码]¶
为提供的 external identifier 返回数据库 ID,是
_xmlid_lookup
的快捷方式- 参数
xid – 完全限定的 external identifier,格式为
模块.标识符
- 抛出
如果未找到,则抛出 ValueError
- 返回
已注册的 ID
- odoo.tests.tagged(*tags)[源代码]¶
一个用于标记 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(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: odoo.models.BaseModel, view: None | int | str | odoo.models.BaseModel = None)[源代码]¶
服务器端表单视图实现(部分)
实现大部分“表单视图”操作流程,使服务器端测试能够更准确地反映界面操作时的行为:
在“创建”时调用相关的 onchange;
在设置字段时调用相关的 onchange;
正确处理 x2many 字段周围的默认值和 onchange。
保存表单将返回当前记录(如果是创建模式,则表示创建的记录)。它也可以通过
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
仅通过其父级存在,因此可以通过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 – 空记录集或单例记录集。空记录集会使视图从默认值进入“创建”模式,而单例记录集会使视图进入“编辑”模式并仅加载视图数据。
view – 用于触发 onchange 和视图约束的 ID、XMLID 或实际视图对象。如果未提供,则简单加载模型的默认视图。
12.0 新版功能.
- save()[源代码]¶
保存表单(如有必要)并返回当前记录:
不会保存
readonly
字段;不会保存未修改的字段(在编辑期间)—— 任何赋值或 onchange 返回都会将字段标记为已修改,即使设置为其当前值也是如此。
当无需保存时,仅返回当前记录。
- 引发
AssertionError – 如果表单中存在未填写的必填字段
- property record¶
返回正在被表单编辑的记录。此属性是只读的,并且只能在表单没有待处理更改时访问。
- class odoo.tests.M2MProxy(form, field_name)[源代码]¶
用于编辑多对多字段值的代理对象。
行为类似于
Sequence
的记录集,可以对其进行索引或切片以获取实际的基础记录集。
- class odoo.tests.O2MProxy(form, field_name)[源代码]¶
用于编辑一对多字段值的代理对象。
- new()[源代码]¶
返回一个新
One2many
记录的Form
,并正确初始化。如果列表视图可编辑,则从列表视图创建表单,否则从字段的表单视图创建。
- 引发
AssertionError – 如果字段不可编辑
- edit(index)[源代码]¶
返回一个用于编辑现有
One2many
记录的Form
。如果列表视图可编辑,则从列表视图创建表单,否则从字段的表单视图创建。
- 引发
AssertionError – 如果字段不可编辑
- remove(index)[源代码]¶
从父表单中移除索引为
index
的记录。- 引发
AssertionError – 如果字段不可编辑
运行测试¶
如果在启动 Odoo 服务器时启用了 --test-enable
,则在安装或更新模块时会自动运行测试。
测试选择¶
在 Odoo 中,可以为 Python 测试添加标签,以便在运行测试时方便选择。
odoo.tests.BaseCase
的子类(通常通过 TransactionCase
或 HttpCase
)默认会自动标记为 standard
和 at_install
。
调用¶
--test-tags
可用于在命令行中选择/过滤要运行的测试。它隐含了 --test-enable
,因此在使用 --test-tags
时无需指定 --test-enable
。
此选项默认值为 +standard
,意味着当使用 --test-enable
启动 Odoo 时,默认会运行标记为 standard
的测试(显式或隐式)。
在编写测试时,可以使用 tagged()
装饰器在 测试类 上添加或移除标签。
装饰器的参数是字符串形式的标签名称。
危险
tagged()
是一个类装饰器,对函数或方法没有影响。
可以通过加前缀减号( -
)来 移除 标签,而不是添加或选择它们。例如,如果您不希望测试默认执行,可以移除 standard
标签:
from odoo.tests import TransactionCase, tagged
@tagged('-standard', 'nice')
class NiceTest(TransactionCase):
...
此测试默认不会被选中,要运行它需要显式选择相关标签:
$ odoo-bin --test-tags nice
请注意,只有标记为 nice
的测试会被执行。要同时运行 nice
和 standard
测试,请向 --test-tags
提供多个值:在命令行中,这些值是 累加的 (即选择具有 任意 指定标签的所有测试)。
$ odoo-bin --test-tags nice,standard
配置开关参数还接受 +
和 -
前缀。 +
前缀是隐含的,因此完全可选。 -
(减号)前缀用于取消选择带有该前缀标签的测试,即使它们被其他指定标签选中。例如,如果有标记为 standard
的测试同时也标记为 slow
,您可以运行所有标准测试,但排除那些慢速测试:
$ 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
),否则不再需要此开关。为简化起见,以下示例中未指定这些开关。
仅运行来自 sale 模块的测试:
$ odoo-bin --test-tags /sale
运行来自 sale 模块的测试,但不包括标记为 slow 的测试:
$ odoo-bin --test-tags '/sale,-slow'
仅运行来自 stock 模块或标记为 slow 的测试:
$ odoo-bin --test-tags '-standard, slow, /stock'
注解
-standard
是隐式的(非必需),仅为清晰起见而存在
测试 JS 代码¶
测试复杂系统是一项重要的保障措施,可以防止回归并确保某些基本功能仍然有效。由于 Odoo 在 JavaScript 中有一个非平凡的代码库,因此有必要对其进行测试。在本节中,我们将讨论隔离测试 JS 代码的做法:这些测试停留在浏览器中,不应触及服务器。
QUnit 测试套件¶
Odoo 框架使用 QUnit 库测试框架作为测试运行器。QUnit 定义了 测试 和 模块 (一组相关测试)的概念,并为我们提供了一个基于 Web 的界面来执行测试。
例如,以下是一个 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 引擎执行。

Web UI 具有许多有用的功能:它可以仅运行某些子模块,或过滤匹配字符串的测试。它可以显示每个断言(无论是失败还是通过),重新运行特定测试,等等。
警告
在测试套件运行时,请确保:
您的浏览器窗口处于焦点状态,
它没有缩放(放大或缩小)。必须保持 100% 的缩放比例。
如果不满足这些条件,某些测试可能会失败,并且不会提供明确的解释。
测试基础设施¶
以下是测试基础设施最重要部分的高级概述:
有一个名为 web.qunit_suite 的资源包。该包包含主要代码(通用资源 + 后端资源)、一些库、QUnit 测试运行器以及下面列出的测试包。
一个名为 web.tests_assets 的资源包包含了测试套件所需的大多数资源和工具:自定义 QUnit 断言、测试助手、延迟加载资源等。
另一个资源包 web.qunit_suite_tests 包含了所有的测试脚本。通常,测试文件会添加到这里。
在 Web 中有一个映射到路由 /web/tests 的 controller 。该控制器简单地渲染 web.qunit_suite 模板。
要执行测试,只需将浏览器指向路由 /web/tests。在这种情况下,浏览器会下载所有资源,然后 QUnit 接管执行。
在 qunit_config.js 中有一些代码,当测试通过或失败时,会在控制台记录一些信息。
我们希望 runbot 也能运行这些测试,因此有一个测试(在 test_js.py 中)会启动一个浏览器并将其指向 web/tests URL。请注意,browser_js 方法会启动一个无头模式的 Chrome 实例。
模块化与测试¶
由于 Odoo 的设计方式,任何插件都可以修改系统其他部分的行为。例如,voip 插件可以修改 FieldPhone 小部件以使用额外功能。从测试系统的角度来看,这并不是很好,因为这意味着当安装了 voip 插件时,addon web 中的测试将会失败(请注意,runbot 会在安装所有插件的情况下运行测试)。
同时,我们的测试系统也是有用的,因为它可以检测到其他模块何时破坏了一些核心功能。这个问题没有完全的解决方案。目前,我们根据具体情况进行处理。
通常,修改其他行为并不是一个好主意。对于我们的 voip 示例,添加一个新的 FieldVOIPPhone 小部件并修改需要它的几个视图无疑是更干净的做法。这样,FieldPhone 小部件不会受到影响,并且两者都可以进行测试。
添加新的测试用例¶
假设我们正在维护一个插件 my_addon,并且我们想为某些 JavaScript 代码(例如,位于 my_addon.utils 中的某个实用函数 myFunction)添加测试。添加新测试用例的过程如下:
创建一个新文件 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 调用,这需要模拟(mock)。这就是我们开发了一些专用辅助函数的原因,这些函数位于 test_utils.js 中。
模拟测试函数:这些函数帮助设置测试环境。最重要的用例是模拟 Odoo 服务器返回的答案。这些函数使用一个 mock server 。这是一个 JavaScript 类,用于模拟最常见的模型方法的答案,例如 read、search_read、nameget 等。
DOM 辅助工具:用于模拟某些特定目标上的事件/操作。例如,testUtils.dom.click 会在目标上执行点击操作。请注意,这样做比手动操作更安全,因为它还会检查目标是否存在且可见。
创建辅助工具:它们可能是 test_utils.js 导出的最重要的函数。这些辅助工具可用于创建一个带有模拟环境的小部件,并包含许多小细节以尽可能模拟真实条件。其中最重要的是 createView 。
qunit assertions :QUnit 可以通过专用断言进行扩展。在 Odoo 中,我们经常测试一些 DOM 属性。这就是为什么我们制作了一些断言来帮助完成这项工作。例如,containsOnce 断言接受一个小部件/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 仅运行此特定测试。
调试标志:大多数创建实用函数都有调试模式(通过 debug: true 参数激活)。在这种情况下,目标小部件将被放入 DOM 中,而不是隐藏在 qunit 特定的 fixture 中,并且会记录更多信息。例如,所有模拟的网络通信都会显示在控制台中。
在处理失败的测试时,通常会添加调试标志,然后注释掉测试的结尾部分(尤其是 destroy 调用)。这样可以直接查看小部件的状态,甚至可以通过点击或交互来操作小部件。
集成测试¶
分别测试 Python 代码和 JavaScript 代码非常有用,但这并不能证明 Web 客户端和服务器能够协同工作。为此,我们可以编写另一种测试:巡游(tours)。巡游是一个有趣的业务流程的小型场景。它描述了一系列应遵循的步骤。测试运行器将创建一个 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', ], },
更新
tests
文件夹中的__init__.py
,以导入test_calling_the_tour
。
参见
JavaScript¶
通过注册来设置您的巡游。
import tour from 'web_tour.tour'; tour.register('rental_product_configurator_tour', { url: '/web', // Here, you can specify any other starting url }, [ // 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"]',
isActive: ['community'], // Optional
run: "click",
}, {
// Third step
},
Example
{
trigger: '.js_product:has(strong:contains(Chair floor protection)) .js_add',
run: "click",
},
Example
{
isActive: ["mobile", "enterprise"],
content: "Click on Add a product link",
trigger: 'a:contains("Add a product")',
tooltipPosition: "bottom",
async run(helpers) { //Exactly the same as run: "click"
helpers.click();
}
},
以下是您个性化步骤的一些可能参数:
trigger:必需,用于在某个元素上执行
run
操作的选择器/元素。巡游将在该元素存在且可见之前等待,然后再对其执行操作。run:可选,在 trigger 元素上执行的操作。如果没有指定
run
,则不执行任何操作。操作可以是:
一个异步函数,使用触发器的
Tip
作为上下文(this
),并以操作助手作为参数执行。操作助手之一的名称,它将在触发器元素上运行:
check
确保 trigger 元素被选中。此助手仅适用于
<input[type=checkbox]>
元素。clear
清除 trigger 元素的值。此助手仅适用于
<input>
或<textarea>
元素。click
单击 trigger 元素,并执行所有相关的中间事件。
dblclick
,与
click
相同,但重复两次。drag_and_drop target
模拟将 trigger 元素拖动到
target
的操作。edit content
先
clear
元素,然后fill
填充内容。editor content
聚焦 触发 元素(wysiwyg),然后
按下
内容
。fill content
聚焦 触发 元素,然后
按下
内容
。此辅助工具仅适用于<input>
或<textarea>
元素。悬停
在 触发 元素上执行悬停操作序列。
press content
执行键盘事件序列。
range content
聚焦 触发 元素并将
内容
设置为值。此辅助工具仅适用于<input[type=range]>
元素。select value
在 触发 元素上执行选择事件序列。通过其
值
选择选项。此辅助工具仅适用于<select>
元素。selectByIndex index
与
select
相同,但通过其索引
选择选项。注意,第一个选项的索引为 0。selectByLabel label
与
select
相同,但通过其标签
选择选项。取消选中
确保 触发 元素未被选中。此辅助工具仅适用于
<input[type=checkbox]>
元素。
isActive :可选,仅当满足 isActive 数组的所有条件时激活步骤。- 浏览器处于 桌面 或 移动 模式。- 该巡游涉及 社区版 或 企业版 。- 巡游以 自动 (runbot)或 手动 (引导)模式运行。
tooltipPosition:可选,
"顶部"
、"右侧"
、"底部"
或"左侧"
。在运行交互式巡游时,提示框相对于 目标 的位置。content:可选但推荐,交互式巡游中提示框的内容,同时也会记录到控制台,因此对于跟踪和调试自动化巡游非常有用。
timeout:步骤可以
运行
前需要等待的时间,单位为毫秒,默认为 10000(10 秒)。
重要
巡游的最后一步应始终将客户端返回到“稳定”状态(例如,没有正在进行的编辑),并确保所有副作用(网络请求)已完成运行,以避免在清理过程中出现竞争条件或错误。
Python¶
要从 Python 测试启动巡游,请使类继承自 HTTPCase
,并调用 start_tour
:
def test_your_test(self):
# Optional Setup
self.start_tour("/web", "your_tour_name", login="admin")
# Optional verifications
编写引导巡游¶
结构¶
要为 your_module
编写引导巡游,请先创建所需的文件:
your_module
├── ...
├── data
| └── your_tour.xml
├── static/src/js/tours/your_tour.js
└── __manifest__.py
然后可以更新 __manifest__.py
,将 your_tour.js
添加到资源中,并将 your_tour.xml
添加到数据中。
'data': [ 'data/your_tour.xml', ], 'assets': { 'web.assets_backend': [ 'your_module/static/src/js/tours/your_tour.js', ], },
JavaScript¶
JavaScript 部分与 :ref: the test tour <testing/javascript/test>
相同。
XML¶
当您的巡游已在 JavaScript 注册表中时,可以在 XML 中创建一个 web_tour.tour
记录,如下所示:
<?xml version="1.0" encoding="utf-8"?> <odoo> <record id="your_tour" model="web_tour.tour"> <field name="name">your_tour</field> <field name="sequence">10</field> <field name="rainbow_man_message">Congrats, that was a great tour</field> </record> </odoo>
name
:必需,名称必须与 JavaScript 注册表中的名称相同。sequence
:可选;确定执行引导巡游的顺序。默认为 1000。url
:可选;启动巡游的 URL。如果url
为False
,则从注册表中获取 URL。默认为 “/odoo” 。rainbow_man_message
:可选;在巡游完成时显示彩虹人效果中的消息。如果rainbow_man_message
为False
,则没有彩虹效果。默认为<b>干得好!</b> 您已经完成了此巡游的所有步骤。
运行引导巡游¶
可以通过在用户菜单中切换 引导 选项,按照其顺序启动所有巡游。您还可以通过进入 并单击 引导 或 测试 来运行特定的引导巡游。
引导:将以交互模式执行巡游。这意味着巡游将显示需要执行的操作,并等待用户进行交互。
测试:将自动执行巡游。这意味着巡游将在用户面前执行所有步骤。
巡游录制器¶
您还可以使用巡游录制器轻松创建巡游。为此,请在引导巡游视图中单击 录制 。启动后,该工具将记录您在 Odoo 中的所有交互。
创建的巡游在引导巡游视图中标记为 自定义。这些巡游还可以导出为 JavaScript 文件,以便放入您的模块中。
调试提示¶
在浏览器中观察测试巡游¶
有三种方法,各有优劣:
watch=True
¶
当通过测试套件在本地运行巡游时,可以将 watch=True
参数添加到 browser_js
或 start_tour
调用中::
self.start_tour("/web", "your_tour_name", watch=True)
这将自动打开一个 Chrome 窗口,巡游将在其中运行。
- 优点
如果巡游包含 Python 设置/外围代码或多个步骤,则始终有效
完全自动运行(只需选择启动巡游的测试)
事务性(应该 始终能够多次运行)
- 缺点
仅在本地工作
仅在测试/巡游可以在本地正确运行时有效
debug=True
¶
当通过测试套件在本地运行巡游时,可以将 debug=True
参数添加到 browser_js
或 start_tour
调用中::
self.start_tour("/web", "your_tour_name", debug=True)
这将自动打开一个全屏的 Chrome 窗口,同时打开开发者工具,并在巡游开始时设置调试器断点。巡游将以 debug=assets
查询参数运行。当抛出错误时,调试器会在异常处停止。
- 优点
与
watch=True
模式具有相同的优点更容易调试步骤
- 缺点
仅在本地工作
仅在测试/巡游可以在本地正确运行时有效
通过浏览器运行¶
测试巡游也可以通过调用以下方式从浏览器界面启动:
odoo.startTour("tour_name");
在 JavaScript 控制台中,或者通过在 URL 中设置 ?debug=tests
来启用 测试模式 。
- 优点
更容易运行
可用于生产或测试站点,而不仅仅是本地实例
允许以“引导”模式运行(手动步骤)
- 缺点
对于涉及 Python 设置的测试巡游来说更难使用
可能由于巡游的副作用而无法多次运行
小技巧
可以使用此方法观察或与需要 Python 设置的巡游进行交互:
在相关巡游启动之前(
start_tour
或browser_js
调用)添加一个 Python 断点当命中断点时,在浏览器中打开实例
运行巡游
此时,Python 设置将对浏览器可见,巡游将能够运行。
如果希望测试在之后继续运行(取决于巡游的副作用),您可能需要注释掉 start_tour
或 browser_js
调用。
在 browser_js 测试期间的截图和录屏¶
当从命令行运行使用 HttpCase.browser_js
的测试时,Chrome 浏览器将以无头模式运行。默认情况下,如果测试失败,将在失败时刻截取 PNG 截图并保存到
'/tmp/odoo_tests/{db_name}/screenshots/'
自 Odoo 13.0 起,新增了两个命令行参数来控制此行为: --screenshots
和 --screencasts
内省/调试步骤¶
当尝试修复/调试巡游时,失败时的截图可能并不足够。在这种情况下,查看某些或每个步骤中发生的情况可能会很有用。
虽然在“引导”模式下这相当容易(因为它们主要由用户显式驱动),但在运行“测试”巡游或通过测试套件运行巡游时会更复杂。在这种情况下,有两种主要技巧:
在调试模式下(debug=True),步骤属性
break: true,
。这会在步骤开始时添加一个调试器断点。然后您可以根据需要自行添加。
- 优点
非常简单
一旦恢复执行,巡游将继续
- 缺点
由于所有 JavaScript 被阻塞,页面交互受到限制
在调试模式下(debug=True),步骤属性
pause: true,
。巡游将在步骤结束时停止。这允许开发人员检查和与页面交互,直到他们准备好通过在浏览器控制台中输入 play(); 来恢复。
- 优点
允许与页面交互
没有无用(针对此情况)的调试器界面
一个包含
run() { debugger; }
操作的步骤。这可以添加到现有步骤中,也可以是一个新的专用步骤。一旦步骤的 trigger 被匹配,执行将停止所有 JavaScript 执行。
- 优点
简单
一旦恢复执行,巡游将继续
- 缺点
由于所有 JavaScript 被阻塞,页面交互受到限制
在尝试查找步骤中定义的目标元素后触发调试器。
性能测试¶
查询计数¶
测试性能的一种方法是测量数据库查询。手动测试时,可以使用 --log-sql
命令行参数。如果要为某个操作设定最大查询数量,可以使用集成在 Odoo 测试类中的 assertQueryCount()
方法。
with self.assertQueryCount(11):
do_something()