frappe-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frappe Testing

Frappe 测试

Write and run tests for Frappe applications using the built-in testing framework.
使用内置测试框架为Frappe应用编写并运行测试。

When to use

适用场景

  • Writing unit tests for DocType controllers
  • Writing integration tests for workflows and APIs
  • Running existing test suites
  • Debugging test failures
  • Setting up CI pipelines for Frappe apps
  • 为DocType控制器编写单元测试
  • 为工作流和API编写集成测试
  • 运行现有测试套件
  • 调试测试失败问题
  • 为Frappe应用搭建CI流水线

Inputs required

所需输入

  • App name to test
  • Site name for test execution
  • Specific module/DocType to test (optional)
  • Test environment (dev site, dedicated test site)
  • 待测试的应用名称
  • 用于测试执行的站点名称
  • 待测试的特定模块/DocType(可选)
  • 测试环境(开发站点、专用测试站点)

Procedure

操作步骤

0) Setup test environment

0) 搭建测试环境

bash
undefined
bash
undefined

Install dev dependencies

Install dev dependencies

bench setup requirements --dev
bench setup requirements --dev

Ensure site is ready

Ensure site is ready

bench --site <site> migrate
undefined
bench --site <site> migrate
undefined

1) Run tests

1) 运行测试

bash
undefined
bash
undefined

Run all tests for an app

Run all tests for an app

bench --site <site> run-tests --app my_app
bench --site <site> run-tests --app my_app

Run tests for specific module

Run tests for specific module

bench --site <site> run-tests --module my_app.my_module.tests
bench --site <site> run-tests --module my_app.my_module.tests

Run tests for specific DocType

Run tests for specific DocType

bench --site <site> run-tests --doctype "My DocType"
bench --site <site> run-tests --doctype "My DocType"

Verbose output

Verbose output

bench --site <site> run-tests --app my_app -v
bench --site <site> run-tests --app my_app -v

Run single test file

Run single test file

bench --site <site> run-tests --module my_app.doctype.sample_doc.test_sample_doc
undefined
bench --site <site> run-tests --module my_app.doctype.sample_doc.test_sample_doc
undefined

2) Write DocType tests

2) 编写DocType测试

Create
test_<doctype_name>.py
alongside the DocType:
python
undefined
在DocType同级目录下创建
test_<doctype_name>.py
文件:
python
undefined

my_app/doctype/sample_doc/test_sample_doc.py

my_app/doctype/sample_doc/test_sample_doc.py

import frappe from frappe.tests.utils import FrappeTestCase
class TestSampleDoc(FrappeTestCase): def setUp(self): # Create test data self.doc = frappe.get_doc({ "doctype": "Sample Doc", "title": "Test Document" }).insert()
def tearDown(self):
    # Cleanup
    frappe.delete_doc("Sample Doc", self.doc.name, force=True)

def test_creation(self):
    self.assertEqual(self.doc.title, "Test Document")

def test_validation(self):
    doc = frappe.get_doc({
        "doctype": "Sample Doc",
        "title": ""  # Invalid - required field
    })
    self.assertRaises(frappe.ValidationError, doc.insert)

def test_workflow(self):
    self.doc.status = "Approved"
    self.doc.save()
    self.assertEqual(self.doc.status, "Approved")
undefined
import frappe from frappe.tests.utils import FrappeTestCase
class TestSampleDoc(FrappeTestCase): def setUp(self): # Create test data self.doc = frappe.get_doc({ "doctype": "Sample Doc", "title": "Test Document" }).insert()
def tearDown(self):
    # Cleanup
    frappe.delete_doc("Sample Doc", self.doc.name, force=True)

def test_creation(self):
    self.assertEqual(self.doc.title, "Test Document")

def test_validation(self):
    doc = frappe.get_doc({
        "doctype": "Sample Doc",
        "title": ""  # Invalid - required field
    })
    self.assertRaises(frappe.ValidationError, doc.insert)

def test_workflow(self):
    self.doc.status = "Approved"
    self.doc.save()
    self.assertEqual(self.doc.status, "Approved")
