sf-testing

安装量: 219
排名: #3979

安装

npx skills add https://github.com/jaganpro/sf-skills --skill sf-testing

Expert testing engineer specializing in Apex test execution, code coverage analysis, mock frameworks, and agentic test-fix loops. Execute tests, analyze failures, and automatically fix issues.

Core Responsibilities

  • Test Execution: Run Apex tests via sf apex run test with coverage analysis

  • Coverage Analysis: Parse coverage reports, identify untested code paths

  • Failure Analysis: Parse test failures, identify root causes, suggest fixes

  • Agentic Test-Fix Loop: Automatically fix failing tests and re-run until passing

  • Test Generation: Create test classes using sf-apex patterns

  • Bulk Testing: Validate with 251+ records for governor limit safety

Workflow (5-Phase Pattern)

Phase 1: Test Discovery

Use AskUserQuestion to gather:

  • Test scope (single class, all tests, specific test suite)

  • Target org alias

  • Coverage threshold requirement (default: 75%, recommended: 90%)

  • Whether to enable agentic fix loop

Then:

  • Check existing tests: Glob: **/*Test*.cls, Glob: **/*_Test.cls

  • Check for Test Data Factories: Glob: **/*TestDataFactory*.cls

  • Create TodoWrite tasks

Phase 2: Test Execution

Run Single Test Class:

sf apex run test --class-names MyClassTest --code-coverage --result-format json --output-dir test-results --target-org [alias]

Run All Tests:

sf apex run test --test-level RunLocalTests --code-coverage --result-format json --output-dir test-results --target-org [alias]

Run Specific Methods:

sf apex run test --tests MyClassTest.testMethod1 --tests MyClassTest.testMethod2 --code-coverage --result-format json --target-org [alias]

Run Test Suite:

sf apex run test --suite-names MySuite --code-coverage --result-format json --target-org [alias]

Phase 3: Results Analysis

Parse test-results JSON:

Read: test-results/test-run-id.json

Coverage Summary Output:

📊 TEST EXECUTION RESULTS
════════════════════════════════════════════════════════════════

Test Run ID: 707xx0000000000
Org: my-sandbox
Duration: 45.2s

SUMMARY
───────────────────────────────────────────────────────────────
✅ Passed:    42
❌ Failed:    3
⏭️ Skipped:   0
📈 Coverage: 78.5%

FAILED TESTS
───────────────────────────────────────────────────────────────
❌ AccountServiceTest.testBulkInsert
   Line 45: System.AssertException: Assertion Failed
   Expected: 200, Actual: 199

❌ LeadScoringTest.testNullHandling
   Line 23: System.NullPointerException: Attempt to de-reference null

❌ OpportunityTriggerTest.testValidation
   Line 67: System.DmlException: FIELD_CUSTOM_VALIDATION_EXCEPTION

COVERAGE BY CLASS
───────────────────────────────────────────────────────────────
Class                          Lines    Covered  Uncovered  %
AccountService                 150      142      8          94.7% ✅
LeadScoringService            85       68       17         80.0% ✅
OpportunityTrigger            45       28       17         62.2% ⚠️
ContactHelper                 30       15       15         50.0% ❌

UNCOVERED LINES (OpportunityTrigger)
───────────────────────────────────────────────────────────────
Lines 23-28: Exception handling block
Lines 45-52: Bulk processing edge case
Lines 78-82: Null check branch

Phase 4: Agentic Test-Fix Loop

When tests fail, automatically:

┌─────────────────────────────────────────────────────────────────┐
│                    AGENTIC TEST-FIX LOOP                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Parse failure message and stack trace                        │
│  2. Identify root cause:                                         │
│     - Assertion failure → Check expected vs actual               │
│     - NullPointerException → Add null checks                     │
│     - DmlException → Check validation rules, required fields     │
│     - LimitException → Reduce SOQL/DML in test                  │
│  3. Read the failing test class                                  │
│  4. Read the class under test                                    │
│  5. Generate fix using sf-apex skill                             │
│  6. Re-run the specific failing test                             │
│  7. Repeat until passing (max 3 attempts)                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Failure Analysis Decision Tree:

| System.AssertException | Wrong expected value or logic bug | Analyze assertion, check if test or code is wrong

| System.NullPointerException | Missing null check or test data | Add null safety or fix test data setup

| System.DmlException | Validation rule, required field, trigger | Check org config, add required fields to test data

| System.LimitException | Governor limit hit | Refactor to use bulkified patterns

| System.QueryException | No rows returned | Add test data or adjust query

| System.TypeException | Type mismatch | Fix type casting or data format

Auto-Fix Command:

Skill(skill="sf-apex", args="Fix failing test [TestClassName].[methodName] - Error: [error message]")

Phase 5: Coverage Improvement

If coverage < threshold:

  • Identify Uncovered Lines:
sf apex run test --class-names MyClassTest --code-coverage --detailed-coverage --result-format json --target-org [alias]
  • Generate Tests for Uncovered Code:
Read: force-app/main/default/classes/MyClass.cls (lines 45-52)

Then use sf-apex to generate test methods targeting those lines.

  • Bulk Test Validation:
Skill(skill="sf-data", args="Create 251 [ObjectName] records for bulk testing")
  • Re-run with New Tests:
sf apex run test --class-names MyClassTest --code-coverage --result-format json --target-org [alias]

Best Practices (120-Point Scoring)

| Test Coverage | 25 | 90%+ class coverage; all public methods tested; edge cases covered

| Assertion Quality | 25 | Assert class used; meaningful messages; positive AND negative tests

| Bulk Testing | 20 | Test with 251+ records; verify no SOQL/DML in loops under load

| Test Data | 20 | Test Data Factory used; no hardcoded IDs; @TestSetup for efficiency

| Isolation | 15 | SeeAllData=false; no org dependencies; mock external callouts

| Documentation | 15 | Test method names describe scenario; comments for complex setup

Scoring Thresholds:

⭐⭐⭐⭐⭐ 108-120 pts (90%+)  → Production Ready
⭐⭐⭐⭐   96-107 pts (80-89%) → Good, minor improvements
⭐⭐⭐    84-95 pts  (70-79%) → Acceptable, needs work
⭐⭐      72-83 pts  (60-69%) → Below standard
⭐        <72 pts   (<60%)   → BLOCKED - Major issues

⛔ TESTING GUARDRAILS (MANDATORY)

BEFORE running tests, verify:

| Org authenticated | sf org display --target-org [alias] | Tests need valid org connection

| Classes deployed | sf project deploy report --target-org [alias] | Can't test undeployed code

| Test data exists | Check @TestSetup or TestDataFactory | Tests need data to operate on

NEVER do these:

| @IsTest(SeeAllData=true) | Tests depend on org data, break in clean orgs | Always SeeAllData=false (default)

| Hardcoded Record IDs | IDs differ between orgs | Query or create in test

| No assertions | Tests pass without validating anything | Assert every expected outcome

| Single record tests only | Misses bulk trigger issues | Always test with 200+ records

| Test.startTest() without Test.stopTest() | Async code won't execute | Always pair start/stop

CLI Command Reference

Test Execution Commands

| sf apex run test | Run tests | See examples above

| sf apex get test | Get async test status | sf apex get test --test-run-id 707xx...

| sf apex list log | List debug logs | sf apex list log --target-org alias

| sf apex tail log | Stream logs real-time | sf apex tail log --target-org alias

Useful Flags

| --code-coverage | Include coverage in results

| --detailed-coverage | Line-by-line coverage (slower)

| --result-format json | Machine-parseable output

| --output-dir | Save results to directory

| --synchronous | Wait for completion (default)

| --test-level RunLocalTests | All tests except managed packages

| --test-level RunAllTestsInOrg | Every test including packages

Test Patterns & Templates

Pattern 1: Basic Test Class

Use template: templates/basic-test.cls

@IsTest
private class AccountServiceTest {

    @TestSetup
    static void setupTestData() {
        // Use Test Data Factory for consistent data creation
        List<Account> accounts = TestDataFactory.createAccounts(5);
        insert accounts;
    }

    @IsTest
    static void testCreateAccount_Success() {
        // Given
        Account testAccount = new Account(Name = 'Test Account');

        // When
        Test.startTest();
        Id accountId = AccountService.createAccount(testAccount);
        Test.stopTest();

        // Then
        Assert.isNotNull(accountId, 'Account ID should not be null');
        Account inserted = [SELECT Name FROM Account WHERE Id = :accountId];
        Assert.areEqual('Test Account', inserted.Name, 'Account name should match');
    }

    @IsTest
    static void testCreateAccount_NullInput_ThrowsException() {
        // Given
        Account nullAccount = null;

        // When/Then
        try {
            Test.startTest();
            AccountService.createAccount(nullAccount);
            Test.stopTest();
            Assert.fail('Expected IllegalArgumentException was not thrown');
        } catch (IllegalArgumentException e) {
            Assert.isTrue(e.getMessage().contains('cannot be null'),
                'Error message should mention null: ' + e.getMessage());
        }
    }
}

Pattern 2: Bulk Test (251+ Records)

Use template: templates/bulk-test.cls

@IsTest
static void testBulkInsert_251Records() {
    // Given - 251 records crosses the 200-record batch boundary
    List<Account> accounts = TestDataFactory.createAccounts(251);

    // When
    Test.startTest();
    insert accounts;  // Triggers fire in batches of 200, then 51
    Test.stopTest();

    // Then
    Integer count = [SELECT COUNT() FROM Account];
    Assert.areEqual(251, count, 'All 251 accounts should be inserted');

    // Verify no governor limits hit
    Assert.isTrue(Limits.getQueries() < 100,
        'Should not approach SOQL limit: ' + Limits.getQueries());
}

Pattern 3: Mock Callout Test

Use template: templates/mock-callout-test.cls

@IsTest
private class ExternalAPIServiceTest {

    // Mock class for HTTP callouts
    private class MockHttpResponse implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setStatusCode(200);
            res.setBody('{"success": true, "data": {"id": "12345"}}');
            return res;
        }
    }

    @IsTest
    static void testCallExternalAPI_Success() {
        // Given
        Test.setMock(HttpCalloutMock.class, new MockHttpResponse());

        // When
        Test.startTest();
        String result = ExternalAPIService.callAPI('test-endpoint');
        Test.stopTest();

        // Then
        Assert.isTrue(result.contains('success'), 'Response should indicate success');
    }
}

Pattern 4: Test Data Factory

Use template: templates/test-data-factory.cls

@IsTest
public class TestDataFactory {

    public static List<Account> createAccounts(Integer count) {
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < count; i++) {
            accounts.add(new Account(
                Name = 'Test Account ' + i,
                Industry = 'Technology',
                BillingCity = 'San Francisco'
            ));
        }
        return accounts;
    }

    public static List<Contact> createContacts(Integer count, Id accountId) {
        List<Contact> contacts = new List<Contact>();
        for (Integer i = 0; i < count; i++) {
            contacts.add(new Contact(
                FirstName = 'Test',
                LastName = 'Contact ' + i,
                AccountId = accountId,
                Email = 'test' + i + '@example.com'
            ));
        }
        return contacts;
    }

    // Convenience method with insert
    public static List<Account> createAndInsertAccounts(Integer count) {
        List<Account> accounts = createAccounts(count);
        insert accounts;
        return accounts;
    }
}

Agentic Test-Fix Loop Implementation

How It Works

When the agentic loop is enabled, sf-testing will:

  • Run tests and capture results

  • Parse failures to identify error type and location

  • Read source files (test class + class under test)

  • Analyze root cause using the decision tree above

  • Generate fix by invoking sf-apex skill

  • Re-run failing test to verify fix

  • Iterate until passing or max attempts (3)

Example Agentic Flow

User: "Run tests for AccountService with auto-fix enabled"

Claude:
1. sf apex run test --class-names AccountServiceTest --code-coverage --result-format json
2. Parse results: 1 failure - testBulkInsert line 45 NullPointerException
3. Read AccountServiceTest.cls (line 45 context)
4. Read AccountService.cls (trace the null reference)
5. Identify: Missing null check in AccountService.processAccounts()
6. Skill(sf-apex): Add null safety to AccountService.processAccounts()
7. Deploy fix
8. Re-run: sf apex run test --tests AccountServiceTest.testBulkInsert
9. ✅ Passing! Report success.

Cross-Skill Integration

| sf-apex | Generate test classes, fix failing code | Skill(skill="sf-apex", args="Create test class for LeadService")

| sf-data | Create bulk test data (251+ records) | Skill(skill="sf-data", args="Create 251 Leads for bulk testing")

| sf-deploy | Deploy test classes to org | Skill(skill="sf-deploy", args="Deploy tests to sandbox")

| sf-debug | Analyze failures with debug logs | Skill(skill="sf-debug", args="Analyze test failure logs")

Common Test Failures & Fixes

| MIXED_DML_OPERATION | User + non-setup object in same transaction | Use System.runAs() or separate transactions

| CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY | Trigger or flow error | Check trigger logic with debug logs

| REQUIRED_FIELD_MISSING | Test data incomplete | Add required fields to TestDataFactory

| DUPLICATE_VALUE | Unique field conflict | Use dynamic values or delete existing

| FIELD_CUSTOM_VALIDATION_EXCEPTION | Validation rule fired | Meet validation criteria in test data

| UNABLE_TO_LOCK_ROW | Record lock conflict | Use FOR UPDATE or retry logic

Dependencies

Required: Target org with sf CLI authenticated Recommended: sf-apex (for auto-fix), sf-data (for bulk test data), sf-debug (for log analysis)

Install: /plugin install github:Jaganpro/sf-skills/sf-testing

Documentation

| testing-best-practices.md | General testing guidelines

| cli-commands.md | SF CLI test commands

| mocking-patterns.md | Mocking vs Stubbing, DML mocking, HttpCalloutMock

| performance-optimization.md | Fast tests, reduce execution time

Templates

| basic-test.cls | Standard test class with Given-When-Then

| bulk-test.cls | 251+ record bulk testing

| mock-callout-test.cls | HTTP callout mocking

| test-data-factory.cls | Reusable test data creation

| dml-mock.cls | DML abstraction for 35x faster tests

| stub-provider-example.cls | StubProvider for dynamic behavior

Credits

See CREDITS.md for acknowledgments of community resources that shaped this skill.

License

MIT License. See LICENSE file. Copyright (c) 2024-2025 Jag Valaiyapathy

返回排行榜