TechLead
Lesson 11 of 25
5 min read
AI-Native Engineering

AI-Powered Testing Strategies

Learn to use AI to generate comprehensive test suites, identify untested code paths, create edge case tests, and maintain test quality at scale

Why AI Changes Testing Forever

Testing has always been the activity most likely to be skipped under time pressure. Engineers know tests are important, but when the deadline looms, tests are the first thing cut. AI eliminates this tradeoff. When generating a comprehensive test suite takes minutes instead of hours, there is no excuse to skip testing.

But AI-generated tests need careful review. AI is excellent at test structure and coverage breadth, but it can assert the wrong behavior — testing that buggy code continues to be buggy. The AI-native testing workflow is: generate, review, correct, run, iterate.

What AI Does Well in Testing

  • Test Structure: Creates well-organized describe/it blocks with proper setup/teardown
  • Coverage Breadth: Thinks of edge cases you might miss (empty arrays, null values, boundary conditions)
  • Boilerplate: Handles mocking, fixtures, and test utilities setup
  • Pattern Matching: If you show it one test file, it replicates the pattern perfectly for others

The AI Testing Workflow

# Step 1: Generate tests
> Write comprehensive tests for app/lib/cart.ts. Include:
  - All exported functions
  - Edge cases: empty cart, single item, max quantity limits
  - Price calculations with discounts and tax
  - Cart persistence (add, remove, update quantity, clear)
  Use our existing test patterns from app/lib/auth.test.ts

# Step 2: Review the generated tests critically
# Ask yourself for each assertion:
# "Is this testing the CORRECT behavior, or just the CURRENT behavior?"

# Step 3: Run the tests
> Run the tests you just wrote. Fix any failures that are bugs in
  the tests (not bugs in the code being tested).

# Step 4: Check coverage
> Run coverage for cart.ts. Are there any untested code paths?
  Add tests for any uncovered branches.

Unit Test Generation

// Source code: app/lib/pricing.ts
export function calculateDiscount(
  subtotal: number,
  couponCode: string | null,
  isVIP: boolean
): { discount: number; finalPrice: number } {
  let discount = 0;

  // VIP gets 10% off
  if (isVIP) {
    discount += subtotal * 0.1;
  }

  // Coupon codes
  if (couponCode === "SAVE20") {
    discount += subtotal * 0.2;
  } else if (couponCode === "FLAT10") {
    discount += 10;
  }

  // Cap discount at 50% of subtotal
  discount = Math.min(discount, subtotal * 0.5);

  return {
    discount: Math.round(discount * 100) / 100,
    finalPrice: Math.round((subtotal - discount) * 100) / 100,
  };
}

// AI-generated tests (after review and corrections):
import { describe, it, expect } from "vitest";
import { calculateDiscount } from "./pricing";

describe("calculateDiscount", () => {
  describe("no discounts", () => {
    it("returns zero discount for non-VIP with no coupon", () => {
      const result = calculateDiscount(100, null, false);
      expect(result.discount).toBe(0);
      expect(result.finalPrice).toBe(100);
    });
  });

  describe("VIP discount", () => {
    it("applies 10% discount for VIP users", () => {
      const result = calculateDiscount(100, null, true);
      expect(result.discount).toBe(10);
      expect(result.finalPrice).toBe(90);
    });
  });

  describe("coupon codes", () => {
    it("applies 20% discount for SAVE20 coupon", () => {
      const result = calculateDiscount(100, "SAVE20", false);
      expect(result.discount).toBe(20);
      expect(result.finalPrice).toBe(80);
    });

    it("applies flat $10 discount for FLAT10 coupon", () => {
      const result = calculateDiscount(100, "FLAT10", false);
      expect(result.discount).toBe(10);
      expect(result.finalPrice).toBe(90);
    });

    it("ignores invalid coupon codes", () => {
      const result = calculateDiscount(100, "INVALID", false);
      expect(result.discount).toBe(0);
      expect(result.finalPrice).toBe(100);
    });
  });

  describe("stacking discounts", () => {
    it("stacks VIP and coupon discounts", () => {
      const result = calculateDiscount(100, "SAVE20", true);
      // 10% VIP + 20% coupon = 30%
      expect(result.discount).toBe(30);
      expect(result.finalPrice).toBe(70);
    });
  });

  describe("discount cap", () => {
    it("caps total discount at 50% of subtotal", () => {
      // VIP (10%) + SAVE20 (20%) on small amount
      // where FLAT10 would exceed 50%
      const result = calculateDiscount(15, "FLAT10", true);
      // VIP: 1.50 + FLAT10: 10 = 11.50, but cap is 7.50
      expect(result.discount).toBe(7.5);
      expect(result.finalPrice).toBe(7.5);
    });
  });

  describe("edge cases", () => {
    it("handles zero subtotal", () => {
      const result = calculateDiscount(0, "SAVE20", true);
      expect(result.discount).toBe(0);
      expect(result.finalPrice).toBe(0);
    });

    it("handles very small amounts", () => {
      const result = calculateDiscount(0.01, null, true);
      expect(result.discount).toBe(0);
      expect(result.finalPrice).toBe(0.01);
    });

    it("rounds currency values correctly", () => {
      const result = calculateDiscount(33.33, "SAVE20", false);
      expect(result.discount).toBe(6.67);
      expect(result.finalPrice).toBe(26.66);
    });
  });
});

Testing Strategies by Type

Test Type AI Effectiveness Prompt Strategy Human Review Focus
Unit TestsExcellentSpecify edge cases and expected behaviorVerify assertions match intended behavior
Integration TestsGoodDescribe the flow and expected outcomesCheck mock boundaries and real-world accuracy
E2E TestsModerateProvide page URLs, user flows, selectorsVerify selectors are stable, flows match UX
Snapshot TestsExcellentJust ask — AI knows the patternVerify snapshots capture meaningful structure
Performance TestsModerateSpecify thresholds and measurement pointsValidate benchmarks are realistic

Identifying Untested Code Paths

# Ask Claude Code to find gaps
> Run our test coverage report and identify the 5 most critical
  files with low coverage. For each file, list the specific
  untested functions and branches. Then write tests for the
  most important untested paths.

# Focus on risk, not coverage percentage
> Look at app/lib/payments.ts. Which code paths handle error
  scenarios? Are all error paths tested? Write tests for any
  untested error handling code — these are the paths most likely
  to fail in production.

The Biggest Mistake: Trusting AI Assertions

The most dangerous AI testing mistake is not reviewing the assertions. AI generates a test that passes, and you assume it is correct. But the test might be asserting the wrong thing — it tests that the function returns what it currently returns, not what it should return. Always read each expect() statement and ask: "Is this the correct expected value, or just the current value?"

Pro Tip: Write Tests First, Then Implement

AI makes TDD (Test-Driven Development) much more practical. Ask Claude to write tests first based on your requirements, review and correct the test expectations, then ask Claude to implement the code that makes the tests pass. This ensures the implementation matches your intent, not the other way around.

Summary

AI eliminates the time excuse for skipping tests. Generate comprehensive test suites in minutes, but always review assertions for correctness. Focus your review energy on verifying that tests assert the right behavior, not just the current behavior. Use AI to find coverage gaps, generate edge cases, and write the boilerplate. Use your judgment to ensure the tests actually protect against real bugs.

Continue Learning