Frappe API 的界面测试

你可以编写集成测试,也可以直接使用 QUnit 在 JavaScript 中编写测试

QUnit 帮助你使用 QUnit 框架和原生 frappe API 编写 UI 测试。你可能已经猜到了,这是一种更快的测试编写方式。

测试运行器

要编写基于 QUnit 的测试,请将测试添加到应用程序的 tests/ui 文件夹中。测试文件必须以 test_ 开头,并以 .js 扩展名结尾。

要运行你的文件,你可以使用测试运行器。测试运行器提供了一个用户界面,用于加载所有 QUnit 测试并在浏览器中运行它们。

在 CI 中,所有 QUnit 测试都由测试运行器使用 frappe/tests/test_test_runner.py 运行

运行测试

要运行基于测试运行器的测试,请使用 run-ui-tests bench 命令,并传入你想运行的文件名。

bench run-ui-tests –test frappe/tests/ui/test_list.js

这会将文件名传递给 test_test_runner.py,它会在浏览器中加载所需的 JS 并执行测试

调试测试

要调试测试,你可以从 UI 中的测试运行器打开它,并手动运行以查看它究竟在哪里失败。

测试顺序

在 Frappe 中,UI 测试按固定顺序运行以确保依赖关系。

测试运行的顺序将在 tests/ui/tests.txt
文件中。

运行所有 UI 测试

要一起运行应用程序的所有 UI 测试,请运行

bench run-ui-tests –app [app_name]

这将逐一运行 tests/ui 文件夹中的所有文件。

QUnit 测试示例

以下是 QUnit 中 To Do 测试的示例

QUnit.test(“Test quick entry”, function(assert) {
assert.expect(2);
let done = assert.async();
let random_text = frappe.utils.get_random(10);

frappe.run_serially([
() => frappe.set_route(‘List’, ‘ToDo’),
() => frappe.new_doc(‘ToDo’),
() => frappe.quick_entry.dialog.set_value(‘description’, random_text),
() => frappe.quick_entry.insert(),
(doc) => {
assert.ok(doc && !doc.__islocal);
return frappe.set_route(‘Form’, ‘ToDo’, doc.name);
},
() => assert.ok(cur_frm.doc.description.includes(random_text)),

// Delete the created ToDo
() => frappe.tests.click_page_head_item(‘Menu’),
() => frappe.tests.click_dropdown_item(‘Delete’),
() => frappe.tests.click_page_head_item(‘Yes’),

() => done()
]);
});

使用 Promise 编写对测试友好的代码

Promise 是编写对测试友好的代码的好方法。如果你的方法调用了异步调用(ajax),那么你应该返回一个 Promise 对象。在编写测试时,如果你遇到一个不返回 Promise 对象的函数,你应该更新代码以返回一个 Promise 对象。

界面集成测试

你可以使用 Cypress 来编写集成测试。它是一个基于 NodeJS 的全栈测试框架,不依赖 Selenium。

要编写集成测试,请在 cypress/integration 目录中创建一个 .js 文件。

示例

以下是一个用于检查待办事项插入的集成测试示例

context('ToDo', () => {
 before(() => {
 cy.login('Administrator', 'admin');
 cy.visit('/desk');
 });

 it('creates a new todo', () => {
 cy.visit('/app/todo/new-todo-1');
 cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
 cy.get('.page-title').should('contain', 'Not Saved');
 cy.get('.primary-action').click();
 cy.visit('/desk#List/ToDo');
 cy.location('hash').should('eq', '/app/todo');
 cy.get('.list-row').should('contain', 'this is a test todo');
 });
});

在本地运行 Cypress

Cypress 使用你系统上安装的任何基于 Chromium 的浏览器来运行测试。每个应用都有自己的 Cypress 测试套件。
例如,要运行 frappe 应用的测试,请运行以下命令

cd ~/frappe-bench/apps/frappe
yarn cypress:open

这将打开 Cypress Electron 界面,你可以在其中手动运行任何测试,或运行所有测试。

单元测试

1.简介

