Generate boundary value test cases for numeric ranges, string lengths, date ranges, collection sizes, and domain-specific constraints using systematic analysis techniques
You are an expert QA engineer specializing in boundary value analysis (BVA) and equivalence class partitioning. When the user asks you to generate boundary value tests, identify edge cases, or create systematic test data for ranges and constraints, follow these detailed instructions to produce comprehensive boundary test suites that catch off-by-one errors, overflow conditions, and constraint violations.
tests/
boundary/
numeric/
integer-ranges.test.ts
float-precision.test.ts
currency-amounts.test.ts
percentage-values.test.ts
string/
length-limits.test.ts
unicode-boundaries.test.ts
encoding-limits.test.ts
date-time/
date-ranges.test.ts
time-zones.test.ts
epoch-boundaries.test.ts
collection/
array-sizes.test.ts
pagination.test.ts
batch-limits.test.ts
file/
file-size-limits.test.ts
upload-constraints.test.ts
api/
rate-limits.test.ts
payload-sizes.test.ts
concurrent-connections.test.ts
generators/
boundary-generator.ts
equivalence-partitioner.ts
test-case-formatter.ts
fixtures/
constraint-definitions.ts
type-boundaries.ts
vitest.config.ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['tests/boundary/**/*.test.ts'],
globals: true,
reporters: ['verbose', 'json'],
outputFile: 'boundary-test-report.json',
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.d.ts'],
},
},
});
// tests/boundary/fixtures/constraint-definitions.ts
/**
* Centralized constraint definitions for all boundary-testable inputs.
* Each constraint defines the valid range and metadata for a specific input parameter.
*/
export interface BoundaryConstraint {
name: string;
type: 'integer' | 'float' | 'string' | 'date' | 'collection' | 'fileSize';
min: number | string;
max: number | string;
unit?: string;
description: string;
}
export const constraints: BoundaryConstraint[] = [
// Numeric constraints
{ name: 'user-age', type: 'integer', min: 13, max: 120, unit: 'years', description: 'User age for registration' },
{ name: 'quantity', type: 'integer', min: 1, max: 99, unit: 'items', description: 'Product quantity in cart' },
{ name: 'price', type: 'float', min: 0.01, max: 999999.99, unit: 'USD', description: 'Product price' },
{ name: 'discount', type: 'float', min: 0, max: 100, unit: 'percent', description: 'Discount percentage' },
{ name: 'rating', type: 'float', min: 1.0, max: 5.0, unit: 'stars', description: 'Product rating' },
// String constraints
{ name: 'username', type: 'string', min: 3, max: 30, unit: 'characters', description: 'Username length' },
{ name: 'password', type: 'string', min: 8, max: 128, unit: 'characters', description: 'Password length' },
{ name: 'bio', type: 'string', min: 0, max: 500, unit: 'characters', description: 'User biography' },
{ name: 'search-query', type: 'string', min: 1, max: 200, unit: 'characters', description: 'Search input' },
// Collection constraints
{ name: 'cart-items', type: 'collection', min: 0, max: 50, unit: 'items', description: 'Shopping cart item count' },
{ name: 'tags', type: 'collection', min: 0, max: 10, unit: 'tags', description: 'Tags per item' },
{ name: 'page-size', type: 'integer', min: 1, max: 100, unit: 'results', description: 'Pagination page size' },
// File constraints
{ name: 'avatar', type: 'fileSize', min: 1, max: 5242880, unit: 'bytes', description: 'Avatar file size (5MB max)' },
{ name: 'document', type: 'fileSize', min: 1, max: 26214400, unit: 'bytes', description: 'Document upload (25MB max)' },
];
// tests/boundary/fixtures/type-boundaries.ts
/**
* Language-level type boundaries for JavaScript/TypeScript.
* These values represent the limits of the data types themselves,
* independent of domain-specific constraints.
*/
export const TYPE_BOUNDARIES = {
integer: {
MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER, // 9007199254740991
MIN_SAFE_INTEGER: Number.MIN_SAFE_INTEGER, // -9007199254740991
MAX_VALUE: Number.MAX_VALUE, // 1.7976931348623157e+308
MIN_VALUE: Number.MIN_VALUE, // 5e-324 (smallest positive)
POSITIVE_INFINITY: Number.POSITIVE_INFINITY,
NEGATIVE_INFINITY: Number.NEGATIVE_INFINITY,
NaN: Number.NaN,
NEGATIVE_ZERO: -0,
ZERO: 0,
},
string: {
EMPTY: '',
SINGLE_CHAR: 'a',
MAX_PRACTICAL_LENGTH: 10_000_000, // Most systems struggle beyond this
NULL_BYTE: '\x00',
UNICODE_BMP_MAX: '\uFFFF',
UNICODE_SUPPLEMENTARY: '\u{1F600}', // Emoji (2 UTF-16 code units)
RTL_CHAR: '\u0627', // Arabic alef
ZERO_WIDTH_SPACE: '\u200B',
COMBINING_CHARS: 'e\u0301', // e + combining acute accent
},
date: {
EPOCH: new Date(0), // 1970-01-01T00:00:00Z
PRE_EPOCH: new Date(-1), // 1969-12-31T23:59:59.999Z
Y2K: new Date('2000-01-01T00:00:00Z'),
Y2K38: new Date('2038-01-19T03:14:07Z'), // Unix 32-bit overflow
FAR_FUTURE: new Date('9999-12-31T23:59:59Z'),
LEAP_DAY: new Date('2024-02-29T00:00:00Z'),
DST_SPRING: new Date('2024-03-10T02:00:00'), // US DST spring forward
DST_FALL: new Date('2024-11-03T02:00:00'), // US DST fall back
INVALID: new Date('invalid'),
},
};
The core of boundary value analysis is systematically generating test values at and around the edges of valid ranges.
// tests/boundary/generators/boundary-generator.ts
export interface BoundaryTestCase {
value: number | string;
expected: 'valid' | 'invalid';
category: 'below-min' | 'at-min' | 'above-min' | 'nominal' | 'below-max' | 'at-max' | 'above-max' | 'type-boundary';
description: string;
}
/**
* Generate boundary value test cases for an integer range.
* Applies the BVA triplet pattern: boundary, boundary-1, boundary+1.
*/
export function generateIntegerBoundaries(
min: number,
max: number,
name: string
): BoundaryTestCase[] {
return [
// Below minimum boundary
{ value: min - 2, expected: 'invalid', category: 'below-min', description: `${name}: far below minimum (${min - 2})` },
{ value: min - 1, expected: 'invalid', category: 'below-min', description: `${name}: just below minimum (${min - 1})` },
// Minimum boundary
{ value: min, expected: 'valid', category: 'at-min', description: `${name}: exactly at minimum (${min})` },
{ value: min + 1, expected: 'valid', category: 'above-min', description: `${name}: just above minimum (${min + 1})` },
// Nominal value
{ value: Math.floor((min + max) / 2), expected: 'valid', category: 'nominal', description: `${name}: nominal mid-range` },
// Maximum boundary
{ value: max - 1, expected: 'valid', category: 'below-max', description: `${name}: just below maximum (${max - 1})` },
{ value: max, expected: 'valid', category: 'at-max', description: `${name}: exactly at maximum (${max})` },
// Above maximum boundary
{ value: max + 1, expected: 'invalid', category: 'above-max', description: `${name}: just above maximum (${max + 1})` },
{ value: max + 2, expected: 'invalid', category: 'above-max', description: `${name}: far above maximum (${max + 2})` },
// Type boundaries
{ value: 0, expected: min > 0 ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: zero` },
{ value: -1, expected: min > -1 ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: negative one` },
{ value: Number.MAX_SAFE_INTEGER, expected: max < Number.MAX_SAFE_INTEGER ? 'invalid' : 'valid', category: 'type-boundary', description: `${name}: MAX_SAFE_INTEGER` },
];
}
/**
* Generate boundary values for floating-point ranges.
* Includes precision-sensitive values around the boundaries.
*/
export function generateFloatBoundaries(
min: number,
max: number,
precision: number,
name: string
): BoundaryTestCase[] {
const step = Math.pow(10, -precision); // e.g., 0.01 for 2 decimal places
return [
// Below minimum
{ value: parseFloat((min - step).toFixed(precision)), expected: 'invalid', category: 'below-min', description: `${name}: one step below minimum` },
// Minimum boundary
{ value: min, expected: 'valid', category: 'at-min', description: `${name}: exactly at minimum (${min})` },
{ value: parseFloat((min + step).toFixed(precision)), expected: 'valid', category: 'above-min', description: `${name}: one step above minimum` },
// Nominal
{ value: parseFloat(((min + max) / 2).toFixed(precision)), expected: 'valid', category: 'nominal', description: `${name}: nominal mid-range` },
// Maximum boundary
{ value: parseFloat((max - step).toFixed(precision)), expected: 'valid', category: 'below-max', description: `${name}: one step below maximum` },
{ value: max, expected: 'valid', category: 'at-max', description: `${name}: exactly at maximum (${max})` },
// Above maximum
{ value: parseFloat((max + step).toFixed(precision)), expected: 'invalid', category: 'above-max', description: `${name}: one step above maximum` },
// Floating-point precision edge cases
{ value: 0.1 + 0.2, expected: 'valid', category: 'type-boundary', description: `${name}: IEEE 754 precision (0.1 + 0.2 = ${0.1 + 0.2})` },
{ value: Number.EPSILON, expected: min <= Number.EPSILON ? 'valid' : 'invalid', category: 'type-boundary', description: `${name}: Number.EPSILON` },
];
}
/**
* Generate boundary values for string length constraints.
*/
export function generateStringLengthBoundaries(
minLength: number,
maxLength: number,
name: string
): BoundaryTestCase[] {
return [
// Empty and below minimum
{ value: '', expected: minLength > 0 ? 'invalid' : 'valid', category: 'below-min', description: `${name}: empty string (0 chars)` },
{ value: 'a'.repeat(Math.max(0, minLength - 1)), expected: minLength > 0 ? 'invalid' : 'valid', category: 'below-min', description: `${name}: ${Math.max(0, minLength - 1)} characters` },
// Minimum boundary
{ value: 'a'.repeat(minLength), expected: 'valid', category: 'at-min', description: `${name}: exactly ${minLength} characters (minimum)` },
{ value: 'a'.repeat(minLength + 1), expected: 'valid', category: 'above-min', description: `${name}: ${minLength + 1} characters` },
// Nominal
{ value: 'a'.repeat(Math.floor((minLength + maxLength) / 2)), expected: 'valid', category: 'nominal', description: `${name}: mid-range length` },
// Maximum boundary
{ value: 'a'.repeat(maxLength - 1), expected: 'valid', category: 'below-max', description: `${name}: ${maxLength - 1} characters` },
{ value: 'a'.repeat(maxLength), expected: 'valid', category: 'at-max', description: `${name}: exactly ${maxLength} characters (maximum)` },
// Above maximum
{ value: 'a'.repeat(maxLength + 1), expected: 'invalid', category: 'above-max', description: `${name}: ${maxLength + 1} characters` },
{ value: 'a'.repeat(maxLength + 100), expected: 'invalid', category: 'above-max', description: `${name}: significantly over maximum` },
];
}
Using the generator to produce actual test suites.
// tests/boundary/numeric/integer-ranges.test.ts
import { describe, it, expect } from 'vitest';
import { generateIntegerBoundaries } from '../generators/boundary-generator';
import { constraints } from '../fixtures/constraint-definitions';
// Example: testing a validation function
function validateAge(age: number): { valid: boolean; error?: string } {
if (!Number.isInteger(age)) return { valid: false, error: 'Age must be a whole number' };
if (age < 13) return { valid: false, error: 'Must be at least 13 years old' };
if (age > 120) return { valid: false, error: 'Age exceeds maximum allowed value' };
return { valid: true };
}
function validateQuantity(qty: number): { valid: boolean; error?: string } {
if (!Number.isInteger(qty)) return { valid: false, error: 'Quantity must be a whole number' };
if (qty < 1) return { valid: false, error: 'Quantity must be at least 1' };
if (qty > 99) return { valid: false, error: 'Maximum quantity is 99' };
return { valid: true };
}
describe('Integer Range Boundaries', () => {
describe('User Age Validation', () => {
const ageConstraint = constraints.find((c) => c.name === 'user-age')!;
const testCases = generateIntegerBoundaries(
ageConstraint.min as number,
ageConstraint.max as number,
'age'
);
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validateAge(tc.value as number);
if (tc.expected === 'valid') {
expect(result.valid).toBe(true);
expect(result.error).toBeUndefined();
} else {
expect(result.valid).toBe(false);
expect(result.error).toBeDefined();
expect(result.error!.length).toBeGreaterThan(0);
}
});
}
// Additional type boundary tests
it('rejects NaN', () => {
const result = validateAge(NaN);
expect(result.valid).toBe(false);
});
it('rejects Infinity', () => {
const result = validateAge(Infinity);
expect(result.valid).toBe(false);
});
it('rejects floating-point numbers', () => {
const result = validateAge(25.5);
expect(result.valid).toBe(false);
});
it('rejects negative zero', () => {
const result = validateAge(-0);
// -0 is technically 0, which is below minimum
expect(result.valid).toBe(false);
});
});
describe('Product Quantity Validation', () => {
const qtyConstraint = constraints.find((c) => c.name === 'quantity')!;
const testCases = generateIntegerBoundaries(
qtyConstraint.min as number,
qtyConstraint.max as number,
'quantity'
);
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validateQuantity(tc.value as number);
expect(result.valid).toBe(tc.expected === 'valid');
});
}
});
});
Financial calculations require special boundary attention due to IEEE 754 floating-point precision.
// tests/boundary/numeric/currency-amounts.test.ts
import { describe, it, expect } from 'vitest';
import { generateFloatBoundaries } from '../generators/boundary-generator';
function validatePrice(price: number): { valid: boolean; error?: string } {
if (typeof price !== 'number' || isNaN(price)) {
return { valid: false, error: 'Price must be a number' };
}
if (!isFinite(price)) {
return { valid: false, error: 'Price must be finite' };
}
if (price < 0.01) {
return { valid: false, error: 'Price must be at least $0.01' };
}
if (price > 999999.99) {
return { valid: false, error: 'Price exceeds maximum' };
}
// Check for more than 2 decimal places
const decimalStr = price.toString();
const decimalPart = decimalStr.includes('.') ? decimalStr.split('.')[1] : '';
if (decimalPart.length > 2) {
return { valid: false, error: 'Price must have at most 2 decimal places' };
}
return { valid: true };
}
describe('Currency Amount Boundaries', () => {
const testCases = generateFloatBoundaries(0.01, 999999.99, 2, 'price');
for (const tc of testCases) {
it(`${tc.description} -> ${tc.expected}`, () => {
const result = validatePrice(tc.value as number);
expect(result.valid).toBe(tc.expected === 'valid');
});
}
describe('Floating-point precision edge cases', () => {
it('handles 0.1 + 0.2 correctly', () => {
// 0.1 + 0.2 = 0.30000000000000004 in IEEE 754
// The system must handle this gracefully
const sum = 0.1 + 0.2;
const result = validatePrice(parseFloat(sum.toFixed(2)));
expect(result.valid).toBe(true);
});
it('handles currency multiplication precision', () => {
// $19.99 * 3 = 59.97, but floating-point may produce 59.96999...
const total = 19.99 * 3;
const result = validatePrice(parseFloat(total.toFixed(2)));
expect(result.valid).toBe(true);
});
it('rejects amounts with more than 2 decimal places', () => {
expect(validatePrice(9.999).valid).toBe(false);
expect(validatePrice(0.001).valid).toBe(false);
expect(validatePrice(100.123).valid).toBe(false);
});
it('accepts exact boundary: $0.01', () => {
expect(validatePrice(0.01).valid).toBe(true);
});
it('accepts exact boundary: $999999.99', () => {
expect(validatePrice(999999.99).valid).toBe(true);
});
it('rejects $0.00', () => {
expect(validatePrice(0.00).valid).toBe(false);
});
it('rejects negative amounts', () => {
expect(validatePrice(-0.01).valid).toBe(false);
expect(validatePrice(-100).valid).toBe(false);
});
});
});
Dates have unique boundary conditions: leap years, daylight saving time transitions, epoch boundaries, and the Y2K38 problem.
// tests/boundary/date-time/date-ranges.test.ts
import { describe, it, expect } from 'vitest';
import { TYPE_BOUNDARIES } from '../fixtures/type-boundaries';
function validateEventDate(date: Date): { valid: boolean; error?: string } {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return { valid: false, error: 'Invalid date' };
}
const now = new Date();
const oneYearFromNow = new Date(now);
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
if (date < now) {
return { valid: false, error: 'Event date must be in the future' };
}
if (date > oneYearFromNow) {
return { valid: false, error: 'Event date must be within one year' };
}
return { valid: true };
}
function validateBirthDate(date: Date): { valid: boolean; error?: string } {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return { valid: false, error: 'Invalid date' };
}
const now = new Date();
const age = now.getFullYear() - date.getFullYear();
if (date > now) {
return { valid: false, error: 'Birth date cannot be in the future' };
}
if (age > 150) {
return { valid: false, error: 'Birth date too far in the past' };
}
return { valid: true };
}
describe('Date Range Boundaries', () => {
describe('Event Date Validation', () => {
it('rejects dates in the past', () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
expect(validateEventDate(yesterday).valid).toBe(false);
});
it('accepts tomorrow', () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
expect(validateEventDate(tomorrow).valid).toBe(true);
});
it('accepts exactly one year from now', () => {
const oneYear = new Date();
oneYear.setFullYear(oneYear.getFullYear() + 1);
expect(validateEventDate(oneYear).valid).toBe(true);
});
it('rejects one year and one day from now', () => {
const beyondOneYear = new Date();
beyondOneYear.setFullYear(beyondOneYear.getFullYear() + 1);
beyondOneYear.setDate(beyondOneYear.getDate() + 1);
expect(validateEventDate(beyondOneYear).valid).toBe(false);
});
it('rejects invalid date object', () => {
expect(validateEventDate(TYPE_BOUNDARIES.date.INVALID).valid).toBe(false);
});
});
describe('Critical Date Boundaries', () => {
it('handles leap year boundary: Feb 29', () => {
const leapDay = new Date('2024-02-29T12:00:00Z');
expect(leapDay.getDate()).toBe(29);
expect(leapDay.getMonth()).toBe(1); // 0-indexed
});
it('handles non-leap year: Feb 28 to Mar 1', () => {
const feb28 = new Date('2023-02-28T23:59:59Z');
const mar1 = new Date(feb28.getTime() + 1000);
expect(mar1.getDate()).toBe(1);
expect(mar1.getMonth()).toBe(2); // March
});
it('handles epoch boundary', () => {
const result = validateBirthDate(TYPE_BOUNDARIES.date.EPOCH);
expect(result.valid).toBe(true);
});
it('handles pre-epoch date', () => {
const result = validateBirthDate(new Date('1969-12-31T23:59:59Z'));
expect(result.valid).toBe(true);
});
it('handles Y2K38 boundary', () => {
// Unix 32-bit timestamp overflow: 2038-01-19T03:14:07Z
const y2k38 = TYPE_BOUNDARIES.date.Y2K38;
expect(y2k38.getTime()).toBeGreaterThan(0);
});
it('handles far future date', () => {
const farFuture = TYPE_BOUNDARIES.date.FAR_FUTURE;
expect(farFuture.getFullYear()).toBe(9999);
});
it('handles month-end transitions', () => {
const monthEnds = [
new Date('2024-01-31T23:59:59Z'), // Jan 31 -> Feb 1
new Date('2024-03-31T23:59:59Z'), // Mar 31 -> Apr 1
new Date('2024-04-30T23:59:59Z'), // Apr 30 -> May 1
new Date('2024-12-31T23:59:59Z'), // Dec 31 -> Jan 1 (year transition)
];
for (const date of monthEnds) {
const nextSecond = new Date(date.getTime() + 1000);
expect(nextSecond.getDate()).toBe(1);
}
});
it('handles year-end transition', () => {
const yearEnd = new Date('2024-12-31T23:59:59Z');
const newYear = new Date(yearEnd.getTime() + 1000);
expect(newYear
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
npx skills add PramodDutta/Boundary Value Generator下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer
Tags:boundary-value, bva, equivalence-partitioning, edge-cases, test-design, numeric-testing, range-testing