AgentSkillsCN

testing-patterns

测试模式与原则。单元测试、集成测试、模拟测试策略。

SKILL.md
--- frontmatter
name: testing-patterns
description: Testing patterns and principles. Unit, integration, mocking strategies.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash

Testing Patterns

Principles for reliable test suites.


1. Testing Pyramid

code
        /\          E2E (Few)
       /  \         Critical flows
      /----\
     /      \       Integration (Some)
    /--------\      API, DB queries
   /          \
  /------------\    Unit (Many)
                    Functions, classes

2. AAA Pattern

StepPurpose
ArrangeSet up test data
ActExecute code under test
AssertVerify outcome

3. Test Type Selection

When to Use Each

TypeBest ForSpeed
UnitPure functions, logicFast (<50ms)
IntegrationAPI, DB, servicesMedium
E2ECritical user flowsSlow

4. Unit Test Principles

Good Unit Tests

PrincipleMeaning
Fast< 100ms each
IsolatedNo external deps
RepeatableSame result always
Self-checkingNo manual verification
TimelyWritten with code

What to Unit Test

TestDon't Test
Business logicFramework code
Edge casesThird-party libs
Error handlingSimple getters

5. Integration Test Principles

What to Test

AreaFocus
API endpointsRequest/response
DatabaseQueries, transactions
External servicesContracts

Setup/Teardown

PhaseAction
Before AllConnect resources
Before EachReset state
After EachClean up
After AllDisconnect

6. Mocking Principles

When to Mock

MockDon't Mock
External APIsThe code under test
Database (unit)Simple dependencies
Time/randomPure functions
NetworkIn-memory stores

Mock Types

TypeUse
StubReturn fixed values
SpyTrack calls
MockSet expectations
FakeSimplified implementation

7. Test Organization

Naming

PatternExample
Should behavior"should return error when..."
When condition"when user not found..."
Given-when-then"given X, when Y, then Z"

Grouping

LevelUse
describeGroup related tests
it/testIndividual case
beforeEachCommon setup

8. Test Data

Strategies

ApproachUse
FactoriesGenerate test data
FixturesPredefined datasets
BuildersFluent object creation

Principles

  • Use realistic data
  • Randomize non-essential values (faker)
  • Share common fixtures
  • Keep data minimal

9. Best Practices

PracticeWhy
One assert per testClear failure reason
Independent testsNo order dependency
Fast testsRun frequently
Descriptive namesSelf-documenting
Clean upAvoid side effects

10. Anti-Patterns

❌ Don't✅ Do
Test implementationTest behavior
Duplicate test codeUse factories
Complex test setupSimplify or split
Ignore flaky testsFix root cause
Skip cleanupReset state


11. Frappe Test Patterns

Test Runner

Frappe uses Python unittest with its own test runner via Bench CLI:

bash
# All tests for an app
bench --site mysite.localhost run-tests --app my_ehealth

# Single DocType
bench --site mysite.localhost run-tests --doctype "Patient"

# Single module
bench --site mysite.localhost run-tests --module my_ehealth.my_ehealth.doctype.patient.test_patient

# Verbose
bench --site mysite.localhost run-tests --app my_ehealth -v

Test File Location

Each DocType has a test file at:

code
my_ehealth/my_ehealth/doctype/<doctype_name>/test_<doctype_name>.py

Frappe Test Utilities

UtilityPurpose
frappe.tests.utils.FrappeTestCaseBase class — handles setup, teardown, DB rollback
frappe.get_doc(...)Create/fetch documents in tests
frappe.mock("method")Patch Frappe methods
self.assertRaises(...)Assert validation errors
frappe.set_user("user@example.com")Test as specific user/role
frappe.flags.in_test = TrueAuto-set by runner; skip side effects

Example: DocType Unit Test

python
import frappe
from frappe.tests.utils import FrappeTestCase

class TestPatient(FrappeTestCase):
    def setUp(self):
        self.patient = frappe.get_doc({
            "doctype": "Patient",
            "first_name": "Test",
            "last_name": "Patient",
            "sex": "Male",
            "dob": "1990-01-01"
        }).insert(ignore_permissions=True)

    def test_patient_creation(self):
        self.assertTrue(self.patient.name)
        self.assertEqual(self.patient.patient_name, "Test Patient")

    def test_duplicate_uid_rejected(self):
        self.patient.uid = "ID-12345"
        self.patient.save()
        duplicate = frappe.get_doc({
            "doctype": "Patient",
            "first_name": "Dup",
            "sex": "Female",
            "uid": "ID-12345"
        })
        self.assertRaises(frappe.DuplicateEntryError, duplicate.insert)

    def test_role_based_access(self):
        frappe.set_user("receptionist@example.com")
        # Receptionist can read Patient
        patient = frappe.get_doc("Patient", self.patient.name)
        self.assertTrue(patient.name)

    def tearDown(self):
        frappe.set_user("Administrator")
        frappe.delete_doc("Patient", self.patient.name, force=True)

Testing Workflows and Submissions

python
def test_encounter_submit_creates_medical_record(self):
    encounter = frappe.get_doc({
        "doctype": "Patient Encounter",
        "patient": self.patient.name,
        "practitioner": self.practitioner.name,
        "encounter_date": frappe.utils.today()
    }).insert()
    encounter.submit()
    # Verify downstream effect
    records = frappe.get_all("Medical Record", filters={"reference_name": encounter.name})
    self.assertTrue(len(records) > 0)

Testing Whitelisted APIs

python
def test_whitelist_api(self):
    from my_ehealth.api.encounter import get_patient_history
    frappe.set_user("doctor@example.com")
    result = get_patient_history(patient=self.patient.name)
    self.assertIsInstance(result, list)

Frappe Test Best Practices

PracticeWhy
Inherit FrappeTestCaseAuto DB rollback per test
Use ignore_permissions=True in setUpIsolate permission tests from data setup
Call frappe.set_user("Administrator") in tearDownReset user context
Never hardcode document namesUse variables from setUp
Test controller hooks (validate, on_submit)These contain business rules
Test RBAC with frappe.set_user()Verify role-based access
Avoid testing Frappe coreOnly test your app logic

Remember: Tests are documentation. If someone can't understand what the code does from the tests, rewrite them.