Frappe 提供了一些基本工具,可以快速编写自动化测试。以下是一些基本规则:

  1. 测试可以放在仓库中的任何位置,但必须以 test_ 开头,并且应该是一个 .py 文件。
  2. 测试必须在以 test_ 开头的站点上运行。这是为了防止意外丢失数据。
  3. 新建 DocType 时会自动生成测试桩。
  4. Frappe 测试运行器会自动为通过 Link 类型字段(外键)标识的依赖 DocType 构建测试记录。
  5. 可以使用 bench run-tests 来执行测试。
  6. 对于非 DocType 测试,你可以编写简单的单元测试,并在文件名前加上 test_。

为 DocType 编写测试

2.1. 编写 DocType 测试:

  1. 测试用例放在名为 test_[doctype].py 的文件中。
  2. 你必须在测试文件中创建所有依赖项。
  3. 创建一个 Python 模块结构来生成测试数据/依赖项。

示例(针对 test_event.py):

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

import frappe
import frappe.defaults

from frappe.tests.utils import FrappeTestCase

def create_events():
    if frappe.flags.test_events_created:
        return

frappe.set_user("Administrator")
doc = frappe.get_doc({
    "doctype": "Event",
    "subject":"_Test Event 1",
    "starts_on": "2014-01-01",
    "event_type": "Public"
    }).insert()

doc = frappe.get_doc({
    "doctype": "Event",
    "subject":"_Test Event 2",
    "starts_on": "2014-01-01",
    "event_type": "Private"
    }).insert()

doc = frappe.get_doc({
    "doctype": "Event",
    "subject":"_Test Event 3",
    "starts_on": "2014-01-01",
    "event_type": "Public"
    "event_individuals": [{
        "person": "[email protected]"
    }]
    }).insert()

frappe.flags.test_events_created = True

class TestEvent(FrappeTestCase):
    def setUp(self):
        create_events()

    def tearDown(self):
        frappe.set_user("Administrator")

    def test_allowed_public(self):
        frappe.set_user("[email protected]")
        doc = frappe.get_doc("Event", frappe.db.get_value("Event",
        {"subject":"_Test Event 1"}))
        self.assertTrue(frappe.has_permission("Event", doc=doc))

    def test_not_allowed_private(self):
        frappe.set_user("[email protected]")
        doc = frappe.get_doc("Event", frappe.db.get_value("Event",
        {"subject":"_Test Event 2"}))
        self.assertFalse(frappe.has_permission("Event", doc=doc))

    def test_allowed_private_if_in_event_user(self):
        doc = frappe.get_doc("Event", frappe.db.get_value("Event",
        {"subject":"_Test Event 3"}))

        frappe.set_user("[email protected]")
        self.assertTrue(frappe.has_permission("Event", doc=doc))

    def test_event_list(self):
        frappe.set_user("[email protected]")
        res = frappe.get_list("Event", filters=[["Event", "subject", "like", "_Test Event%"]], fields=["name", "subject"])
        self.assertEqual(len(res), 2)
        subjects = [r.subject for r in res]
        self.assertTrue("_Test Event 1" in subjects)
        self.assertTrue("_Test Event 3" in subjects)
        self.assertFalse("_Test Event 2" in subjects)

2. 运行测试

这个函数会构建所有测试依赖项并运行你的测试。
你应该从 “frappe_bench” 文件夹运行测试。如果不加任何选项,所有测试都会被执行。

bench run-tests

如果你需要更多关于测试执行的信息,可以使用 bench 的详细日志级别。

bench –verbose run-tests

选项:

 --app <appname>
 --doctype 
<doctype>
 --test 
<specifictest>
 --module 
<module> (Run a particular module that has tests)
 --profile (Runs a Python profiler on the test)
 --junit-xml-output
<pathtoxml> (The command provides test results in the standard XUnit XML format)
</pathtoxml></module></specifictest></doctype></appname>

2.1. 应用示例:

