當咱們開發大型應用的時候,經過自動化測試能夠大幅提升應用的健壯性。每一年,odoo都會發布新版本,自動化測試對於應用的迴歸測試很是有幫助。幸運的是,odoo框架有不一樣自動化測試用例。odoo主要包括三種測試方案:javascript
本章包含:html
本章,咱們將詳細討論測試用例的狀況。爲了能覆蓋全部的應用場景,咱們建立了一個新的模型。模型以下:java
class LibraryBook(models.Model): _name = 'library.book' name = fields.Char('Title', required=True) date_release = fields.Date('Release Date') author_ids = fields.Many2many('res.partner', string='Authors') state = fields.Selection( [('draft', 'Not Available'), ('available', 'Available'), ('lost', 'Lost')], 'State', default="draft") color = fields.Integer() def make_available(self): self.write({'state': 'available'}) def make_lost(self): self.write({'state': 'lost'})
對於JavaScript的測試用例,咱們將使用第十五章中"建立用戶小部件"一節中的int_color小部件。
你能夠在 (github)[ https://github.com/PacktPublishing/Odoo-14-Development-Cookbook-Fourth-Edition/tree/master/Chapter18/00_initial_module]python
Python的測試用例用於測試業務邏輯的可用性。第五章,「服務器側開發-基礎篇」,咱們瞭解瞭如何調整現有業務邏輯。因爲對現有模型的修改有可能會破壞原有的邏輯,測試就顯得尤其重要。在本節,咱們將建立用於驗證改變圖書狀態的業務邏輯。git
from . import test_book_state
from odoo.tests.common import TransactionCase class TestBookState(TransactionCase): def setUp(self, *args, **kwargs): super(TestBookState, self).setUp(*args, **kwargs) self.test_book = self.env['library.book'].create({'name': 'Book 1'}) def test_button_available(self): '''Make available button''' self.test_book.make_available() self.assertEqual( self.test_book.state, 'available', 'Book state should be changed to available') def test_button_lost(self): '''Make lost button''' self.test_book.make_lost() self.assertEqual( self.test_book.state, 'lost', 'Book state should be changed to lost')
./odoo-bin -c server.conf -i my_library --test-enable
查看運行日誌,測試用例信息以下github
... INFO test odoo.addons.my_library.tests.test_ book_state: Starting TestBookState.test_button_available ... ... INFO test odoo.addons.my_library.tests.test_book_state: Starting TestBookState.test_button_lost ... ... INFO test odoo.modules.loading: Module my_library loaded in 0.79s (incl. 0.12s test), 179 queries (+10 test)
若是報錯,"INFO"》》"ERROR"。web
在odoo中,測試用例添加在tests/目錄。odoo將自動識別該目錄並運行測試用例。chrome
注意
咱們須要在tests/__init__.py文件中添加咱們的測試用例。數據庫
Odoo中使用了python的unittest包。詳細瞭解見 https://docs.python.org/3.5/library/ unittest.html。odoo經過對unittest的簡單封裝,實現了多個很是有幫助的類,可用於簡化測試用例。在咱們的例子中,咱們使用了TransactionCase。如今TransactionCase可在單獨的食物中執行測試用例。在測試用例執行成功後,將會回滾。意味着,下一個測試用例也將在初始化的環境下執行。
以test_開頭的類方法將被認爲是測試用例。在咱們的例子中,咱們添加了兩個測試用例。可用於檢查圖書的狀態。self.assertEqual方法可用於檢查測試用例是否運行正常。瀏覽器
重要信息
setUp()方法將在每個測試用例前執行,所以在本節,咱們添加了兩個測試用例,所以setUp()將調用兩次。TransactionCase將負責在每次測試用例執行完後進行回滾。
測試單元中還提供了以下測試類:
當咱們在啓動odoo實例時傳入--test-enabled,測試用例將在模塊完成安裝後馬上執行。若是你想在全部的模塊完成安裝後再執行,或者僅想執行某一個模塊的測試用例,可經過tagged()裝飾器實現。本章,咱們將介紹如何使用該裝飾器。
from odoo.tests.common import TransactionCase, tagged @tagged('-at_install', 'post_install') class TestBookState(TransactionCase): ···
./odoo-bin -c server.conf -i my_library --test-enable
... INFO book odoo.modules.loading: 9 modules loaded in 1.87s, 177 queries (+0 extra) ... INFO book odoo.modules.loading: Modules loaded. ... INFO book odoo.service.server: Starting post tests ... INFO book odoo.addons.my_library.tests.test_book_ state: Starting TestBookState.test_button_available ... ... INFO book odoo.addons.my_library.tests.test_book_ state: Starting TestBookState.test_button_lost ... ... INFO book odoo.service.server: 2 post-tests in 0.14s, 10 queries
如上顯示在全部模塊完成安裝後(post_install)執行測試用例。
默認,測試用例被標記爲standard, at_install及模塊的名稱。所以,若是你並無使用tagged裝飾器,將默認是如上標識。
在咱們的案例中,咱們但願在安裝全部模塊以後運行測試用例。爲此,咱們向TestBookState類添加了一個tagged()裝飾器。默認狀況下,測試用例具備at_install標記。因爲這個標記,您的測試用例將在模塊安裝後當即運行;它不會等待其餘模塊被安裝。咱們不但願這樣,因此爲了刪除at_install標記,咱們向標記函數添加了-at_install。以-爲前綴的標籤將刪除該標籤。
經過向tagged()函數添加-at_install,咱們在模塊安裝後中止了測試用例的執行。因爲咱們沒有在這裏指定任何其餘標記,測試用例將不會運行。
所以,咱們添加了一個post_install標記。這個標記指定測試用例須要在全部模塊安裝完成後運行。
如您所見,默認狀況下,全部的測試用例都是用標準標記標記的。Odoo將運行全部用標準標籤標記的測試用例,以防您不想一直運行特定的測試用例,而只想在被請求時運行測試用例。要作到這一點,你須要經過在tagged()裝飾器中添加-standard來移除standard標籤,你須要添加一個像這樣的自定義標籤:
@tagged('-standard', 'my_custom_tag') class TestClass(TransactionCase): ···
在使用--test-enable時全部非標的測試用例將不會執行。可經過--test-tags=name執行目標測試用例,以下:
./odoo-bin -c server.conf -i my_library --test-tags=my_custom_ tag
在測試用例的開發過程當中,只爲一個模塊運行測試用例是很重要的。默認狀況下,模塊的技術名稱是做爲標記添加的,所以可使用模塊的技術名稱和--test-tags選項。例如,若是你想爲my_library模塊運行測試用例,那麼你能夠這樣運行服務器:
./odoo-bin -c server.conf -i my_library --test-tags=my_library
這裏給出的命令將運行my_library模塊中測試用例,可是它仍然會根據at_install和post_install選項來決定順序。
Odoo使用Headless Chrome來執行JavaScript測試用例和tour測試用例。Headless Chrome是一種不須要完整UI就能夠運行Chrome的方法。這樣,咱們就能夠在與最終用戶相同的環境中運行JavaScript測試用例。在這個食譜中,咱們將安裝Headless Chrome和其餘包來運行JavaScript測試用例。
您將須要安裝Chrome來啓用JavaScript測試用例。對於模塊的開發,咱們主要使用桌面操做系統。所以,若是你的系統上安裝了Chrome瀏覽器,那麼就不須要單獨安裝。您可使用桌面Chrome運行客戶端測試用例。請確保您的Chrome版本高於Chrome 59。Odoo也支持Chromium瀏覽器。
小貼士
Headless Chrome客戶端測試用例在macOS和Linux上運行良好,但Odoo不支持Windows上的Headless Chrome測試用例。
當您想要在生產服務器或服務器操做系統上運行測試用例時,狀況會略有變化。服務器操做系統沒有GUI,因此你須要安裝不一樣的Chrome。若是你使用的是基於debian的操做系統,你可使用如下命令安裝Chromium:
apt-get install chromium-browser
重要信息
Ubuntu 18.04服務器版默認沒有啓用universe存儲庫。所以,有可能安裝鉻瀏覽器將顯示安裝候選錯誤。要修復此錯誤,可使用如下命令啓用universe存儲庫:sudo add-apt-repository universe。
Odoo還支持WebSockets用於JavaScript測試用例。爲此,Odoo使用websocket客戶端Python庫。要安裝它,使用如下命令:
pip3 install websocket-client
Odoo使用無頭瀏覽器進行JavaScript測試用例。這背後的緣由是它在後臺運行測試用例,因此它也能夠在服務器操做系統上運行。Headless Chrome更喜歡在後臺運行Chrome瀏覽器,而不須要打開GUI瀏覽器。Odoo在後臺打開一個Chrome標籤,並開始運行測試用例。它還使用jQuery的QUnit來進行JavaScript測試用例。在接下來的幾個食譜中,咱們將爲自定義JavaScript小部件建立一個QUnit測試用例。
對於測試用例,Odoo在一個單獨的進程中打開了Headless Chrome,因此爲了找到在這個進程中運行的測試用例的狀態,Odoo服務器使用WebSockets。websocket-client Python庫用於管理WebSockets,以便從Odoo服務器與Chrome通訊。
在Odoo中,建立新的領域或視圖是很是簡單的。只需幾行XML,就能夠定義一個新的視圖。然而,在底層,它使用了大量的JavaScript。在客戶端修改/添加新特性是複雜的,可能會破壞一些東西。大多數客戶端問題不會被注意到,由於大多數錯誤只顯示在控制檯中。所以,在Odoo中使用QUnit測試用例來檢查不一樣JavaScript組件的正確性。
按照如下步驟向int_color小部件添加JavaScript測試用例:
odoo.define('colorpicker_tests', function(require){ 'use strict'; var FormView = require('web.FormView'); var testUtils = require('web.test_utils'); Qunit.module('Color Picker Tests',{ beforeEach: function(){ this.data = { book: { fields: { name: {string:"Name", type:"char"}, color: {string:"color", type:"integer"}, }, records: [ {id:1, name:'Book 1', color: 1}, {id:2, name:'Book 2', color: 3} ] } }; } }, function(){ // 步驟2中內容 }); });
QUnit.only('int_color field test cases', async function (assert) { assert.expect(2); var form = await testUtils.createView({ View: FormView, model: 'book', data: this.data, arch: '<form string="Books">' + '<group>' + '<field name="name"/>' + '<field name="color" widget="int_color"/>' + '</group>' + '</form>', res_id: 1, }); await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_int_colorpicker .o_ color_pill').length, 10, "colorpicker should have 10 pills"); await testUtils.dom.click(form.$('.o_int_colorpicker .o_color_pill:eq(5)')); assert.strictEqual(form.$('.o_int_colorpicker .o_ color_5').hasClass('active'), true, "click on pill should make pill active"); form.destroy(); });
<template id="qunit_suite" name="colorpicker test" inherit_id="web.qunit_suite"> <xpath expr="." position="inside"> <script type="text/javascript" src="/my_library/static/tests/ colorpicker_tests.js" /> </xpath> </template>
運行測試用例
./odoo-bin -c server.conf -i my_library,web --test-enable
檢查日誌
... INFO test odoo.addons.web.tests.test_js.WebSuite: console log: "Color Picker Tests" passed 2 tests.
在odoo中,JavaScript測試用例添加在/static/tests目錄中。步驟1,咱們新增了colorpicker_tests.js文件。咱們引入了formView及test_utils。由於咱們建立的ini_color小部件是做用於form視圖的,所以咱們還須要引入web.FormView。
web.test_utils爲咱們提供了構建js測試用例的實體。若是你不瞭解js的引入機制,可參考第十四章"擴展CSS及JavaScript"。
odoo的客戶端測試用例是經過QUit框架實現了,它是用於JS單元測試的JQuery框架。參考https://qunitjs.com/。beforeEach函數會運行測試用例前執行,可用於初始化測試數據。函數由QUit框架提供。
咱們在beforeEach函數中初始化了一些數據。客戶端測試用例是運行在一個獨立(mock)的環境中,它並不與數據庫進行交互。所以,咱們須要建立一些測試數據。在內部,odoo建立了虛擬環境模擬RPC,this.data模式數據庫。this.data中的keys至關於數據庫中表,values至關於表中的字段及行。在咱們的例子中,咱們添加了book的表,book表有兩個字段name(char)及color(integer)。在這,咱們可使用全部的字段類型,好比,{string:"M2oField", type:"many2one", relation:"partner"}。咱們在records中添加了兩條記錄。
下一步,咱們藉助Quit.test函數建立了測試用例。函數的第一個參數是string類型,用於描述測試用例的用途。第二個參數是具體測試用例代碼邏輯的函數。函數由QUnit框架調用,assert實例做爲參數傳遞到函數體內。在咱們的例子中,咱們在assert.expect函數中傳入了測試用例的個數。咱們一共有兩個測試用例,所以咱們傳入2。
咱們計劃測試編輯模式下的int_color小部件。因此咱們經過testUtils.createView建立了編輯模式下的form視圖。createView接收不一樣參數:
在使用int_color小部件建立表單視圖以後,咱們添加了兩個測試用例。第一個測試用例用於檢查UI上彩色藥片的數量,第二個測試用例用於檢查藥片在單擊後是否被正確激活。咱們從斷言的QUnit框架的效用中獲得了嚴格的函數。若是前兩個參數匹配,則strictEqual函數經過測試用例。若是它們不匹配,則測試用例將失敗。
還有一些assert函數能夠用於QUnit測試用例,好比assert.deepEqual,assert.ok,assert.notOk。要了解有關QUnit的更多信息,請參閱https://qunitjs.com/上的文檔。
如今您已經看到了Python和JavaScript測試用例。它們都在一個孤立的環境中工做,它們彼此之間不相互做用。爲了測試JavaScript和Python代碼之間的集成,使用了嚮導測試用例。
對於本節,咱們將繼續使用上一節中的my_library模塊。咱們將添加一個巡迴測試用例來檢查圖書模型的流程。另外,確保您已經安裝了web_tour模塊,或者已經將web_tour模塊依賴項添加到清單中。
按照如下步驟添加圖書的嚮導測試用例:
odoo.define('my_library.tour', function (require) { "use strict"; var core = require('web.core'); var tour = require('web_tour.tour'); var _t = core._t; tour.register('library_tour', { url: "/web", test: true, rainbowManMessage: _t("Congrats, you have listed a book."), sequence: 5, }, [tour.stepUtils.showAppsMenuItem(), // Place step 3 here ]); });
{ trigger: '.o_app[data-menu-xmlid="my_library.library_ base_menu"]', content: _t('Manage books and authors in <b>Library app</b>.'), position: 'right' }, { trigger: '.o_list_button_add', content: _t("Let's create new book."), position: 'bottom', }, { trigger: 'input[name="name"]', extra_trigger: '.o_form_editable', content: _t('Set the book title'), position: 'right', run: function (actions) { actions.text('Test Book'); }, }, { trigger: '.o_form_button_save', content: _t('Save this book record'), position: 'bottom', }
<template id="assets_tests" name="Library Assets Tests" inherit_id="web.assets_tests"> <xpath expr="." position="inside"> <script type="text/javascript" src="/my_library/ static/tests/my_library_tour.js" /> </xpath> </template>
from odoo.tests.common import HttpCase, tagged class TestBookUI(HttpCase): @tagged('post_install', '-at_install') def test_01_book_tour(self): """Books UI tour test case""" self.browser_js("/web", "odoo.__DEBUG__.services['web_tour.tour']. run('library_tour')", "odoo.__DEBUG__.services['web_tour.tour']. tours.library_tour.ready", login = "admin")
運行測試用例
./odoo-bin -c server.conf -i my_library --test-enable
運行日誌以下:
...INFO test odoo.addons.my_library.tests.test_tour.TestBookUI: console log: Tour library_tour succeeded
爲了建立tour測試用例,您須要首先建立UI tour。若是你想了解更多關於UI tours的信息,請參考第15章「Web客戶端開發」中的經過嚮導提高交互感一節。
步驟1,咱們用library_tour註冊了一個新的tour。
步驟2,咱們爲嚮導測試用例添加了步驟。
與以前的嚮導案例相比,這裏有兩個主要變化。首先,咱們爲嚮導定義添加了一個test=true參數;其次,咱們添加了一個額外的屬性,run。在run函數中,您必須編寫邏輯來執行一般由用戶執行的操做。例如,在嚮導的第四步中,咱們要求用戶輸入書名。
爲了自動化這個步驟,咱們添加了一個run函數來設置title字段中的值。run函數將操做實用程序做爲參數傳遞。這提供了一些執行基本操做的快捷方式。最重要的問題以下:
odoo.__DEBUG__.services['web_tour.tour'].run('library_tour')
在咱們的示例中,嚮導測試用例的名稱將做爲browser_js的第一個參數。隨後的參數待執行的測試用用例,最後一個參數是執行測試用例用戶名。
odoo提供了經過UI運行客戶端測試用例的方法,咱們能夠看到測試用例每一步的執行狀況。
咱們能夠經過UI運行QUit和嚮導測試用例。咱們能夠激活開發者模式,查看經過UI執行的選項。
點擊BUG圖標,選擇Run JS Tests,以下:
這會打開QUit實例,並逐個運行測試用例,以下圖。默認,僅展現測試失敗的用例。若是想顯示全部的測試用例,取消Hide passed tests選項。
點擊BUG圖標,選擇Start Tour,以下:
將展現嚮導測試用例的列表,咱們可點擊後面的start啓動相應的測試用例:
若是您啓用了測試資產模式,那麼測試之旅只會顯示在列表中。若是在列表中沒有找到library_tour,請確保已激活測試資產模式。
QUnit的UI是由QUnit框架自己提供的。在這裏,您能夠爲模塊篩選測試用例。您甚至能夠爲一個模塊運行一個測試用例。經過UI,您能夠看到每一個測試用例的進度,而且能夠深刻到測試用例的每一個步驟。在內部,Odoo只是在Headless Chrome打開相同的URL。
點擊Run tours選項將顯示可用的tours列表。經過點擊列表上的播放按鈕,您能夠運行嚮導測試用例。注意,當嚮導經過命令行選項運行時,它將在回滾事務中運行,所以在嚮導成功後,經過嚮導所作的更改將被回滾。可是,當tour從UI運行時,它的工做方式就像用戶正在操做它同樣,這意味着tour所作的更改不會回滾並停留在那裏,因此要當心使用這個選項。
開發複雜的客戶端測試用例是件很是頭痛的事。本節,咱們將學習如何調試客戶端測試用例,如何運行其中一個測試用例。
QUnit.only('int_color field test cases', function (assert) {
var form = testUtils.createView({ View: FormView, model: 'book', data: this.data, arch: '<form string="Books">' + '<group>' + '<field name="name"/>' + '<field name="color" widget="int_color"/>' + '</group>' + '</form>', res_id: 1, debug:true });
點擊 Run JS Tests:
步驟1,咱們用QUnit.only替換了QUnit.test。這將只運行這個測試用例。在測試用例的開發過程當中,這能夠節省時間。注意,使用QUnit.only將會終止經過命令行選項運行的測試用例。這隻能用於調試或測試,而且只能在從UI打開測試用例時使用,因此不要忘記在開發完成後將其替換爲QUnit.test。
在咱們的QUnit測試用例示例中,咱們建立了表單視圖來測試int_ color小部件。若是您在UI中運行QUnit測試用例,您將瞭解到您不能在UI中看到建立的表單視圖。在QUnit套件的UI中,您只能看到日誌。這使得開發QUnit測試用例很是困難。爲了解決這個問題,在createView函數中使用了debug參數。在步驟2中,咱們在createView函數中添加了debug: true。這將在瀏覽器中顯示test form視圖。在這裏,您能夠經過瀏覽器調試器定位文檔對象模型(Document Object Model, DOM)元素。
警告
在測試用例的最後,咱們經過destroy()方法銷燬視圖。若是您已經銷燬了視圖,那麼您將沒法在UI中看到表單視圖,所以爲了在瀏覽器中看到它,請在開發期間刪除這一行。這將幫助您調試測試用例。
Odoo使用headless CHrome。這開啓了新的可能性。從Odoo 12開始,您能夠錄製失敗的測試用例的視頻,也能夠對失敗的測試用例進行截屏。
爲測試用例錄製視頻須要一個ffmpeg包。
apt-get install ffmpeg
./odoo-bin -c server.conf -i my_library --test-enable --screencasts=/home/pga/odoo_test/
./odoo-bin -c server.conf -i my_library --test-enable --screenshots=/home/pga/odoo_test/
爲了爲失敗的測試案例生成截屏/截屏,您須要使用保存視頻或圖像文件的路徑運行服務器。當您運行測試用例時,若是測試用例失敗了,Odoo將在給定目錄中保存失敗測試用例的截圖/視頻。
爲了生成測試用例的視頻,Odoo使用了ffmpeg包。若是您沒有在服務器上安裝這個包,那麼它將只保存一個失敗的測試用例的截圖。在安裝這個包以後,您將可以看到任何失敗的測試用例的mp4文件。
小貼士
爲測試用例生成視頻會佔用磁盤上更多的空間,因此請謹慎使用此選項,而且只在確實須要時使用。
請記住,屏幕截圖和視頻只會爲失敗的測試用例生成,因此若是您想要測試它們,您須要編寫一個失敗的測試用例。
到目前爲止,咱們已經看到了用於檢測業務邏輯中的錯誤或bug的測試用例。然而,有時咱們須要用大量的數據來測試咱們的開發。生成大量數據多是一項乏味的工做。Odoo提供了一套工具,能夠幫助您爲模型生成大量隨機數據。在本節,咱們將使用populate命令爲library.book模型生成測試數據。
對於本節,咱們將繼續使用上一節的my_library模塊。咱們將添加_populate_factories方法,它將用於生成測試數據。
from . import library_data
from odoo import models from odoo.tools import populate class BookData(models.Model): _inherit = "library.book" _populate_sizes = {"small": 10, "medium": 100, "large": 500} def _populate_factories(self): return [ ("name", populate.constant("Book no {counter}")), ]
./odoo-bin populate -–models=library.book –-size=medium -c server.conf -i my_library
這將爲圖書生成100個單位的數據。生成數據後,流程將被終止。要查看該書的數據,運行不帶填充參數的命令。
步驟1,新增生成數據文件目錄。
步驟2,添加生成數據代碼。使用populate_factories生成隨機數據。_populate_factories方法返回模型字段的工廠,這些工廠將用於生成隨機數據。模型具備必需的name字段,所以在咱們的示例中,咱們返回了name字段的生成器。這個生成器將用於爲圖書記錄生成隨機數據。咱們已經使用了populate.constant;當咱們在數據生成過程當中進行迭代時,這將生成不一樣的名稱。
除了populate.constant,Odoo提供了其餘幾個生成器來填充數據,以下:
另外一個重要的屬性是_populate_sizes。基於--size參數,表示想要生成的數據的數量。
步驟3,咱們生成了圖書的模型。爲了生成測試數據,咱們須要使用--size和--model參數。odoo將使用_populate方法生成隨機記錄。_populate方法調用_populate_factories方法生成隨機數據。
小貼士
若是屢次運行populate命令,數據也將生成屢次。當心使用這一點很是重要:若是在生產數據庫中運行該命令,它將在生產數據庫中生成測試數據。這是你要避免的。
有時,您也但願生成關係數據。例如,對於書籍,您可能還但願建立做者或租借記錄。要管理這樣的記錄,可使用_populate_dependencies屬性:
class BookData(models.Model): _inherit = 'library.book' _populate_sizes = {'small': 10, 'medium': 100, 'large': 500} _populate_dependencies = ['res.users', 'res.company']
這將在填充當前模型以前填充依賴項數據。一旦完成,你能夠經過populated_models註冊表訪問已填充的數據:
company_ids = self.env.registry.populated_models['res.company']
這裏給出的行將爲您提供在爲當前模型生成測試數據以前填充的公司列表。