测试 Odoo

有很多测试应用程序的方法。在Odoo中,我们有三种测试方式。

  • Python 单元测试(参见 测试 Python 代码):用于测试模型业务逻辑

  • JS 单元测试(参见 测试 JS 代码):用于独立测试 JavaScript 代码

  • Tours (see Integration Testing): tours simulate a real situation. They ensures that the python and the javascript parts properly talk to each other.

测试 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文档 _所述,但Odoo提供了一些与测试Odoo内容(主要是模块)相关的实用工具和辅助函数:

class odoo.tests.common.TransactionCase(methodName='runTest')[源代码]

在单个事务中运行所有测试方法,但每个测试方法都在由保存点管理的子事务中运行。事务的游标始终在不提交的情况下关闭。

所有方法共同的数据设置应该在类方法 setUpClass 中完成,这样它只会在所有测试方法中执行一次。这对于包含快速测试但具有与所有用例共同的重要数据库设置(复杂的数据库测试数据)的测试用例非常有用。

运行后,每个测试方法都会清理记录缓存和注册表缓存。但是,没有清理注册表模型和字段。如果测试修改了注册表(自定义模型和/或字段),应该准备必要的清理工作(self.registry.reset_changes())。

browse_ref(xid)[源代码]

Returns a record object for the provided external identifier

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

BaseModel

ref(xid)[源代码]

返回提供的 外部标识符 的数据库 ID,是 _xmlid_lookup 的快捷方式

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

已注册的ID

class odoo.tests.common.SingleTransactionCase(methodName='runTest')[源代码]

这是一个测试用例,其中所有测试方法都在同一个事务中运行,事务从第一个测试方法开始,并在最后一个测试方法结束时回滚。

browse_ref(xid)[源代码]

Returns a record object for the provided external identifier

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

BaseModel

ref(xid)[源代码]

返回提供的 外部标识符 的数据库 ID,是 _xmlid_lookup 的快捷方式

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

已注册的ID

class odoo.tests.common.SavepointCase(methodName='runTest')[源代码]
class odoo.tests.common.HttpCase(methodName='runTest')[源代码]

带有url_open和Chrome无头浏览器助手的事务性HTTP测试用例。

browse_ref(xid)[源代码]

Returns a record object for the provided external identifier

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

BaseModel

browser_js(url_path, code, ready='', login=None, timeout=60, cookies=None, error_checker=None, watch=False, **kw)[源代码]

在浏览器中运行测试js代码 - 可选地作为’login’进行日志记录 - 通过url_path加载页面 - 等待ready对象可用 - 在页面内部评估代码 - 如果watch为True,则打开另一个Chrome窗口以观察代码执行

要表示测试成功,请执行:console.log(‘测试成功’)。要表示测试失败,请引发异常或使用消息调用console.error。如果未定义error_checker或该消息返回True,则测试将在发生失败时停止。

ref(xid)[源代码]

返回提供的 外部标识符 的数据库 ID,是 _xmlid_lookup 的快捷方式

参数

xid – 完全限定的 外部标识符,格式为 module.identifier

抛出

如果未找到则引发 ValueError

返回

已注册的ID

odoo.tests.common.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(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)[源代码]

服务器端表单视图实现(部分)

实现了大部分“表单视图”操作流程,使得服务器端测试可以更准确地反映在操作界面时观察到的行为:

  • 在”创建”时调用default_get和相关的onchanges

  • 在设置字段时调用相关的onchange函数

  • 正确处理关于x2many字段的默认值和onchange

如果处于创建模式,保存表单将返回创建的记录。

常规字段可以直接分配给表单,对于 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 的调用,单例记录集将使其处于“编辑”模式并仅加载视图的数据。

  • view (int | str | odoo.model.Model) – 用于onchanges和视图约束的id、xmlid或实际视图对象。如果没有提供,则仅加载模型的默认视图。

12.0 新版功能.

save()[源代码]

保存表单,如适用,返回创建的记录

  • 不保存 readonly 字段

  • 在编辑期间不保存未修改的字段——任何赋值或onchange返回都会将字段标记为已修改,即使设置为其当前值

引发

AssertionError – 如果表单中有任何未填写的必填字段

class odoo.tests.common.M2MProxy[源代码]

表现为 Sequence 的记录集,可以通过索引或切片获取实际的底层记录集。

add(record)[源代码]

record 添加到字段中,该记录必须已经存在。

只有在保存父记录时,添加才会最终完成。

clear()[源代码]

移除所有现有的m2m记录

remove(id=None, index=None)[源代码]