undefined

3) Write API tests

3) 编写API测试

python
undefined
python
undefined

my_app/tests/test_api.py

my_app/tests/test_api.py

import frappe from frappe.tests.utils import FrappeTestCase
class TestAPI(FrappeTestCase): def test_whitelist_method(self): from my_app.api import process_order
    # Create test order
    order = frappe.get_doc({
        "doctype": "Sales Order",
        "customer": "_Test Customer"
    }).insert()
    
    # Test the API
    result = process_order(order.name, "approve")
    
    self.assertEqual(result["status"], "success")
    
    # Cleanup
    frappe.delete_doc("Sales Order", order.name, force=True)

def test_permission_denied(self):
    # Test as restricted user
    frappe.set_user("guest@example.com")
    
    from my_app.api import sensitive_action
    self.assertRaises(frappe.PermissionError, sensitive_action, "doc-001")
    
    # Reset user
    frappe.set_user("Administrator")
undefined
import frappe from frappe.tests.utils import FrappeTestCase
class TestAPI(FrappeTestCase): def test_whitelist_method(self): from my_app.api import process_order
    # Create test order
    order = frappe.get_doc({
        "doctype": "Sales Order",
        "customer": "_Test Customer"
    }).insert()
    
    # Test the API
    result = process_order(order.name, "approve")
    
    self.assertEqual(result["status"], "success")
    
    # Cleanup
    frappe.delete_doc("Sales Order", order.name, force=True)

def test_permission_denied(self):
    # Test as restricted user
    frappe.set_user("guest@example.com")
    
    from my_app.api import sensitive_action
    self.assertRaises(frappe.PermissionError, sensitive_action, "doc-001")
    
    # Reset user
    frappe.set_user("Administrator")
undefined

4) Test permissions

4) 测试权限

python
def test_role_permissions(self):
    # Create user with specific role
    user = frappe.get_doc({
        "doctype": "User",
        "email": "test_user@example.com",
        "roles": [{"role": "Sales User"}]
    }).insert()
    
    frappe.set_user("test_user@example.com")
    
    # Test permission
    self.assertTrue(frappe.has_permission("Sales Order", "read"))
    self.assertFalse(frappe.has_permission("Sales Order", "delete"))
    
    # Cleanup
    frappe.set_user("Administrator")
    frappe.delete_doc("User", user.name, force=True)
python
def test_role_permissions(self):
    # Create user with specific role
    user = frappe.get_doc({
        "doctype": "User",
        "email": "test_user@example.com",
        "roles": [{"role": "Sales User"}]
    }).insert()
    
    frappe.set_user("test_user@example.com")
    
    # Test permission
    self.assertTrue(frappe.has_permission("Sales Order", "read"))
    self.assertFalse(frappe.has_permission("Sales Order", "delete"))
    
    # Cleanup
    frappe.set_user("Administrator")
    frappe.delete_doc("User", user.name, force=True)

5) Use fixtures

5) 使用测试夹具(Fixtures)

python
undefined
python
undefined

my_app/doctype/sample_doc/test_records.json

my_app/doctype/sample_doc/test_records.json

[ { "doctype": "Sample Doc", "title": "Test Record 1" }, { "doctype": "Sample Doc", "title": "Test Record 2" } ]

Reference in tests:
```python
class TestSampleDoc(FrappeTestCase):
    def test_fixture_loaded(self):
        doc = frappe.get_doc("Sample Doc", "Test Record 1")
        self.assertIsNotNone(doc)
[ { "doctype": "Sample Doc", "title": "Test Record 1" }, { "doctype": "Sample Doc", "title": "Test Record 2" } ]

在测试中引用:
```python
class TestSampleDoc(FrappeTestCase):
    def test_fixture_loaded(self):
        doc = frappe.get_doc("Sample Doc", "Test Record 1")
        self.assertIsNotNone(doc)

6) Run UI tests (Cypress)

6) 运行UI测试(Cypress)

bash
undefined
bash
undefined

Run UI tests for an app

Run UI tests for an app

