使用單元測試保護您的代碼?
重要
本教程是 開始 教程的擴展。請確保您已經完成了該教程,并使用您構建的 estate
模塊作為本教程中練習的基礎。如果您想從一個干凈的基礎開始,請從 technical-training-solutions 存儲庫中獲取 16.0-core
分支。
參考 : `Odoo 測試框架:學習最佳實踐 <https://www.youtube.com/watch?v=JEIscps0OOQ>`__(Odoo Experience 2020)在 YouTube 上。
編寫測試是必要的,原因有很多。以下是一個非詳盡列表:
確保代碼不會在未來出現問題
定義你的代碼范圍
給出使用案例的例子
這是一種技術上記錄代碼的方式
在開始工作之前,通過定義目標來幫助你的編碼工作
運行測試?
在學習如何編寫測試之前,我們需要知道如何運行它們。
$ odoo-bin -h
Usage: odoo-bin [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
[...]
Testing Configuration:
--test-file=TEST_FILE
Launch a python test file.
--test-enable Enable unit tests.
--test-tags=TEST_TAGS
Comma-separated list of specs to filter which tests to
execute. Enable unit tests if set. A filter spec has
the format: [-][tag][/module][:class][.method] The '-'
specifies if we want to include or exclude tests
matching this spec. The tag will match tags added on a
class with a @tagged decorator (all Test classes have
'standard' and 'at_install' tags until explicitly
removed, see the decorator documentation). '*' will
match all tags. If tag is omitted on include mode, its
value is 'standard'. If tag is omitted on exclude
mode, its value is '*'. The module, class, and method
will respectively match the module name, test class
name and test method name. Example: --test-tags
:TestClass.test_func,/test_module,external Filtering
and executing the tests happens twice: right after
each module installation/update and at the end of the
modules loading. At each stage tests are filtered by
--test-tags specs and additionally by dynamic specs
'at_install' and 'post_install' correspondingly.
--screencasts=DIR Screencasts will go in DIR/{db_name}/screencasts.
--screenshots=DIR Screenshots will go in DIR/{db_name}/screenshots.
Defaults to /tmp/odoo_tests.
$ # run all the tests of account, and modules installed by account
$ # the dependencies already installed are not tested
$ # this takes some time because you need to install the modules, but at_install
$ # and post_install are respected
$ odoo-bin -i account --test-enable
$ # run all the tests in this file
$ odoo-bin --test-file=addons/account/tests/test_account_move_entry.py
$ # test tags can help you filter quite easily
$ odoo-bin --test-tags=/account:TestAccountMove.test_custom_currency_on_account_1
集成機器人?
注解
本節僅適用于Odoo員工和為 github.com/odoo
做出貢獻的人員。否則,我們強烈建議您擁有自己的CI。
編寫測試時,重要的是確保在對源代碼進行修改時始終通過測試。為了自動化這個任務,我們使用稱為持續集成(CI)的開發實踐。這就是為什么我們有一些機器人在不同的時刻運行所有測試的原因。無論您是在Odoo工作還是不在,如果您正在嘗試合并 odoo/odoo
, odoo/enterprise
, odoo/upgrade
或在odoo.sh上的任何內容,您都必須通過CI。如果您正在另一個項目上工作,您應該考慮添加自己的CI。
運行機器人?
參考 : 有關此主題的文檔可以在 Runbot FAQ 中找到。
大多數測試都在每次提交到GitHub時在 `Runbot <https://runbot.odoo.com>`__上運行。
您可以通過在runbot儀表板上進行篩選來查看提交/分支的狀態。
每個分支都創建了一個 bundle 。一個bundle由配置和批次組成。
批次 是一組構建,取決于捆綁包的參數。如果所有構建都是綠色的,則批次是綠色的(即通過測試)。
構建 是指我們啟動服務器的過程。它可以分為子構建。通常有社區版本的構建、企業版本的構建(僅在有企業分支時才能強制構建)以及分支的遷移構建。如果每個子構建都是綠色的,則構建是綠色的。
子構建 只執行完整構建的某些部分。它用于加快CI過程。通常用于將安裝后測試分成4個并行實例。如果所有測試都通過且沒有記錄錯誤/警告,則子構建為綠色。
注解
無論做了哪些修改,所有測試都會運行。糾正錯誤消息中的拼寫錯誤或重構整個模塊都會觸發相同的測試。所有模塊也將被安裝。這意味著即使Runbot是綠色的,也可能會出現問題,即您的更改依賴于模塊,而這些模塊不依賴于更改所在的模塊。
Runbot 上沒有安裝本地化模塊(即特定于某個國家/地區的模塊),除了通用模塊。一些具有外部依賴關系的模塊也可能被排除在外。
每晚都會運行額外的測試:模塊操作、本地化、單模塊安裝、多構建用于非確定性錯誤等。這些測試不會保留在標準 CI 中,以縮短執行時間。
您也可以登錄到Runbot構建的版本。有3個可用的用戶: admin
, demo
和 portal
。密碼與登錄名相同。這對于在不必本地構建的情況下快速測試不同版本的功能非常有用。完整的日志也可用;這些用于監控。
機器人杜?
在你獲得召喚robodoo的權利之前,你很可能需要獲得更多的經驗,但這里還是有一些評論。
Robodoo 是那個在你的 PR 上以標簽形式轟炸 CI 狀態的家伙,但他也是那個將你的提交友好地集成到主要倉庫中的人。
當最后一個批次變為綠色時,評審員可以要求 robodoo 合并您的 PR(這更像是一個 rebase
而不是一個 merge
)。然后它將進入合并機器人。
合并機器人?
Mergebot 是合并 PR 前的最后一個測試階段。
它將獲取您分支中尚未在目標上出現的提交,對其進行分段并重新運行測試一次,即使您只是在社區中更改某些內容,也會包括企業版。
此步驟可能會失敗并顯示“暫存失敗”錯誤消息。這可能是由于
如果您是Odoo員工,您可以在此處檢查已經在目標上的非確定性錯誤:https://runbot.odoo.com/runbot/errors
你引入的一個不確定性錯誤,在 CI 中之前沒有被檢測到
在嘗試合并之前,與另一個提交合并的不兼容性
如果您只在社區存儲庫中進行更改,則可能會與企業存儲庫不兼容
在要求合并機器人重試之前,請始終檢查問題是否來自您:將您的分支重新基于目標并在本地重新運行測試。
模塊?
由于Odoo是模塊化的,因此測試也需要是模塊化的。這意味著測試應該在添加功能的模塊中定義,而且測試不能依賴于您的模塊不依賴的模塊提供的功能。
參考 : 有關此主題的文檔可以在 特殊標記 中找到。
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install') # add `post_install` and remove `at_install`
class PostInstallTestCase(TransactionCase):
def test_01(self):
...
@tagged('at_install') # this is the default
class AtInstallTestCase(TransactionCase):
def test_01(self):
...
如果您想要測試的行為可以通過安裝另一個模塊來更改,您需要確保設置了標簽 at_install
;否則,您可以使用標簽 post_install
來加快 CI 的速度,并確保它不會在不應該更改的情況下更改。
編寫測試?
參考 : 有關此主題的文檔可以在 Python unittest 和 測試Odoo 中找到。
在編寫測試之前,請考慮以下幾點
測試應該獨立于當前數據庫中的數據(包括演示數據)
測試不應該通過留下/更改殘留數據來影響數據庫。這通常是測試框架通過回滾來完成的。因此,在測試中(也不在業務代碼的任何其他地方)絕不能調用
cr.commit
。對于一個 bug 修復,應該在應用修復前失敗,在應用修復后成功。
不要測試已經在其他地方測試過的東西;你可以相信ORM。大多數業務模塊中的測試應該只測試業務流程。
你不需要將數據刷新到數據庫中。
注解
請記住, onchange
僅適用于表單視圖,而不適用于在Python中更改屬性。這也適用于測試。如果您想模擬表單視圖,可以使用 odoo.tests.common.Form
。
測試應該在模塊的根目錄下的 tests
文件夾中。每個測試文件名應該以 test_
開頭,并在測試文件夾的 __init__.py
中導入。不應該在模塊的 __init__.py
中導入測試文件夾/模塊。
estate
├── models
│ ├── *.py
│ └── __init__.py
├── tests
│ ├── test_*.py
│ └── __init__.py
├── __init__.py
└── __manifest__.py
所有的測試都應該擴展 odoo.tests.common.TransactionCase
。通常你需要定義一個 setUpClass
和測試用例。在編寫 setUpClass
后,你可以在類中使用 env
并開始與 ORM 交互。
這些測試類是基于 unittest
Python模塊構建的。
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
from odoo.tests import tagged
# The CI will run these tests after all the modules are installed,
# not right after installing the one defining it.
@tagged('post_install', '-at_install')
class EstateTestCase(TransactionCase):
@classmethod
def setUpClass(cls):
# add env on cls and many other things
super(EstateTestCase, cls).setUpClass()
# create the data for each tests. By doing it in the setUpClass instead
# of in a setUp or in each test case, we reduce the testing time and
# the duplication of code.
cls.properties = cls.env['estate.property'].create([...])
def test_creation_area(self):
"""Test that the total_area is computed like it should."""
self.properties.living_area = 20
self.assertRecordValues(self.properties, [
{'name': ..., 'total_area': ...},
{'name': ..., 'total_area': ...},
])
def test_action_sell(self):
"""Test that everything behaves like it should when selling a property."""
self.properties.action_sold()
self.assertRecordValues(self.properties, [
{'name': ..., 'state': ...},
{'name': ..., 'state': ...},
])
with self.assertRaises(UserError):
self.properties.forbidden_action_on_sold_property()
注解
為了更好的可讀性,根據測試的范圍將測試拆分成多個文件。您還可以擁有一個公共類,大多數測試都應該從中繼承;這個公共類可以定義整個模塊的設置。例如,在 account。
Exercise
更新代碼,以便沒有人可以:
為已售出的房產創建一個報價
出售一處沒有被接受的報價的房產
并為這兩種情況創建測試。此外,檢查銷售可出售的房產后是否正確標記為已售出。
Exercise
有人在取消勾選花園復選框時,一直破壞花園區域和方向的重置。請確保不再發生此類情況。
小技巧
提示:記得上面關于 Form
的注釋。