从字段中删除特定索引或提供的ID的记录。

class odoo.tests.common.O2MProxy[源代码]
edit(index)[源代码]

返回一个 Form 以编辑预先存在的 One2many 记录。

如果可编辑,则从列表视图创建表单;否则从字段的表单视图创建。

引发

AssertionError – 如果该字段不可编辑

new()[源代码]

返回一个新的 Form 对象,用于一个新的 One2many 记录,正确初始化。

如果可编辑,则从列表视图创建表单;否则从字段的表单视图创建。

引发

AssertionError – 如果该字段不可编辑

remove(index)[源代码]

从父表单中删除索引为 index 的记录。

引发

AssertionError – 如果该字段不可编辑

运行测试

当安装或更新模块时,如果在启动Odoo服务器时启用了 --test-enable ,则会自动运行测试。

测试选择

在Odoo中,可以为Python测试打标签,以便在运行测试时方便地选择测试。

通常情况下, odoo.tests.common.BaseCase 的子类(通常通过 TransactionCaseSavepointCaseHttpCase )默认会自动标记为 standardat_install

调用

--test-tags 可以用于在命令行上选择/过滤要运行的测试。它隐含了 --test-enable,因此在使用 --test-tags 时不需要指定 --test-enable

此选项默认为 +standard,意味着标记为 standard 的测试(无论是显式还是隐式标记)将在使用 --test-enable 启动 Odoo 时默认运行。

在编写测试时,可以在 测试类 上使用 tagged() 装饰器来添加或删除标签。

装饰器的参数是标签名称,以字符串形式表示。

危险

tagged() 是一个类装饰器,对函数或方法没有影响

标签可以以减号(-)作为前缀,以*删除*它们而不是添加或选择它们,例如,如果您不希望默认情况下执行您的测试,可以删除``standard``标签:

from odoo.tests import TransactionCase, tagged

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

此测试不会默认选择,需要显式选择相关标签才能运行:

$ odoo-bin --test-tags nice

请注意,只有标记为``nice``的测试将被执行。要运行``nice``和``standard``测试,请在命令行中为:option:`–test-tags <odoo-bin –test-tags>`提供多个值:值是*累加*的(您选择了具有*任何*指定标记的所有测试)

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

配置开关参数也接受 +- 前缀。 + 前缀是隐含的,因此完全可选。 - (减号)前缀用于取消选择带有前缀标签的测试,即使它们被其他指定的标签选择,例如,如果有标记为 slowstandard 测试,您可以运行所有标准测试 除了 慢速测试:

$ 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:所有继承自:class:~odoo.tests.common.BaseCase`的Odoo测试都隐式标记为standard。:option:–test-tags <odoo-bin –test-tags>`也默认为``standard``。

    这意味着当测试被启用时,默认情况下将执行未标记的测试。

  • at_install: Means that the test will be executed right after the module installation and before other modules are installed. This is a default implicit tag.

  • post_install: Means that the test will be executed after all the modules are installed. This is what you want for HttpCase tests most of the time.

    请注意,这与 at_install 不是互斥的 ,然而,由于通常不会同时使用两者,当标记一个测试类时, post_install 通常与 -at_install 配对使用。

示例

重要

只有已安装的模块才会执行测试。如果您从一个干净的数据库开始,您需要使用 -i 开关至少安装一次模块。之后就不再需要了,除非您需要升级模块,在这种情况下可以使用 -u。为简单起见,这些开关在下面的示例中没有指定。

仅运行销售模块的测试:

$ odoo-bin --test-tags /sale

运行销售模块的测试,但不运行标记为slow的测试:

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

仅运行库存中的测试或标记为slow的测试:

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

注解

-standard is implicit (not required), and present for clarity

测试 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引擎执行。

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

Web UI 有许多有用的功能:它可以运行一些子模块,或过滤匹配某个字符串的测试。它可以显示每个断言,无论是失败还是通过,重新运行特定的测试,等等。

警告

在测试套件运行时,请确保:

  • 您的浏览器窗口已聚焦,

  • 它不是放大/缩小。它需要保持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 中)简单地生成一个浏览器并将其指向 web/tests 的 URL。请注意,browser_js 方法生成一个 Chrome 无头实例。

模块化和测试

Odoo 的设计方式允许任何插件修改系统的其他部分的行为。例如, voip 插件可以修改 FieldPhone 小部件以使用额外的功能。从测试系统的角度来看,这并不是很好,因为这意味着在安装 voip 插件时,插件 web 中的测试将失败(请注意,runbot 在安装所有插件的情况下运行测试)。