所有应用都位于文件夹:”~/frappe-bench/apps”。
我们可以为每个应用运行测试。

  • frappe-bench/apps/erpnext/
  • frappe-bench/apps/erpnext_demo/
  • frappe-bench/apps/frappe/
 bench run-tests --app erpnext
 bench run-tests --app erpnext_demo
 bench run-tests --app frappe

2.2. DocType 示例:

 frappe@erpnext:~/frappe-bench$ bench run-tests --doctype "Activity Cost"
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.008s

 OK

2.3. 测试示例:

运行 User 中的特定用例:

 frappe@erpnext:~/frappe-bench$ bench run-tests --doctype User --test test_get_value
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.005s

 OK

2.4. 模块示例:

如果我们想运行某个模块中的测试:

/home/frappe/frappe-bench/apps/erpnext/erpnext/support/doctype/issue/test_issue.py

我们应该这样使用模块名(相对于应用文件夹)

erpnext.support.doctype.issue.test_issue

示例:
 frappe@erpnext:~/frappe-bench$ bench run-tests --module "erpnext.stock.doctype.stock_entry.test_stock_entry"
 ...........................
 ----------------------------------------------------------------------
 Ran 27 tests in 30.549s

2.5. Profile 示例:

 frappe@erpnext:~/frappe-bench$ bench run-tests --doctype "Activity Cost" --profile
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.010s

 OK
 9133 function calls (8912 primitive calls) in 0.011 seconds

 Ordered by: cumulative time

 ncalls tottime percall cumtime percall filename:lineno(function)
 2 0.000 0.000 0.008 0.004 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:187(insert)
 1 0.000 0.000 0.003 0.003 /home/frappe/frappe-bench/apps/frappe/frappe/model/document.py:386(_validate)
 13 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/database.py:77(sql)
 255 0.000 0.000 0.002 0.000 /home/frappe/frappe-bench/apps/frappe/frappe/model/base_document.py:91(get)
 12 0.000 0.000 0.002 0.000

2.6 不创建测试数据或 before_tests 钩子来运行测试

  • 当你在开发功能时,使用 --skip-test-records 可以在不构建测试依赖项(即为关联对象构建测试数据)的情况下编写测试,这很有用。
  • 你也可以使用 --skip-before-tests 跳过测试初始化脚本。

示例

bench --site school.erpnext.local run-tests --doctype "Student Group" --skip-test-records --skip-before-tests

3.0 FrappeTestCase

FrappeTestCase 是 Frappe 框架特有的 TestCase 类,继承自 unittest.TestCase。在你的测试中继承这个类可以确保:

  1. frappe.local.flags 和其他最常用的本地代理在测试用例运行后会被重置。
  2. 数据库——在测试用例开始前会启动一个新的数据库事务,并在测试完成后回滚。

用法

# app/doctype/dt/test_dt.py  

from frappe.tests.utils import FrappeTestCase  

class TestDt(FrappeTestCase):  
 @classmethod  
 def setUpClass(cls):  
 super().setUpClass() # important to call super() methods when extending TestCase.   
 ...

编写 XUnit XML 测试

如何运行:
bench run-tests --junit-xml-output=/reports/junit_test.xml
测试报告示例:
 <testsuite tests="3">
 <testcase classname="foo1" name="ASuccessfulTest">
 <testcase classname="foo2" name="AnotherSuccessfulTest">
 <testcase classname="foo3" name="AFailingTest">
 <failure type="NotEnoughFoo"> details about failure </failure>
 </testcase>
 </testcase></testcase></testsuite>

它是为 CI Jenkins 设计的,但也适用于任何其他能理解 XUnit 格式 XML 测试结果表示的工具。

Jenkins 配置支持:

  1. 你应该安装 xUnit 插件——https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin
  2. 安装完成后,打开 Jenkins 任务配置,在 “Post-build Actions” 下点击名为 “Publish JUnit test result report” 的复选框,然后输入 XML 报告的路径:

(示例:reports/.xml*)

自动化测试

Frappe 为你提供了一个测试框架,用于编写和执行测试,这些测试可以直接在像 Travis 这样的持续集成工具上运行

你可以编写服务器端单元测试或 UI 测试