mcpField
F
frappe-testing

frappe-testing

github.com/lubusin测试与质量69221 次安装12 周前更新
代码仓库

关于 frappe-testing

frappe-testing 是面向 Frappe 应用开发者的测试技能,指导使用内置测试框架编写和运行测试,涵盖 DocType、API、UI 测试及故障排查。

主要功能

  1. 01DocType 单元与集成测试
  2. 02API 和权限测试
  3. 03运行 Cypress UI 测试
  4. 04测试数据与隔离管理
  5. 05故障排查和 CI 指引

适用场景

  1. 01测试 DocType 控制器和 API
  2. 02运行应用或模块测试套件
  3. 03检查基于角色的权限
  4. 04为 CI 流水线准备测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lubusin/frappe-skills --skill frappe-testing

安装后在 Claude Code 中输入/frappe-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ci-testing.md
cypress.md
fixtures.md
test-patterns.md
testing.md

详细介绍

Frappe 测试

使用内置测试框架为 Frappe 应用编写并运行测试。

适用场景

  • 为 DocType 控制器编写单元测试
  • 为工作流和 API 编写集成测试
  • 运行现有测试套件
  • 调试测试失败
  • 为 Frappe 应用设置 CI 流水线

所需输入

  • 要测试的应用名称
  • 执行测试所用的站点名称
  • 要测试的特定模块/DocType(可选)
  • 测试环境(开发站点、专用测试站点)

流程

0) 设置测试环境
# Install dev dependencies
bench setup requirements --dev

# Ensure site is ready
bench --site <site> migrate
1) 运行测试
# Run all tests for an app
bench --site <site> run-tests --app my_app

# Run tests for specific module
bench --site <site> run-tests --module my_app.my_module.tests

# Run tests for specific DocType
bench --site <site> run-tests --doctype "My DocType"

# Verbose output
bench --site <site> run-tests --app my_app -v

# Run single test file
bench --site <site> run-tests --module my_app.doctype.sample_doc.test_sample_doc
2) 编写 DocType 测试

在 DocType 同级目录中创建 test_<doctype_name>.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")
3) 编写 API 测试
# 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("[email protected]")
        
        from my_app.api import sensitive_action
        self.assertRaises(frappe.PermissionError, sensitive_action, "doc-001")
        
        # Reset user
        frappe.set_user("Administrator")
4) 测试权限
def test_role_permissions(self):
    # Create user with specific role
    user = frappe.get_doc({
        "doctype": "User",
        "email": "[email protected]",
        "roles": [{"role": "Sales User"}]
    }).insert()
    
    frappe.set_user("[email protected]")
    
    # 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) 使用 fixtures
# my_app/doctype/sample_doc/test_records.json
[
    {
        "doctype": "Sample Doc",
        "title": "Test Record 1"
    },
    {
        "doctype": "Sample Doc",
        "title": "Test Record 2"
    }
]

在测试中引用:

class TestSampleDoc(FrappeTestCase):
    def test_fixture_loaded(self):
        doc = frappe.get_doc("Sample Doc", "Test Record 1")
        self.assertIsNotNone(doc)
6) 运行 UI 测试(Cypress)
# Run UI tests for an app
bench --site <site> run-ui-tests my_app

# Headless mode
bench --site <site> run-ui-tests my_app --headless

验证

  • 所有测试均通过:bench --site <site> run-tests --app my_app
  • 无测试污染(测试相互隔离)
  • 测试在 < 5 分钟内运行完毕,以便快速获得反馈
  • CI 流水线在每次提交时运行测试

失败情况/调试

  • 未找到测试:确保文件名以 test_ 开头,且类名/方法名符合约定
  • 数据库错误:测试可能未隔离——检查是否缺少清理操作
  • 测试中的权限错误:在设置过程中使用 frappe.set_user("Administrator")
  • 测试运行缓慢:避免不必要的 fixtures,并模拟外部服务

升级处理

参考资料

防护措施

  • 使用测试 fixtures:通过 fixtures 加载测试数据,而不是在每个测试中手动创建
  • 清理测试数据:在 tearDown() 中删除创建的记录,或使用 frappe.db.rollback()
  • 模拟外部服务:测试中绝不调用真实 API;应模拟 HTTP 调用
  • 隔离测试:每个测试都应独立,不依赖测试执行顺序
  • 明确设置用户上下文:使用 frappe.set_user() 以特定用户身份进行测试

常见错误

错误失败原因修复方法
未使用 FrappeTestCase缺少测试设置/清理操作继承 frappe.tests.utils.FrappeTestCase
缺少数据库回滚测试污染、测试不稳定在 tearDown 中或事务中使用 frappe.db.rollback()
不稳定的异步测试间歇性失败使用 frappe.tests.utils.run_until() 或正确处理异步操作
测试实现而非行为测试脆弱测试结果,而不是内部方法调用
测试数据硬编码与现有数据冲突使用 _Test Record {uuid} 等唯一名称
跳过权限测试存在安全漏洞使用不同用户角色进行测试,而不只是使用 Administrator

评论

支持 Markdown