同时,我们的测试系统很好,因为它可以检测到其他模块破坏了一些核心功能。这个问题没有完美的解决方案。目前,我们是根据具体情况逐个解决的。

通常情况下,修改其他行为并不是一个好主意。对于我们的 voip 示例来说,添加一个新的 FieldVOIPPhone 小部件并修改需要它的少数视图肯定更加清晰。这样, FieldPhone 小部件不会受到影响,两者都可以进行测试。

添加一个新的测试用例

假设我们正在维护一个插件 my_addon,我们想为一些JavaScript代码(例如,位于 my_addon.utils 中的一些实用函数 myFunction)添加一个测试。添加新的测试用例的过程如下:

  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服务器给出的答案。这些函数使用 mock server。这是一个模拟常见模型方法的答案的JavaScript类:read,search_read,nameget,…

  • DOM 帮助程序:用于模拟对特定目标的事件/操作。例如,testUtils.dom.click 在目标上执行单击操作。请注意,它比手动执行更安全,因为它还检查目标是否存在且可见。

  • 创建助手:它们可能是由 test_utils.js 导出的最重要的函数。这些助手用于创建一个小部件,带有模拟环境和许多小细节,以尽可能地模拟真实条件。其中最重要的当然是 createView

  • qunit assertions: QUnit can be extended with specialized assertions. For Odoo, we frequently test some DOM properties. This is why we made some assertions to help with that. For example, the containsOnce assertion takes a widget/jQuery/HtmlElement and a selector, then checks if the target contains exactly one match for the css selector.

例如,使用这些辅助工具,一个简单的表单测试可能如下所示:

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 特定装置中,并且将记录更多信息。例如,所有模拟的网络通信将在控制台中可用。

  • 在处理失败的测试时,通常会添加调试标志,然后注释测试的结尾(特别是销毁调用)。这样,就可以直接查看小部件的状态,甚至更好的是,通过点击/交互来操作小部件。

集成测试

分别测试Python代码和JS代码非常有用,但这并不能证明Web客户端和服务器能够协同工作。为了做到这一点,我们可以编写另一种测试:tours。一个tour是一种有趣的业务流程的迷你场景。它解释了应该遵循的一系列步骤。测试运行程序将创建一个PhantomJs浏览器,将其指向正确的URL,并根据场景模拟点击和输入。

编写测试向导

结构

要为 your_module 编写测试导览,请从创建所需的文件开始:

your_module
├── ...
├── static
|   └── tests
|       └── tours
|           └── your_tour.js
├── tests
|   ├── __init__.py
|   └── test_calling_the_tour.py
└── __manifest__.py

然后您可以:

  • 更新 __manifest__.py 文件,在 assets 中添加 your_tour.js

    'assets': {
        'web.assets_tests': [
            'your_module/static/tests/tours/your_tour.js',
        ],
    },
    
  • 更新 tests 文件夹中的 __init__.py 文件以导入 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: 选择器/元素,用于在其上执行操作。在执行操作之前,导览将等待元素存在且可见。

  • extra_trigger:可选的步骤“运行”的次要条件。将像**trigger**元素一样等待,但动作不会在额外的触发器上运行。

    有一个前提条件或两个不同且无关的条件是很有用的。

  • run:在*trigger*元素上执行的操作。

    默认情况下,如果 triggerinput,则尝试将其内容设置为 Text,否则点击它。

    该操作也可以是:

    • 一个函数,同步执行,以触发器的 Tip 作为上下文(this)和操作助手作为参数。

    • 触发器元素上将运行的动作助手之一的名称:

      click

      单击元素,执行所有相关的中间事件。

      text content

      点击(聚焦)元素,然后将 content 设置为元素的值(如果是输入框),选项(如果是下拉框)或内容。

      dblclick, tripleclick

      click ,但可以多次重复点击。

      clicknoleave

      默认情况下, click (及其变体)将在触发元素上触发“退出”事件(mouseout,mouseleave)。此辅助程序会抑制这些事件(注意:进一步单击其他元素不会隐式触发这些事件)。

      text_blur

      类似于 text,但在编辑后会触发 focusoutblur 事件。

      拖放 target

      模拟将 trigger 元素拖动到 target 上。

  • edition: 可选的,

    • 如果您没有指定版本,则该步骤将在社区版和企业版中均处于活动状态。

    • 有时,在企业版和社区版中,某个步骤可能会有所不同。您可以编写两个步骤,一个用于企业版,另一个用于社区版。

    • 通常,您需要为使用主菜单的步骤指定版本,因为社区版和企业版的主菜单不同。

  • position: 可选项,"top""right""bottom",或 "left"。在运行交互式导览时,相对于 target 的工具提示的位置。

  • content: 可选但建议的,交互式导览中工具提示的内容,也会记录到控制台,非常有用于跟踪和调试自动化导览。

  • auto: 是否应该等待用户执行操作,如果导览是交互式的,默认为 false

  • in_modal: 如果设置了 trigger 元素将只在顶层模态窗口中搜索,默认为 false

  • timeout: 等待步骤可以 run 的时间,以毫秒为单位,10000 (10 秒)。

