Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Database schema validation, data integrity testing, migration testing, transaction isolation, and query performance. Use when testing data persistence, ensuring referential integrity, or validating database migrations.
.claude/skills/microck-database-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 39% | 0% |
Data is your most valuable asset. Database bugs cause data loss/corruption.
Database testing ensures schema correctness, data integrity, transaction safety, and query performance. Critical for preventing catastrophic data issues.
Validate database structure:
sql-- Test schema exists SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'users'; -- Test column types SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'users'; -- Test constraints SELECT constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_name = 'users';
Test with code:
javascripttest('users table has correct schema', async () => { const schema = await db.raw(` SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = 'users' `); expect(schema).toContainEqual({ column_name: 'id', data_type: 'integer', is_nullable: 'NO' }); expect(schema).toContainEqual({ column_name: 'email', data_type: 'character varying', is_nullable: 'NO' }); });
Test constraints:
javascripttest('email must be unique', async () => { await db.users.create({ email: 'test@example.com' }); // Duplicate should fail await expect( db.users.create({ email: 'test@example.com' }) ).rejects.toThrow('unique constraint violation'); }); test('foreign key prevents orphaned records', async () => { const user = await db.users.create({ email: 'test@example.com' }); await db.orders.create({ userId: user.id, total: 100 }); // Cannot delete user with orders await expect( db.users.delete({ id: user.id }) ).rejects.toThrow('foreign key constraint'); }); test('check constraint validates data', async () => { // Age must be ≥ 18 await expect( db.users.create({ email: 'minor@example.com', age: 17 }) ).rejects.toThrow('check constraint violation'); });
Test database migrations:
javascriptimport { migrate, rollback } from './migrations'; test('migration adds users table', async () => { // Start fresh await rollback(); // Run migration await migrate(); // Verify table exists const tables = await db.raw(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' `); expect(tables.map(t => t.table_name)).toContain('users'); }); test('migration is reversible', async () => { await migrate(); await rollback(); // Table should be gone const tables = await db.raw(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' `); expect(tables.map(t => t.table_name)).not.toContain('users'); }); test('migration preserves existing data', async () => { // Create data before migration await db.users.create({ email: 'test@example.com' }); // Run migration that adds 'age' column await migrate('add-age-column'); // Data should still exist const user = await db.users.findOne({ email: 'test@example.com' }); expect(user).toBeDefined(); expect(user.age).toBeNull(); // New column, null default });
Test ACID properties:
javascripttest('transaction rolls back on error', async () => { const initialCount = await db.users.count(); try { await db.transaction(async (trx) => { await trx('users').insert({ email: 'user1@example.com' }); await trx('users').insert({ email: 'user2@example.com' }); // Force error throw new Error('Rollback test'); }); } catch (error) { // Expected } // No users should be inserted const finalCount = await db.users.count(); expect(finalCount).toBe(initialCount); }); test('concurrent transactions are isolated', async () => { const user = await db.users.create({ email: 'test@example.com', balance: 100 }); // Two concurrent withdrawals const withdraw1 = db.transaction(async (trx) => { const current = await trx('users').where({ id: user.id }).first(); await sleep(100); // Simulate delay await trx('users').where({ id: user.id }).update({ balance: current.balance - 50 }); }); const withdraw2 = db.transaction(async (trx) => { const current = await trx('users').where({ id: user.id }).first(); await sleep(100); await trx('users').where({ id: user.id }).update({ balance: current.balance - 50 }); }); await Promise.all([withdraw1, withdraw2]); // With proper isolation, balance should be 0, not 50 const final = await db.users.findOne({ id: user.id }); expect(final.balance).toBe(0); // Not 50! });
Test slow queries:
javascripttest('user lookup by email is fast', async () => { // Seed 10,000 users await seedUsers(10000); const start = Date.now(); await db.users.findOne({ email: 'user5000@example.com' }); const duration = Date.now() - start; // Should use index on email expect(duration).toBeLessThan(10); // < 10ms }); test('EXPLAIN shows index usage', async () => { const explain = await db.raw(` EXPLAIN SELECT * FROM users WHERE email = 'test@example.com' `); // Should show index scan, not sequential scan const plan = explain.rows[0]['QUERY PLAN']; expect(plan).toContain('Index Scan'); expect(plan).not.toContain('Seq Scan'); });
Database bugs are catastrophic.
Test migrations before production:
With Agents: qe-test-data-architect generates realistic test data with referential integrity. qe-test-executor runs DB migration tests automatically in CI/CD.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 13,449 | 9,832 | -27% | 1 | 1 | 0% | 2,509 | 3,495 | +39% | 0 | 0 | — |
case-02 | pass→pass | 12,328 | 8,530 | -31% | 1 | 1 | 0% | 2,115 | 3,092 | +46% | 0 | 0 | — |
case-03 | pass→pass | 14,106 | 7,853 | -44% | 1 | 1 | 0% | 2,274 | 2,920 | +28% | 0 | 0 | — |
case-04 | pass→pass | 12,083 | 6,753 | -44% | 1 | 1 | 0% | 2,054 | 2,703 | +32% | 0 | 0 | — |
case-05 | fail→fail | 12,714 | 8,406 | -34% | 1 | 1 | 0% | 2,361 | 3,253 | +38% | 0 | 0 | — |
case-06 | fail→pass | 13,723 | 11,460 | -16% | 1 | 1 | 0% | 2,478 | 3,815 | +54% | 0 | 0 | — |
case-07 | pass→pass | 12,960 | 15,149 | +17% | 1 | 1 | 0% | 2,366 | 4,385 | +85% | 0 | 0 | — |
case-08 | pass→pass | 8,096 | 6,137 | -24% | 1 | 1 | 0% | 1,382 | 2,720 | +97% | 0 | 0 | — |
case-09 | fail→fail | 17,504 | 13,279 | -24% | 1 | 1 | 0% | 3,124 | 4,206 | +35% | 0 | 0 | — |
case-10 | pass→pass | 19,051 | 12,124 | -36% | 1 | 1 | 0% | 3,418 | 3,923 | +15% | 0 | 0 | — |
case-11 | pass→pass | 11,598 | 11,458 | -1% | 1 | 1 | 0% | 2,066 | 3,672 | +78% | 0 | 0 | — |
case-12 | pass→pass | 11,709 | 9,039 | -23% | 1 | 1 | 0% | 2,285 | 3,525 | +54% | 0 | 0 | — |
case-13 | fail→pass | 6,759 | 2,256 | -67% | 1 | 1 | 0% | 1,081 | 2,069 | +91% | 0 | 0 | — |
case-14 | fail→pass | 8,781 | 1,836 | -79% | 1 | 1 | 0% | 1,396 | 2,005 | +44% | 0 | 0 | — |
case-15 | pass→pass | 5,481 | 3,869 | -29% | 1 | 1 | 0% | 1,006 | 2,479 | +146% | 0 | 0 | — |
case-16 | pass→pass | 14,007 | 9,344 | -33% | 1 | 1 | 0% | 2,163 | 3,120 | +44% | 0 | 0 | — |
case-17 | pass→pass | 7,760 | 3,842 | -50% | 1 | 1 | 0% | 1,259 | 2,312 | +84% | 0 | 0 | — |
case-18 | pass→pass | 13,956 | 10,617 | -24% | 1 | 1 | 0% | 2,587 | 3,763 | +45% | 0 | 0 | — |
case-19 | fail→fail | 16,839 | 11,889 | -29% | 1 | 1 | 0% | 2,651 | 3,702 | +40% | 0 | 0 | — |
case-20 | fail→pass | 10,936 | 2,357 | -78% | 1 | 1 | 0% | 1,622 | 2,090 | +29% | 0 | 0 | — |
case-21 | pass→pass | 8,976 | 3,760 | -58% | 1 | 1 | 0% | 1,475 | 2,331 | +58% | 0 | 0 | — |
case-22 | pass→pass | 13,598 | 12,304 | -10% | 1 | 1 | 0% | 2,708 | 4,145 | +53% | 0 | 0 | — |
case-23 | fail→fail | 19,879 | 18,947 | -5% | 1 | 1 | 0% | 3,637 | 5,066 | +39% | 0 | 0 | — |
case-24 | pass→pass | 21,476 | 22,967 | +7% | 1 | 1 | 0% | 3,236 | 5,448 | +68% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.