bench --site <site> run-ui-tests my_app
bench --site <site> run-ui-tests my_app

Headless mode

Headless mode

bench --site <site> run-ui-tests my_app --headless
undefined
bench --site <site> run-ui-tests my_app --headless
undefined

Verification

验证标准

  • All tests pass:
    bench --site <site> run-tests --app my_app
  • No test pollution (tests are isolated)
  • Tests run in < 5 minutes for fast feedback
  • CI pipeline runs tests on each commit
  • 所有测试通过:
    bench --site <site> run-tests --app my_app
  • 无测试污染(测试相互独立)
  • 测试执行时间<5分钟以获取快速反馈
  • CI流水线在每次提交时自动运行测试

Failure modes / debugging

失败场景与调试

  • Test not found: Ensure filename starts with
    test_
    and class/method names follow conventions
  • Database errors: Tests may not be isolated—check for missing cleanup
  • Permission errors in tests: Use
    frappe.set_user("Administrator")
    in setup
  • Slow tests: Avoid unnecessary fixtures, mock external services
  • 未找到测试:确保文件名以
    test_
    开头,且类/方法名称遵循规范
  • 数据库错误:测试可能未隔离——检查是否缺少清理步骤
  • 测试中出现权限错误:在初始化步骤中使用
    frappe.set_user("Administrator")
  • 测试运行缓慢:避免不必要的测试夹具,模拟外部服务

Escalation

升级支持

  • For complex fixtures, see references/fixtures.md
  • For UI testing patterns, see references/cypress.md
  • For CI setup, see references/ci-testing.md
  • 复杂夹具相关问题,请查看references/fixtures.md
  • UI测试模式相关问题,请查看references/cypress.md
  • CI搭建相关问题,请查看references/ci-testing.md

References

注意事项

  • references/test-patterns.md - Common test patterns
  • references/fixtures.md - Test data management
  • references/cypress.md - UI testing
  • 使用测试夹具:通过夹具加载测试数据,而非在每个测试中手动创建
  • 清理测试数据:在
    tearDown()
    中删除创建的记录,或使用
    frappe.db.rollback()
  • 模拟外部服务:测试中切勿调用真实API;模拟HTTP请求
  • 隔离测试:每个测试应相互独立;不依赖测试执行顺序
  • 显式设置用户上下文:使用
    frappe.set_user()
    以特定用户身份进行测试

Guardrails

常见错误

  • Use test fixtures: Load test data via fixtures, not manual creation in each test
  • Clean up test data: Delete created records in
    tearDown()
    or use
    frappe.db.rollback()
  • Mock external services: Never call real APIs in tests; mock HTTP calls
  • Isolate tests: Each test should be independent; no reliance on test execution order
  • Set user context explicitly: Use
    frappe.set_user()
    to test as specific users
错误类型失败原因修复方案
未使用
FrappeTestCase
缺少测试初始化/清理逻辑继承
frappe.tests.utils.FrappeTestCase
未执行数据库回滚测试污染,导致不稳定测试在tearDown中使用
frappe.db.rollback()
或事务
异步测试不稳定间歇性失败使用
frappe.tests.utils.run_until()
或正确处理异步逻辑
测试实现细节而非行为测试用例脆弱测试结果而非内部方法调用
硬编码测试数据与现有数据冲突使用唯一名称,如
_Test Record {uuid}
跳过权限测试存在安全漏洞使用不同用户角色进行测试,而非仅用管理员

Common Mistakes

MistakeWhy It FailsFix
Not using
FrappeTestCase
Missing test setup/teardownExtend
frappe.tests.utils.FrappeTestCase
Missing db rollbackTest pollution, flaky testsUse
frappe.db.rollback()
in tearDown or transactions
Flaky async testsIntermittent failuresUse
frappe.tests.utils.run_until()
or proper async handling
Testing implementation not behaviorBrittle testsTest outcomes, not internal method calls
Hardcoded test dataConflicts with existing dataUse unique names like
_Test Record {uuid}
Skipping permission testsSecurity holesTest with different user roles, not just Administrator