重要

一个导览的最后一步应该总是将客户端返回到“稳定”状态(例如,没有进行中的编辑),并确保所有副作用(网络请求)已经完成运行,以避免拆卸期间的竞争条件或错误。

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

在本地通过测试套件运行游览时,可以在 browser_jsstart_tour 调用中添加 watch=True 参数::

self.start_tour("/web", code, watch=True)

这将自动打开一个 Chrome 窗口,其中包含正在运行的导览。

优势
  • 如果导览有 Python 设置/周围代码或多个步骤,则始终有效

  • 完全自动运行(只需选择启动导览的测试)

  • transactional (should always be runnable multiple times)

缺点
  • 仅在本地工作

  • 仅在测试/导览可以在本地正确运行时才有效

通过浏览器运行

游览也可以通过浏览器 UI 启动,可以通过调用

odoo.startTour(tour_name);

在javascript控制台中,或者通过启用 测试模式 ,在URL中设置 ?debug=tests ,然后在调试菜单中选择 开始导览 并选择一个导览:

../../../_images/tours.png
优势
  • 更容易运行

  • 可以在生产或测试站点上使用,而不仅仅是本地实例

  • 允许在“入职”模式下运行(手动步骤)

缺点
  • 在涉及 Python 设置的测试环节中使用起来更加困难

  • 根据您的副作用,可能无法多次使用

小技巧

可以使用此方法观察或与需要 Python 设置的导览进行交互:

  • 在相关的导览开始之前添加一个 python 断点 (start_tourbrowser_js 调用)

  • 当断点被触发时,在浏览器中打开实例

  • 运行导览

此时,Python设置将对浏览器可见,并且可以运行该教程。

如果您希望测试继续进行,取决于导览的副作用,您可能需要注释掉 start_tourbrowser_js 调用。

在浏览器JS测试期间进行截图和屏幕录像

在运行使用 HttpCase.browser_js 的测试时,从命令行中使用Chrome浏览器的无头模式。默认情况下,如果测试失败,将在失败时刻拍摄PNG截图并写入

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

自 Odoo 13.0 版本以来,添加了两个新的命令行参数来控制此行为: --screenshots--screencasts

内省/调试步骤

当尝试修复/调试导览时,截图(在失败时)可能不足够。在这种情况下,查看每个步骤发生的情况可能会很有用。

当在”引导”模式下时,这非常容易(因为它们大多数是由用户显式驱动的),但在运行”测试”游览或通过测试套件运行游览时,情况就更加复杂了。在这种情况下,有两个主要技巧:

  • 在一个步骤中添加一个带有 run() { debugger; } 动作的操作。

    这可以添加到现有步骤中,也可以是新的专用步骤。一旦匹配了步骤的 触发器 ,执行将停止所有JavaScript执行。

    优势
    • 非常简单

    • 一旦您恢复执行,导览将立即重新开始

    缺点
    • 由于所有 JavaScript 都被阻止,页面交互受到限制

    • 调试旅游经理内部并不是很有用

  • 添加一个步骤,其中包含一个永远不会成功的触发器和一个非常长的 timeout

    浏览器将在 timeout 之前等待 触发器 ,这允许检查和与页面交互,直到开发人员准备好恢复,通过手动启用 触发器 (在那里使用无意义的类很有用,因为它可以通过将类添加到页面上的任何可见元素来触发)。

    优势
    • 允许与页面交互

    • 易于应用于超时的步骤(只需添加一个长的 timeout ,然后四处查看)

    • 没有无用的(对于此情况)调试器用户界面

    缺点
    • 更加手动化,特别是在恢复时

性能测试

查询计数

测试性能的一种方法是测量数据库查询。手动测试可以使用 --log-sql CLI 参数。如果您想要为一个操作建立最大查询数,您可以使用 assertQueryCount() 方法,该方法集成在 Odoo 测试类中。

with self.assertQueryCount(11):
    do_something()