算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
test-specialist【设计与创意】Test-writing patterns for JS/TS, Python, Go, and Rust (unit, integration, E2E, visual regression). Use only when the user explicitly asks for tests or test coverage; for debugging without a test request, use debug-systematic.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Test-writing patterns for JS/TS, Python, Go, and Rust, plus bug-analysis habits that apply when tests are requested.
Use this skill only when the user explicitly asks for tests. Without that request, validate by reviewing the diff and the code it touches.
Use for (when tests are requested):
Don't use when:
debug-systematicgeneric-code-reviewertech-debt-analyzergeneric-feature-developer| Project Type | Unit Tests | Component | E2E |
|---|---|---|---|
| React/Next.js | Vitest/Jest | Testing Library | Playwright |
| Node.js | Vitest/Jest | Supertest | Playwright |
| Static | Jest | - | Playwright |
Browser automation (Playwright, Percy, Chromatic, npx browser installs) is not installed or run on the owner's Mac. Run it only when the user asks, through an environment they have approved for it.
describe("calculateTotal", () => {
test("sums amounts correctly", () => {
// Arrange
const items = [{ amount: 100 }, { amount: 50 }];
// Act
const total = calculateTotal(items);
// Assert
expect(total).toBe(150);
});
test("handles empty list", () => {
expect(calculateTotal([])).toBe(0);
});
});
// ✅ Test user behavior, not implementation
it('creates item when user clicks Add', async () => {
const user = userEvent.setup();
render(<ItemList />);
await user.click(screen.getByRole('button', { name: /add/i }));
await user.type(screen.getByLabelText(/title/i), 'New item');
await user.click(screen.getByRole('button', { name: /save/i }));
expect(screen.getByText('New item')).toBeInTheDocument();
});
import { test, expect } from "@playwright/test";
test("user can complete checkout", async ({ page }) => {
await page.goto("/products");
// Add to cart
await page.click('button:has-text("Add to Cart")');
await page.click('a:has-text("Cart")');
// Checkout
await page.click('button:has-text("Checkout")');
await page.fill('[name="email"]', "test@example.com");
await page.click('button:has-text("Place Order")');
// Verify
await expect(page.locator("h1")).toContainText("Order Confirmed");
});
test("POST /items creates item", async () => {
const response = await request(app)
.post("/api/items")
.send({ name: "Test" })
.expect(201);
expect(response.body).toMatchObject({ id: expect.any(Number) });
});
When debugging an issue:
test("handles concurrent updates", async () => {
const promises = Array.from({ length: 100 }, () => increment());
await Promise.all(promises);
expect(getCount()).toBe(100);
});
test.each([null, undefined, "", 0])("handles invalid input: %p", (input) => {
expect(() => process(input)).toThrow("Invalid");
});
test("handles edge cases", () => {
expect(paginate([], 1, 10)).toEqual([]); // empty
expect(paginate([item], 1, 10)).toEqual([item]); // single
expect(paginate(items25, 3, 10)).toHaveLength(5); // partial last page
});
test("prevents SQL injection", async () => {
const malicious = "'; DROP TABLE users; --";
await expect(search(malicious)).resolves.not.toThrow();
});
test("sanitizes XSS", () => {
const xss = '<script>alert("xss")</script>';
expect(sanitize(xss)).not.toContain("<script>");
});
test("requires auth", async () => {
await request(app).post("/api/items").expect(401);
});
test("handles large datasets efficiently", () => {
const largeList = Array.from({ length: 10000 }, (_, i) => ({ value: i }));
const start = performance.now();
process(largeList);
expect(performance.now() - start).toBeLessThan(100);
});
| Code Type | Target |
|---|---|
| Critical paths | 90%+ |
| Business logic | 85%+ |
| UI components | 75%+ |
| Utilities | 70%+ |
| Situation | Action |
|---|---|
| Adding feature (tests requested) | Write test first (TDD) |
| Fixing bug (tests requested) | Write failing test, then fix |
| Improving coverage | Find gaps, prioritize critical paths |
| Code review | Check edge cases, error handling |
import pytest
from myapp.services import UserService
@pytest.fixture
def user_service(db_session):
"""Provide a UserService with test database."""
return UserService(session=db_session)
@pytest.fixture
def sample_user(user_service):
"""Create and return a sample user."""
return user_service.create(name="Test User", email="test@example.com")
class TestUserService:
def test_create_user(self, user_service):
user = user_service.create(name="John", email="john@example.com")
assert user.name == "John"
assert user.id is not None
def test_get_user_not_found(self, user_service):
with pytest.raises(UserNotFoundError, match="User 999 not found"):
user_service.get(999)
@pytest.mark.parametrize("email,is_valid", [
("user@example.com", True),
("user@sub.domain.com", True),
("invalid", False),
("@example.com", False),
("", False),
])
def test_validate_email(self, user_service, email: str, is_valid: bool):
assert user_service.validate_email(email) == is_valid
from unittest.mock import Mock, patch, AsyncMock
def test_send_notification(user_service):
with patch("myapp.services.email_client") as mock_email:
mock_email.send = Mock(return_value=True)
user_service.notify(user_id=1, message="Hello")
mock_email.send.assert_called_once_with(
to="test@example.com",
body="Hello",
)
# Async mocking
@pytest.mark.asyncio
async def test_fetch_data():
with patch("myapp.client.fetch", new_callable=AsyncMock) as mock_fetch:
mock_fetch.return_value = {"status": "ok"}
result = await process_data()
assert result["status"] == "ok"
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture(scope="session")
def engine():
return create_engine("sqlite:///:memory:")
@pytest.fixture(scope="function")
def db_session(engine):
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.rollback()
session.close()
Base.metadata.drop_all(engine)
func TestCalculateDiscount(t *testing.T) {
tests := []struct {
name string
amount float64
code string
want float64
wantErr bool
}{
{
name: "valid 10% discount",
amount: 100.0,
code: "SAVE10",
want: 90.0,
},
{
name: "no discount",
amount: 100.0,
code: "",
want: 100.0,
},
{
name: "invalid code",
amount: 100.0,
code: "INVALID",
wantErr: true,
},
{
name: "zero amount",
amount: 0,
code: "SAVE10",
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateDiscount(tt.amount, tt.code)
if (err != nil) != tt.wantErr {
t.Errorf("CalculateDiscount() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CalculateDiscount() = %v, want %v", got, tt.want)
}
})
}
}
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
// Mock
type MockUserRepo struct {
mock.Mock
}
func (m *MockUserRepo) FindByID(id string) (*User, error) {
args := m.Called(id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*User), args.Error(1)
}
func TestGetUser(t *testing.T) {
repo := new(MockUserRepo)
repo.On("FindByID", "123").Return(&User{ID: "123", Name: "John"}, nil)
service := NewUserService(repo)
user, err := service.GetUser("123")
require.NoError(t, err)
assert.Equal(t, "John", user.Name)
repo.AssertExpectations(t)
}
// HTTP handler testing
func TestGetUserHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/users/123", nil)
w := httptest.NewRecorder()
handler := NewHandler(mockService)
handler.GetUser(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var user User
err := json.NewDecoder(w.Body).Decode(&user)
require.NoError(t, err)
assert.Equal(t, "123", user.ID)
}
// src/lib.rs - Unit tests (same file)
pub fn calculate_discount(amount: f64, percentage: f64) -> Result<f64, DiscountError> {
if percentage < 0.0 || percentage > 100.0 {
return Err(DiscountError::InvalidPercentage(percentage));
}
Ok(amount * (1.0 - percentage / 100.0))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_discount() {
let result = calculate_discount(100.0, 10.0).unwrap();
assert!((result - 90.0).abs() < f64::EPSILON);
}
#[test]
fn test_zero_discount() {
assert_eq!(calculate_discount(100.0, 0.0).unwrap(), 100.0);
}
#[test]
fn test_invalid_percentage() {
assert!(matches!(
calculate_discount(100.0, 150.0),
Err(DiscountError::InvalidPercentage(_))
));
}
#[test]
fn test_negative_percentage() {
assert!(calculate_discount(100.0, -10.0).is_err());
}
}
// tests/integration_test.rs - Integration tests (separate file)
use my_crate::calculate_discount;
#[test]
fn test_full_workflow() {
let original = 200.0;
let discounted = calculate_discount(original, 25.0).unwrap();
assert_eq!(discounted, 150.0);
}
use proptest::prelude::*;
proptest! {
#[test]
fn discount_never_exceeds_original(amount in 0.0f64..10000.0, pct in 0.0f64..100.0) {
let result = calculate_discount(amount, pct).unwrap();
prop_assert!(result <= amount);
prop_assert!(result >= 0.0);
}
#[test]
fn roundtrip_serialization(name in "[a-zA-Z]{1,50}", age in 0u32..150) {
let user = User { name: name.clone(), age };
let json = serde_json::to_string(&user).unwrap();
let deserialized: User = serde_json::from_str(&json).unwrap();
prop_assert_eq!(user, deserialized);
}
}
import { test, expect } from "@playwright/test";
test("homepage visual regression", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveScreenshot("homepage.png", {
maxDiffPixelRatio: 0.01,
});
});
test("component states", async ({ page }) => {
await page.goto("/components");
// Default state
await expect(page.locator(".card")).toHaveScreenshot("card-default.png");
// Hover state
await page.locator(".card").hover();
await expect(page.locator(".card")).toHaveScreenshot("card-hover.png");
// Full page with specific viewport
await page.setViewportSize({ width: 375, height: 812 }); // iPhone
await expect(page).toHaveScreenshot("homepage-mobile.png");
});
// Update snapshots: npx playwright test --update-snapshots
import percySnapshot from "@percy/playwright";
test("visual test with Percy", async ({ page }) => {
await page.goto("/dashboard");
await percySnapshot(page, "Dashboard");
// Percy compares across browsers and viewport sizes
});
# Run Chromatic on Storybook stories
npx chromatic --project-token=<token>
# CI integration
# Chromatic captures every story as a visual snapshot
# and detects pixel-level changes across PRs
| Tool | Approach | Best For |
|---|---|---|
| Playwright | Local screenshot comparison | E2E visual tests, free |
| Percy | Cloud cross-browser comparison | Multi-browser, team review |
| Chromatic | Storybook story snapshots | Component library visual QA |
CLAUDE.md - Testing rules