Skill125 estrellas del repoactualizado 2mo ago
frappe-testing-unit
>
Instalar en Claude Code
Copiargit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-testing-unit && cp -r /tmp/frappe-testing-unit/skills/source/testing/frappe-testing-unit ~/.claude/skills/frappe-testing-unitDespués abre una sesión nueva de Claude Code; el skill carga automáticamente.
Definición
SKILL.md
# Unit & Integration Testing
## Quick Reference
| Task | Command / Class |
|------|----------------|
| Run all tests | `bench --site test_site run-tests` |
| Run tests for app | `bench --site test_site run-tests --app myapp` |
| Run tests for doctype | `bench --site test_site run-tests --doctype "Sales Order"` |
| Run single test method | `bench --site test_site run-tests --doctype "Sales Order" --test test_submit` |
| Run tests for module | `bench --site test_site run-tests --module "myapp.mymodule.doctype.mydt.test_mydt"` |
| Run with profiler | `bench --site test_site run-tests --doctype "Task" --profile` |
| Run with failfast | `bench --site test_site run-tests --failfast` |
| Generate JUnit XML | `bench --site test_site run-tests --junit-xml-output /path/report.xml` |
| Skip fixture loading | `bench --site test_site run-tests --skip-test-records --skip-before-tests` |
| Base class (v14) | `from frappe.tests.utils import FrappeTestCase` |
| Unit test class (v15+) | `from frappe.tests.classes import UnitTestCase` |
| Integration test class (v15+) | `from frappe.tests.classes import IntegrationTestCase` |
## Decision Tree: Which Test Base Class?
```
Need to test a function or method in isolation?
├─ YES → Does it require database access?
│ ├─ NO → UnitTestCase (v15+) or FrappeTestCase (v14)
│ └─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → Need to test document lifecycle (create/submit/cancel)?
├─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → Need to test permissions or user context?
├─ YES → IntegrationTestCase (v15+) or FrappeTestCase (v14)
└─ NO → UnitTestCase (v15+) or FrappeTestCase (v14)
```
**Version note**: In v14, `FrappeTestCase` is the ONLY base class. In v15+, it still works (deprecated wrapper) but ALWAYS prefer `UnitTestCase` or `IntegrationTestCase` for new code.
## Test Base Classes
### FrappeTestCase (v14: still works in v15+ as compatibility wrapper)
```python
from frappe.tests.utils import FrappeTestCase
class TestMyDoctype(FrappeTestCase):
def test_something(self):
doc = frappe.get_doc({"doctype": "My Doctype", "field": "value"})
doc.insert()
self.assertEqual(doc.field, "value")
```
**Behavior**: Resets `frappe.local.flags` after each test. Database transactions start before each test and rollback afterward. ALWAYS call `super().setUpClass()` if you override `setUpClass`.
### UnitTestCase (v15+): No Database Access
```python
from frappe.tests.classes import UnitTestCase
class TestMyUtils(UnitTestCase):
def test_calculation(self):
result = my_calculation(10, 20)
self.assertEqual(result, 30)
def test_html_output(self):
html = generate_html()
self.assertEqual(self.normalize_html(html), self.normalize_html(expected))
```
**Behavior**: Sets `frappe.set_user("Administrator")` in `setUpClass`. Auto-detects doctype from module path. Provides `normalize_html()`, `normalize_sql()`, `assertDocumentEqual()`, `assertQueryEqual()`, `assertSequenceSubset()`.
### IntegrationTestCase (v15+): Full Database Access
```python
from frappe.tests.classes import IntegrationTestCase
class TestSalesOrder(IntegrationTestCase):
def test_submit_order(self):
so = frappe.get_doc({
"doctype": "Sales Order",
"customer": "_Test Customer",
"items": [{"item_code": "_Test Item", "qty": 1, "rate": 100}]
}).insert()
so.submit()
self.assertEqual(so.docstatus, 1)
```
**Behavior**: Extends `UnitTestCase`. Calls `frappe.init()` and sets up site connection. Loads test record dependencies via `make_test_records()`. Provides `primary_connection()` and `secondary_connection()` context managers. `maxDiff = 10_000`.
## Test File Structure
ALWAYS place test files in the doctype directory following this naming convention:
```
myapp/
└── mymodule/
└── doctype/
└── my_doctype/
├── my_doctype.py # DocType controller
├── my_doctype.json # DocType definition
├── test_my_doctype.py # Test file (MUST start with test_)
└── test_records.json # Optional: test fixtures
```
**Rules**:
- ALWAYS prefix test files with `test_` — the test runner ignores files without this prefix
- ALWAYS use `test_{doctype_in_snake_case}.py` for doctype tests
- NEVER place test files outside the doctype directory for doctype-specific tests
- Non-doctype tests can live in any module, but MUST follow the `test_*.py` naming
## Test Fixtures
### Method 1: test_records.json (Static Fixtures)
Create a `test_records.json` file in the doctype directory:
```json
[
{
"doctype": "My Doctype",
"field1": "_Test Value 1",
"field2": 100
},
{
"doctype": "My Doctype",
"field1": "_Test Value 2",
"field2": 200
}
]
```
**Rules**:
- ALWAYS prefix test data values with `_Test` to distinguish from production data
- The test runner auto-loads these before running tests for the doctype
- Link field dependencies are resolved automatically — the runner builds records for linked DocTypes first
### Method 2: _test_records List (In-Module Fixtures)
```python
_test_records = [
{"doctype": "My Doctype", "field1": "_Test Value 1"},
{"doctype": "My Doctype", "field1": "_Test Value 2"},
]
```
### Method 3: Programmatic Fixtures (Recommended for Complex Data)
```python
def create_test_data():
if frappe.flags.test_data_created:
return
frappe.set_user("Administrator")
frappe.get_doc({
"doctype": "My Doctype",
"field1": "_Test Value",
}).insert()
frappe.flags.test_data_created = True
class TestMyDoctype(IntegrationTestCase):
def setUp(self):
create_test_data()
```
ALWAYS use `frappe.flags` to prevent duplicate fixture creation across test methods.
## Testing Patterns
### Testing Document Lifecycle
```python
class TestInvoice(IntegrationTe