測試 Odoo?

有很多測試應用程序的方法。在Odoo中,我們有三種測試方式。

  • Python單元測試(參見 測試Python代碼 ):用于測試模型業務邏輯的有用工具

  • JS單元測試(參見 測試JS代碼):用于單獨測試JavaScript代碼

  • 游覽(參見 Integration Testing):游覽模擬真實情況。它們確保Python和JavaScript部分正確地相互通信。

測試 Python 代碼?

Odoo提供了使用 `Python的unittest庫<https://docs.python.org/3/library/unittest.html>`_測試模塊的支持。

編寫測試,只需在您的模塊中定義一個名為 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)[源代碼]?

返回提供的 外部標識符 的記錄對象

參數

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)[源代碼]?

返回提供的 外部標識符 的記錄對象

參數

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)[源代碼]?

返回提供的 外部標識符 的記錄對象

參數

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) – 空記錄集或單例記錄集??沼涗浖瘜⑹挂晥D處于“創建”模式,并觸發對 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()[源代碼]?

返回一個新的 One2many 記錄的 Form ,已經正確初始化。

如果可編輯,則從列表視圖創建表單;否則從字段的表單視圖創建。

引發

AssertionError – 如果該字段不可編輯

remove(index)[源代碼]?

從父表單中刪除索引為 index 的記錄。

引發

AssertionError – 如果該字段不可編輯

正在運行測試?

如果在啟動Odoo服務器時啟用了 --test-enable ,則在安裝或更新模塊時會自動運行測試。

測試選擇?

在Odoo中,可以為Python測試打標簽,以便在運行測試時方便地選擇測試。

通常通過 TransactionCase 、 SavepointCaseHttpCase 的方式, odoo.tests.common.BaseCase 的子類會默認自動標記為 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 的測試將被執行。要運行 同時包含 nicestandard 測試,請在命令行中提供多個值給 --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 : 所有繼承自 BaseCase 的 Odoo 測試都隱式標記為 standard。 --test-tags 也默認為 standard 。

    這意味著當測試被啟用時,默認情況下將執行未標記的測試。

  • at_install : 表示測試將在模塊安裝后立即執行,而其他模塊安裝之前。這是默認的隱式標簽。

  • post_install : 表示測試將在所有模塊安裝完成后執行。這通常是 HttpCase 測試所需的。

    請注意,這與 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 是隱式的(不是必需的),為了清晰起見而存在

測試 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瀏覽器中導航到 /web/tests 。 測試套件將由Web瀏覽器JavaScript引擎執行。

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

Web UI 有許多有用的功能:它可以運行一些子模塊,或過濾匹配某個字符串的測試。它可以顯示每個斷言,無論是失敗還是通過,重新運行特定的測試,等等。

警告

在測試套件運行時,請確保:

  • 您的瀏覽器窗口已聚焦,

  • 它沒有放大/縮小。它需要恰好100%的縮放級別。

如果不是這種情況,一些測試將會失敗,但沒有適當的解釋。

測試基礎設施?

這里是測試基礎設施最重要部分的高級概述:

  • 有一個名為 web.qunit_suite 的資產包。該包含有主要代碼(assets common + assets backend),一些庫,QUnit測試運行器以及下面列出的測試包。

  • 一個名為 web.tests_assets 的 bundle 包含了測試套件所需的大部分資源和工具:自定義的 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可以使用專門的斷言進行擴展。對于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 僅運行此特定測試非常有用。

  • 調試標志: 大多數創建實用函數都有一個調試模式(通過 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 ,在資源中添加 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',
},

以下是您個性化步驟的可能參數:

  • 觸發器 : 在其上執行操作的選擇器/元素。該向導將等待元素存在且可見,然后在其上執行操作。

  • extra_trigger : 步驟運行的可選次要條件。與 trigger 元素一樣等待,但不會在額外觸發器上運行操作。

    有一個前提條件或兩個不同且無關的條件是很有用的。

  • run :對 trigger 元素執行的操作。

    默認情況下,如果 triggerinput ,則嘗試將其內容設置為 Text ,否則點擊它。

    該操作也可以是:

    • 一個同步函數,以觸發器的 Tip 作為上下文( this ),并將操作助手作為參數執行。

    • 觸發器元素上將運行的動作助手之一的名稱:

      click

      單擊元素,執行所有相關的中間事件。

      text content

      點擊(聚焦)元素,然后將 content 設置為元素的值(如果是輸入框),選項(如果是下拉框)或內容。

      dblclick , tripleclick

      與多次單擊相同。

      clicknoleave

      默認情況下, click (及其變體)將在觸發元素上觸發“退出”事件(mouseout,mouseleave)。此輔助程序會抑制這些事件(注意:進一步單擊其他元素不會隱式觸發這些事件)。

      text_blur

      類似于 text ,但會在“失去焦點”和“模糊”事件后進行編輯。

      拖放 target

      模擬將 觸發器 元素拖動到 目標 上。

  • 版本 : 可選的,

    • 如果您沒有指定版本,則該步驟將在社區版和企業版中均處于活動狀態。

    • 有時,在企業版和社區版中,某個步驟可能會有所不同。您可以編寫兩個步驟,一個用于企業版,另一個用于社區版。

    • 通常,您需要為使用主菜單的步驟指定版本,因為社區版和企業版的主菜單不同。

  • position : 可選項, "top" , "right" , "bottom""left" 。在運行交互式導覽時,相對于 target 將提示工具定位在何處。

  • 內容 :可選但建議,交互式導覽中工具提示的內容,也記錄在控制臺中,非常有用于跟蹤和調試自動化導覽。

  • auto : 如果該向導是交互式的,向導管理器是否應等待用戶執行操作,默認為 false 。

  • in_modal : 如果設置, trigger 元素將僅在頂部模態窗口中搜索,默認為 false 。

  • timeout : 等待步驟可以“運行”的時間,以毫秒為單位,為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 ( 應該 始終可以多次運行)

缺點
  • 僅在本地工作

  • 僅在測試/導覽可以在本地正確運行時才有效

通過瀏覽器運行?

游覽也可以通過瀏覽器 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 ,然后四處查看)

    • 沒有無用的(對于此情況)調試器用戶界面

    缺點
    • 更加手動化,特別是在恢復時

性能測試?

查詢計數?

測試性能的一種方法是測量數據庫查詢。手動測試可以使用 --log-sql 命令行參數。如果您想為操作建立最大查詢數,可以使用集成在Odoo測試類中的 assertQueryCount() 方法。

with self.assertQueryCount(11):
    do_something()