Testing and Debugging Strategies
1. Test Case Generation Strategies
| Strategy | Description | Application |
|---|---|---|
| Equivalence Partitioning | Divide input domain into classes where behavior is same | Test one value from each partition instead of all values |
| Boundary Testing | Test at and around boundaries of input ranges | min-1, min, min+1, max-1, max, max+1 |
| Random Testing | Generate random inputs to find unexpected failures | Fuzzing, stress testing with random data |
| Property-based Testing | Define properties that should hold for all inputs | Idempotence, commutativity, reversibility |
| Coverage-driven | Generate tests to maximize code coverage | Branch coverage, path coverage metrics |
Example 1: Test Case Generator for Sorting Function
class SortTestGenerator {
// Equivalence partitioning
static getPartitions() {
return {
empty: [],
single: [42],
sorted: [1, 2, 3, 4, 5],
reverse: [5, 4, 3, 2, 1],
duplicates: [3, 1, 4, 1, 5, 9, 2, 6, 5],
allSame: [7, 7, 7, 7],
negative: [-5, -1, -10, 3, 0]
};
}
// Boundary value testing
static getBoundaryTests() {
return [
[], // min size (0)
[1], // min size + 1 (1)
[1, 2], // 2 elements
Array(1000).fill(0).map((_, i) => i), // large sorted
[Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER],
[0, -0, 0.1, -0.1] // floating point boundaries
];
}
// Random test generation
static generateRandomTests(count = 100) {
const tests = [];
for (let i = 0; i < count; i++) {
const size = Math.floor(Math.random() * 1000);
const arr = Array(size).fill(0).map(() =>
Math.floor(Math.random() * 2000) - 1000
);
tests.push(arr);
}
return tests;
}
// Property-based: sort should be idempotent
static verifyIdempotence(sortFn, arr) {
const sorted1 = sortFn([...arr]);
const sorted2 = sortFn([...sorted1]);
return JSON.stringify(sorted1) === JSON.stringify(sorted2);
}
// Property-based: sort should preserve elements
static verifyPreservation(sortFn, arr) {
const original = [...arr].sort();
const result = sortFn([...arr]).sort();
return JSON.stringify(original) === JSON.stringify(result);
}
// Run comprehensive test suite
static runAllTests(sortFn) {
console.log('Testing Partitions...');
const partitions = this.getPartitions();
for (const [name, arr] of Object.entries(partitions)) {
const sorted = sortFn([...arr]);
const passed = this.isSorted(sorted) &&
this.verifyPreservation(sortFn, arr);
console.log(` ${name}: ${passed ? 'PASS' : 'FAIL'}`);
}
console.log('Testing Boundaries...');
const boundaries = this.getBoundaryTests();
boundaries.forEach((arr, i) => {
const sorted = sortFn([...arr]);
const passed = this.isSorted(sorted);
console.log(` Boundary ${i}: ${passed ? 'PASS' : 'FAIL'}`);
});
console.log('Random Testing...');
const randomTests = this.generateRandomTests(50);
let passCount = 0;
randomTests.forEach(arr => {
if (this.isSorted(sortFn([...arr])) &&
this.verifyPreservation(sortFn, arr)) {
passCount++;
}
});
console.log(` Random: ${passCount}/50 PASS`);
}
static isSorted(arr) {
for (let i = 1; i < arr.length; i++) {
if (arr[i] < arr[i-1]) return false;
}
return true;
}
}
// Example usage
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
SortTestGenerator.runAllTests(bubbleSort);
Example 2: Automated Test Case Generator with Coverage
class TestCaseGenerator {
constructor(targetFunction) {
this.targetFunction = targetFunction;
this.coverage = new Set();
}
// Generate tests to maximize branch coverage
generateBranchCoverageTests(inputRanges) {
const tests = [];
// For each input parameter, try boundary values
inputRanges.forEach((range, paramIndex) => {
const { min, max, type } = range;
if (type === 'number') {
// Boundary values for numbers
const values = [
min - 1, // below range
min, // minimum
min + 1, // just above min
Math.floor((min + max) / 2), // middle
max - 1, // just below max
max, // maximum
max + 1, // above range
0, // zero (special)
-1, // negative boundary
null, // null check
undefined, // undefined check
NaN // NaN check
];
values.forEach(val => {
const testCase = this.createTestCase(paramIndex, val, inputRanges);
tests.push(testCase);
});
} else if (type === 'array') {
// Array-specific test cases
const arrayTests = [
[], // empty
[min], // single element
[min, max], // two elements
Array(max).fill(min), // large array
null, // null array
undefined // undefined array
];
arrayTests.forEach(arr => {
const testCase = this.createTestCase(paramIndex, arr, inputRanges);
tests.push(testCase);
});
}
});
return tests;
}
createTestCase(targetParam, value, ranges) {
// Create test case with one parameter varied
const testCase = ranges.map((range, i) => {
if (i === targetParam) return value;
// Use middle value for other params
if (range.type === 'number') {
return Math.floor((range.min + range.max) / 2);
} else if (range.type === 'array') {
return [range.min, range.max];
}
});
return testCase;
}
// Execute tests and track coverage
runTests(testCases) {
const results = [];
testCases.forEach((testCase, index) => {
try {
const result = this.targetFunction(...testCase);
const branchId = this.getBranchSignature(testCase, result);
this.coverage.add(branchId);
results.push({
index,
input: testCase,
output: result,
passed: true,
branch: branchId
});
} catch (error) {
results.push({
index,
input: testCase,
error: error.message,
passed: false
});
}
});
return {
results,
coverage: this.coverage.size,
totalTests: testCases.length,
passRate: results.filter(r => r.passed).length / results.length
};
}
getBranchSignature(input, output) {
// Simple branch signature based on input/output characteristics
return `${input.map(i => typeof i).join(',')}->${typeof output}`;
}
// Report coverage gaps
suggestAdditionalTests() {
// Suggest tests for uncovered scenarios
return [
'Test with null inputs',
'Test with extreme values',
'Test with empty collections',
'Test with duplicate elements',
'Test with negative numbers'
];
}
}
// Example: Testing binary search function
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
const generator = new TestCaseGenerator(binarySearch);
const testCases = generator.generateBranchCoverageTests([
{ min: 0, max: 100, type: 'array' }, // array parameter
{ min: -100, max: 100, type: 'number' } // target parameter
]);
const report = generator.runTests(testCases);
console.log(`Coverage: ${report.coverage} branches`);
console.log(`Pass Rate: ${(report.passRate * 100).toFixed(2)}%`);
Note: Effective test generation combines multiple strategies. Start with equivalence partitioning for broad coverage, add boundary tests for edge cases, then use random testing to find unexpected failures. Property-based testing ensures algorithmic correctness across all inputs.
2. Edge Case Identification Techniques
| Edge Case Category | Examples | Why Critical |
|---|---|---|
| Empty/Null Input | [], null, undefined, "" | Most functions fail without proper checks |
| Single Element | [1], "a", single node | Breaks algorithms expecting pairs/comparisons |
| Duplicates | [1,1,1], "aaa" | Unique vs non-unique assumptions |
| Sorted/Reverse | [1,2,3], [3,2,1] | Best/worst case performance |
| Numeric Extremes | MAX_INT, MIN_INT, 0, -0, Infinity | Overflow, underflow, division by zero |
| Special Characters | whitespace, unicode, emoji | String processing edge cases |
Example 1: Comprehensive Edge Case Checker
class EdgeCaseIdentifier {
static identifyArrayEdgeCases(arr) {
const edgeCases = [];
// Empty check
if (arr === null || arr === undefined) {
edgeCases.push({ type: 'NULL_INPUT', severity: 'HIGH' });
}
if (arr && arr.length === 0) {
edgeCases.push({ type: 'EMPTY_ARRAY', severity: 'HIGH' });
}
// Single element
if (arr && arr.length === 1) {
edgeCases.push({ type: 'SINGLE_ELEMENT', severity: 'MEDIUM' });
}
// All duplicates
if (arr && new Set(arr).size === 1) {
edgeCases.push({ type: 'ALL_DUPLICATES', severity: 'MEDIUM' });
}
// Already sorted
if (arr && this.isSorted(arr)) {
edgeCases.push({ type: 'ALREADY_SORTED', severity: 'LOW' });
}
// Reverse sorted
if (arr && this.isSorted([...arr].reverse())) {
edgeCases.push({ type: 'REVERSE_SORTED', severity: 'MEDIUM' });
}
// Contains extremes
if (arr && arr.some(x =>
x === Number.MAX_SAFE_INTEGER ||
x === Number.MIN_SAFE_INTEGER ||
x === Infinity ||
x === -Infinity
)) {
edgeCases.push({ type: 'EXTREME_VALUES', severity: 'HIGH' });
}
// Contains special values
if (arr && arr.some(x =>
x === 0 || x === -0 || Number.isNaN(x) || x === null
)) {
edgeCases.push({ type: 'SPECIAL_VALUES', severity: 'HIGH' });
}
// Very large array
if (arr && arr.length > 100000) {
edgeCases.push({ type: 'LARGE_INPUT', severity: 'MEDIUM' });
}
return edgeCases;
}
static isSorted(arr) {
for (let i = 1; i < arr.length; i++) {
if (arr[i] < arr[i-1]) return false;
}
return true;
}
static identifyStringEdgeCases(str) {
const edgeCases = [];
if (str === null || str === undefined) {
edgeCases.push({ type: 'NULL_STRING', severity: 'HIGH' });
}
if (str === '') {
edgeCases.push({ type: 'EMPTY_STRING', severity: 'HIGH' });
}
if (str && str.length === 1) {
edgeCases.push({ type: 'SINGLE_CHAR', severity: 'MEDIUM' });
}
// All same character
if (str && new Set(str).size === 1) {
edgeCases.push({ type: 'ALL_SAME_CHAR', severity: 'MEDIUM' });
}
// Contains whitespace
if (str && /\s/.test(str)) {
edgeCases.push({ type: 'CONTAINS_WHITESPACE', severity: 'LOW' });
}
// Contains unicode/emoji
if (str && /[^\x00-\x7F]/.test(str)) {
edgeCases.push({ type: 'UNICODE_CHARACTERS', severity: 'MEDIUM' });
}
// Very long string
if (str && str.length > 100000) {
edgeCases.push({ type: 'LARGE_STRING', severity: 'MEDIUM' });
}
return edgeCases;
}
// Generate test cases for identified edge cases
static generateEdgeCaseTests(dataType) {
if (dataType === 'array') {
return [
{ input: null, description: 'Null array' },
{ input: undefined, description: 'Undefined array' },
{ input: [], description: 'Empty array' },
{ input: [42], description: 'Single element' },
{ input: [5, 5, 5, 5], description: 'All duplicates' },
{ input: [1, 2, 3, 4, 5], description: 'Already sorted' },
{ input: [5, 4, 3, 2, 1], description: 'Reverse sorted' },
{ input: [Number.MAX_SAFE_INTEGER], description: 'Max value' },
{ input: [Number.MIN_SAFE_INTEGER], description: 'Min value' },
{ input: [0, -0], description: 'Zero and negative zero' },
{ input: [NaN, null, undefined], description: 'Special values' },
{ input: Array(100000).fill(1), description: 'Large array' }
];
} else if (dataType === 'string') {
return [
{ input: null, description: 'Null string' },
{ input: undefined, description: 'Undefined string' },
{ input: '', description: 'Empty string' },
{ input: 'a', description: 'Single character' },
{ input: 'aaaa', description: 'All same character' },
{ input: ' ', description: 'Only whitespace' },
{ input: 'hello world', description: 'Contains spaces' },
{ input: '你好', description: 'Unicode characters' },
{ input: '👋🌍', description: 'Emoji characters' },
{ input: 'a'.repeat(100000), description: 'Very long string' }
];
}
return [];
}
}
// Example usage
const testArray = [1, 1, 1, 1];
const arrayEdgeCases = EdgeCaseIdentifier.identifyArrayEdgeCases(testArray);
console.log('Array Edge Cases:', arrayEdgeCases);
const testString = '👋 Hello';
const stringEdgeCases = EdgeCaseIdentifier.identifyStringEdgeCases(testString);
console.log('String Edge Cases:', stringEdgeCases);
// Generate comprehensive test suite
const arrayTests = EdgeCaseIdentifier.generateEdgeCaseTests('array');
console.log(`Generated ${arrayTests.length} array edge case tests`);
Warning: Edge cases are where most bugs hide. Always test: null/undefined, empty inputs, single elements, all duplicates, already sorted data, extreme values (MAX/MIN), special values (0, NaN, Infinity), and very large inputs.
3. Boundary Value Testing
| Boundary Type | Test Values | Pattern |
|---|---|---|
| Numeric Range [min, max] | min-1, min, min+1, max-1, max, max+1 | Test below, at, above boundaries |
| Array Size | 0, 1, 2, n-1, n, n+1 | Empty, single, pair, full, overflow |
| String Length | 0, 1, max-1, max, max+1 | Empty, single char, at limit |
| Loop Iterations | 0, 1, n-1, n | No iteration, single, last, complete |
| Index Access | -1, 0, 1, length-2, length-1, length | Below start, start, end, beyond end |
Example 1: Boundary Value Test Generator
class BoundaryValueTester {
// Generate boundary tests for numeric range
static generateNumericBoundaryTests(min, max) {
return {
belowMin: min - 1,
atMin: min,
justAboveMin: min + 1,
midpoint: Math.floor((min + max) / 2),
justBelowMax: max - 1,
atMax: max,
aboveMax: max + 1,
zero: 0, // Special boundary
negativeOne: -1 // Special boundary
};
}
// Test array index boundaries
static testArrayBoundaries(fn, arr) {
const results = [];
const indices = [
-2, -1, // Below bounds
0, 1, // Start
arr.length - 2, arr.length - 1, // End
arr.length, arr.length + 1 // Beyond bounds
];
indices.forEach(index => {
try {
const result = fn(arr, index);
results.push({
index,
result,
passed: true,
boundary: this.classifyIndexBoundary(index, arr.length)
});
} catch (error) {
results.push({
index,
error: error.message,
passed: false,
boundary: this.classifyIndexBoundary(index, arr.length)
});
}
});
return results;
}
static classifyIndexBoundary(index, length) {
if (index < 0) return 'BELOW_START';
if (index === 0) return 'AT_START';
if (index === length - 1) return 'AT_END';
if (index === length) return 'JUST_BEYOND_END';
if (index > length) return 'BEYOND_END';
return 'WITHIN_BOUNDS';
}
// Test loop boundary conditions
static testLoopBoundaries(loopFn, maxIterations) {
const iterationTests = [
0, // No iterations
1, // Single iteration
2, // Two iterations
maxIterations - 1, // One before max
maxIterations, // Exactly max
maxIterations + 1 // One beyond max
];
const results = [];
iterationTests.forEach(n => {
try {
const startTime = performance.now();
const result = loopFn(n);
const duration = performance.now() - startTime;
results.push({
iterations: n,
result,
duration,
passed: true
});
} catch (error) {
results.push({
iterations: n,
error: error.message,
passed: false
});
}
});
return results;
}
// Comprehensive boundary test suite
static runBoundaryTestSuite(functionUnderTest, config) {
const report = {
function: functionUnderTest.name,
timestamp: new Date().toISOString(),
tests: []
};
// Test numeric boundaries
if (config.numericRange) {
const { min, max } = config.numericRange;
const boundaries = this.generateNumericBoundaryTests(min, max);
Object.entries(boundaries).forEach(([label, value]) => {
try {
const result = functionUnderTest(value);
report.tests.push({
type: 'NUMERIC_BOUNDARY',
label,
input: value,
output: result,
passed: true
});
} catch (error) {
report.tests.push({
type: 'NUMERIC_BOUNDARY',
label,
input: value,
error: error.message,
passed: false
});
}
});
}
// Test array size boundaries
if (config.arrayTest) {
const sizes = [0, 1, 2, 10, 100, 1000];
sizes.forEach(size => {
const testArray = Array(size).fill(0).map((_, i) => i);
try {
const result = functionUnderTest(testArray);
report.tests.push({
type: 'ARRAY_SIZE_BOUNDARY',
size,
output: result,
passed: true
});
} catch (error) {
report.tests.push({
type: 'ARRAY_SIZE_BOUNDARY',
size,
error: error.message,
passed: false
});
}
});
}
// Calculate statistics
const passedTests = report.tests.filter(t => t.passed).length;
report.summary = {
total: report.tests.length,
passed: passedTests,
failed: report.tests.length - passedTests,
passRate: (passedTests / report.tests.length * 100).toFixed(2) + '%'
};
return report;
}
}
// Example 1: Testing array access function
function getElement(arr, index) {
if (index < 0 || index >= arr.length) {
throw new Error('Index out of bounds');
}
return arr[index];
}
const arrayBoundaryTests = BoundaryValueTester.testArrayBoundaries(
getElement,
[10, 20, 30, 40, 50]
);
console.log('Array Boundary Tests:', arrayBoundaryTests);
// Example 2: Testing numeric function
function factorial(n) {
if (n < 0) throw new Error('Negative input');
if (n === 0 || n === 1) return 1;
return n * factorial(n - 1);
}
const numericReport = BoundaryValueTester.runBoundaryTestSuite(
factorial,
{ numericRange: { min: 0, max: 10 } }
);
console.log('Factorial Boundary Report:', numericReport.summary);
Note: Boundary value testing catches off-by-one errors and range validation bugs. Always test: minimum boundary (min, min-1, min+1), maximum boundary (max, max-1, max+1), zero/empty, and typical values. This reveals fencepost errors and improper range checking.
4. Performance Profiling and Optimization
| Technique | Measures | Use Case |
|---|---|---|
| Time Complexity Measurement | Execution time vs input size | Verify O(n), O(n log n), O(n²) complexity |
| Space Complexity Profiling | Memory allocation patterns | Detect memory leaks, excessive allocations |
| Benchmarking | Operations per second | Compare algorithm implementations |
| Hotspot Analysis | Time spent in each function | Identify bottlenecks for optimization |
| Asymptotic Analysis | Growth rate comparison | Predict scalability to large inputs |
Example 1: Performance Profiler with Time Complexity Verification
class PerformanceProfiler {
constructor() {
this.results = [];
}
// Measure execution time for various input sizes
profileTimeComplexity(fn, inputGenerator, sizes = [10, 100, 1000, 10000]) {
const measurements = [];
sizes.forEach(size => {
const input = inputGenerator(size);
// Warmup
for (let i = 0; i < 3; i++) {
fn(inputGenerator(size));
}
// Actual measurement (multiple runs for accuracy)
const times = [];
for (let i = 0; i < 10; i++) {
const testInput = inputGenerator(size);
const start = performance.now();
fn(testInput);
const end = performance.now();
times.push(end - start);
}
// Use median to reduce outlier impact
times.sort((a, b) => a - b);
const medianTime = times[Math.floor(times.length / 2)];
measurements.push({
size,
time: medianTime,
avgTime: times.reduce((a, b) => a + b) / times.length
});
});
// Analyze complexity
const complexity = this.inferComplexity(measurements);
return {
measurements,
estimatedComplexity: complexity,
report: this.generateReport(measurements, complexity)
};
}
// Infer time complexity from measurements
inferComplexity(measurements) {
if (measurements.length < 2) return 'UNKNOWN';
// Calculate ratios
const ratios = [];
for (let i = 1; i < measurements.length; i++) {
const sizeRatio = measurements[i].size / measurements[i-1].size;
const timeRatio = measurements[i].time / measurements[i-1].time;
ratios.push({ sizeRatio, timeRatio });
}
const avgTimeRatio = ratios.reduce((sum, r) => sum + r.timeRatio, 0) / ratios.length;
const avgSizeRatio = ratios.reduce((sum, r) => sum + r.sizeRatio, 0) / ratios.length;
// Classify complexity
if (Math.abs(avgTimeRatio - 1) < 0.3) return 'O(1)';
if (Math.abs(avgTimeRatio - avgSizeRatio) < 0.3) return 'O(n)';
if (Math.abs(avgTimeRatio - avgSizeRatio * Math.log2(avgSizeRatio)) < 0.5) return 'O(n log n)';
if (Math.abs(avgTimeRatio - avgSizeRatio * avgSizeRatio) < 1) return 'O(n²)';
if (avgTimeRatio > avgSizeRatio * avgSizeRatio) return 'O(n³) or worse';
return 'COMPLEX';
}
generateReport(measurements, complexity) {
let report = `Performance Profile:\n`;
report += `Estimated Complexity: ${complexity}\n\n`;
report += `Size\t\tTime (ms)\tGrowth\n`;
report += `${'='.repeat(50)}\n`;
measurements.forEach((m, i) => {
const growth = i > 0
? (m.time / measurements[i-1].time).toFixed(2) + 'x'
: '-';
report += `${m.size}\t\t${m.time.toFixed(4)}\t\t${growth}\n`;
});
return report;
}
// Memory profiling
profileMemoryUsage(fn, input) {
if (typeof performance.memory !== 'undefined') {
const before = performance.memory.usedJSHeapSize;
fn(input);
const after = performance.memory.usedJSHeapSize;
return {
allocated: after - before,
total: after,
unit: 'bytes'
};
}
return { error: 'Memory profiling not available' };
}
// Benchmark comparison
benchmark(functions, input, iterations = 1000) {
const results = [];
functions.forEach(({ name, fn }) => {
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
fn([...input]); // Copy to avoid mutation
const end = performance.now();
times.push(end - start);
}
times.sort((a, b) => a - b);
const median = times[Math.floor(times.length / 2)];
const mean = times.reduce((a, b) => a + b) / times.length;
const min = Math.min(...times);
const max = Math.max(...times);
results.push({
name,
median,
mean,
min,
max,
opsPerSec: 1000 / mean
});
});
// Sort by performance
results.sort((a, b) => a.median - b.median);
return results;
}
// Hotspot detection
profileHotspots(fn, input) {
const functionCalls = new Map();
// Wrap function calls to track execution
const originalFn = fn;
const wrapper = (...args) => {
const fnName = originalFn.name || 'anonymous';
const start = performance.now();
const result = originalFn(...args);
const duration = performance.now() - start;
if (!functionCalls.has(fnName)) {
functionCalls.set(fnName, { count: 0, totalTime: 0 });
}
const stats = functionCalls.get(fnName);
stats.count++;
stats.totalTime += duration;
return result;
};
wrapper(input);
return Array.from(functionCalls.entries()).map(([name, stats]) => ({
function: name,
calls: stats.count,
totalTime: stats.totalTime,
avgTime: stats.totalTime / stats.count
})).sort((a, b) => b.totalTime - a.totalTime);
}
}
// Example usage
const profiler = new PerformanceProfiler();
// Test 1: Profile bubble sort time complexity
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
const inputGenerator = size => Array(size).fill(0).map(() => Math.random());
const profile = profiler.profileTimeComplexity(bubbleSort, inputGenerator);
console.log(profile.report);
// Test 2: Benchmark multiple implementations
const quickSort = arr => {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = arr.slice(1).filter(x => x < pivot);
const right = arr.slice(1).filter(x => x >= pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
};
const benchmarkResults = profiler.benchmark([
{ name: 'Bubble Sort', fn: bubbleSort },
{ name: 'Quick Sort', fn: quickSort },
{ name: 'Native Sort', fn: arr => arr.sort((a, b) => a - b) }
], inputGenerator(100));
console.log('Benchmark Results:');
benchmarkResults.forEach((r, i) => {
console.log(`${i + 1}. ${r.name}: ${r.opsPerSec.toFixed(2)} ops/sec`);
});
Warning: Performance measurements can be noisy. Always: run warmup iterations, take multiple measurements, use median instead of mean to reduce outlier impact, test with realistic input sizes, and profile in production-like environment.
5. Algorithm Code Review Checklist
| Category | Check Items | Common Issues |
|---|---|---|
| Correctness | Logic errors, edge cases, algorithm correctness | Off-by-one, null handling, boundary conditions |
| Performance | Time/space complexity, optimization opportunities | Nested loops, redundant operations, inefficient data structures |
| Readability | Naming, comments, code structure | Unclear variable names, missing documentation, complex logic |
| Error Handling | Exception handling, validation, graceful degradation | Missing null checks, unhandled exceptions, silent failures |
| Testing | Test coverage, edge cases tested, assertions | Incomplete tests, missing edge cases, no negative tests |
| Security | Input validation, injection prevention, data exposure | SQL injection, XSS, exposed sensitive data |
Example 1: Automated Code Review Checker
class CodeReviewChecker {
constructor(code) {
this.code = code;
this.issues = [];
}
// Check for common correctness issues
checkCorrectness() {
const correctnessIssues = [];
// Check for potential null pointer issues
const nullChecks = this.code.match(/\.\w+(?!\?\.)/g) || [];
if (nullChecks.length > 0 && !this.code.includes('if (') && !this.code.includes('?.')) {
correctnessIssues.push({
type: 'NULL_SAFETY',
severity: 'HIGH',
message: 'Potential null pointer access without checks',
suggestion: 'Use optional chaining (?.) or add null checks'
});
}
// Check for array index access
const arrayAccess = this.code.match(/\[\w+\]/g) || [];
if (arrayAccess.length > 0 && !this.code.includes('< length') && !this.code.includes('<= length')) {
correctnessIssues.push({
type: 'BOUNDS_CHECK',
severity: 'MEDIUM',
message: 'Array access without bounds checking',
suggestion: 'Validate index: if (i >= 0 && i < arr.length)'
});
}
// Check for off-by-one in loops
if (this.code.includes('i <= length') || this.code.includes('i <= arr.length')) {
correctnessIssues.push({
type: 'OFF_BY_ONE',
severity: 'HIGH',
message: 'Potential off-by-one error with <= length',
suggestion: 'Use i < length instead of i <= length'
});
}
// Check for missing base case in recursion
if (this.code.includes('function') && this.code.match(/\w+\(.*\)/g)) {
const funcName = this.code.match(/function\s+(\w+)/);
if (funcName && this.code.includes(funcName[1] + '(')) {
if (!this.code.includes('return') || !this.code.includes('if')) {
correctnessIssues.push({
type: 'RECURSION_BASE_CASE',
severity: 'HIGH',
message: 'Recursive function may lack base case',
suggestion: 'Add base case: if (condition) return value;'
});
}
}
}
return correctnessIssues;
}
// Check for performance issues
checkPerformance() {
const performanceIssues = [];
// Check for nested loops
const nestedLoops = this.code.match(/for\s*\([^)]*\)\s*{[^}]*for\s*\(/g);
if (nestedLoops && nestedLoops.length > 0) {
performanceIssues.push({
type: 'NESTED_LOOPS',
severity: 'MEDIUM',
message: `Found ${nestedLoops.length} nested loop(s) - O(n²) or worse`,
suggestion: 'Consider using hash map or two-pointer technique'
});
}
// Check for repeated calculations
const calculations = this.code.match(/arr\.length/g);
if (calculations && calculations.length > 3) {
performanceIssues.push({
type: 'REPEATED_CALCULATION',
severity: 'LOW',
message: 'arr.length calculated multiple times',
suggestion: 'Cache in variable: const n = arr.length;'
});
}
// Check for inefficient string concatenation
if (this.code.includes('+=') && this.code.includes('for')) {
const stringConcat = this.code.match(/\w+\s*\+=\s*['"`]/);
if (stringConcat) {
performanceIssues.push({
type: 'STRING_CONCATENATION',
severity: 'MEDIUM',
message: 'String concatenation in loop is O(n²)',
suggestion: 'Use array.push() then join(): arr.push(s); arr.join("")'
});
}
}
// Check for creating new arrays unnecessarily
const arrayCreations = this.code.match(/new Array|Array\(|\.map\(|\.filter\(/g);
if (arrayCreations && arrayCreations.length > 2) {
performanceIssues.push({
type: 'EXCESSIVE_ALLOCATIONS',
severity: 'LOW',
message: 'Multiple array allocations detected',
suggestion: 'Consider in-place operations or single pass'
});
}
return performanceIssues;
}
// Check for readability issues
checkReadability() {
const readabilityIssues = [];
// Check for single letter variables (except i, j, k in loops)
const singleLetterVars = this.code.match(/\b[a-hln-z]\b/g);
if (singleLetterVars && singleLetterVars.length > 3) {
readabilityIssues.push({
type: 'UNCLEAR_NAMING',
severity: 'LOW',
message: 'Too many single-letter variable names',
suggestion: 'Use descriptive names: count, index, result'
});
}
// Check for long functions
const lines = this.code.split('\n').length;
if (lines > 50) {
readabilityIssues.push({
type: 'LONG_FUNCTION',
severity: 'MEDIUM',
message: `Function is ${lines} lines - too long`,
suggestion: 'Break into smaller functions with clear responsibilities'
});
}
// Check for missing comments
const hasComments = this.code.includes('//') || this.code.includes('/*');
const complexity = this.code.split('if').length + this.code.split('for').length;
if (complexity > 3 && !hasComments) {
readabilityIssues.push({
type: 'MISSING_COMMENTS',
severity: 'LOW',
message: 'Complex logic without comments',
suggestion: 'Add comments explaining algorithm and edge cases'
});
}
return readabilityIssues;
}
// Check error handling
checkErrorHandling() {
const errorIssues = [];
// Check for missing input validation
if (!this.code.includes('if (') && !this.code.includes('throw')) {
errorIssues.push({
type: 'NO_VALIDATION',
severity: 'HIGH',
message: 'No input validation detected',
suggestion: 'Validate inputs: check null, bounds, types'
});
}
// Check for try-catch in async code
if ((this.code.includes('async') || this.code.includes('.then(')) && !this.code.includes('catch')) {
errorIssues.push({
type: 'UNHANDLED_ASYNC',
severity: 'HIGH',
message: 'Async code without error handling',
suggestion: 'Add try-catch or .catch() handler'
});
}
return errorIssues;
}
// Generate comprehensive review report
generateReport() {
const allIssues = [
...this.checkCorrectness(),
...this.checkPerformance(),
...this.checkReadability(),
...this.checkErrorHandling()
];
// Group by severity
const bySeverity = {
HIGH: allIssues.filter(i => i.severity === 'HIGH'),
MEDIUM: allIssues.filter(i => i.severity === 'MEDIUM'),
LOW: allIssues.filter(i => i.severity === 'LOW')
};
return {
totalIssues: allIssues.length,
bySeverity,
issues: allIssues,
score: this.calculateScore(allIssues)
};
}
calculateScore(issues) {
const weights = { HIGH: 10, MEDIUM: 5, LOW: 1 };
const totalPenalty = issues.reduce((sum, issue) =>
sum + weights[issue.severity], 0
);
return Math.max(0, 100 - totalPenalty);
}
}
// Example usage
const codeToReview = `
function findMax(arr) {
let max = arr[0];
for (let i = 1; i <= arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
`;
const reviewer = new CodeReviewChecker(codeToReview);
const report = reviewer.generateReport();
console.log('Code Review Report:');
console.log(`Score: ${report.score}/100`);
console.log(`Total Issues: ${report.totalIssues}`);
console.log('\nHigh Severity Issues:');
report.bySeverity.HIGH.forEach(issue => {
console.log(` - ${issue.message}`);
console.log(` ${issue.suggestion}`);
});
Note: Effective code reviews check: correctness (logic, edge cases, algorithms), performance (time/space complexity, bottlenecks), readability (naming, structure, comments), error handling (validation, exceptions), testing (coverage, edge cases), and security (input validation, injection prevention).
6. Stress Testing with Large Inputs
| Stress Test Type | Purpose | Input Characteristics |
|---|---|---|
| Maximum Size Test | Verify behavior at maximum input size | n = 10^5, 10^6, or problem limit |
| Random Large Input | Find bugs that appear only with large data | Random values at maximum size |
| Worst Case Input | Test worst-case time/space complexity | Already sorted, all duplicates, nested structure |
| Memory Stress Test | Detect memory leaks and excessive allocation | Repeated operations on large data |
| Time Limit Test | Ensure solution runs within time constraints | Maximum input with time measurement |
Example 1: Stress Test Framework
class StressTester {
constructor(solutionFn, referenceFn = null) {
this.solutionFn = solutionFn;
this.referenceFn = referenceFn; // Naive but correct solution
this.results = [];
}
// Generate maximum size input
generateMaxSizeInput(size, type = 'random') {
if (type === 'random') {
return Array(size).fill(0).map(() =>
Math.floor(Math.random() * 1000000)
);
} else if (type === 'sorted') {
return Array(size).fill(0).map((_, i) => i);
} else if (type === 'reverse') {
return Array(size).fill(0).map((_, i) => size - i);
} else if (type === 'duplicates') {
return Array(size).fill(42);
} else if (type === 'alternating') {
return Array(size).fill(0).map((_, i) => i % 2);
}
return [];
}
// Run stress test
runStressTest(inputSize, iterations = 100, timeLimit = 1000) {
console.log(`Running stress test: size=${inputSize}, iterations=${iterations}`);
const testResults = {
size: inputSize,
iterations,
passed: 0,
failed: 0,
timeouts: 0,
errors: [],
avgTime: 0,
maxTime: 0
};
const times = [];
for (let i = 0; i < iterations; i++) {
// Generate random test case
const input = this.generateMaxSizeInput(inputSize,
['random', 'sorted', 'reverse', 'duplicates'][i % 4]
);
try {
const start = performance.now();
const result = this.solutionFn([...input]);
const duration = performance.now() - start;
times.push(duration);
if (duration > timeLimit) {
testResults.timeouts++;
} else {
// Verify against reference solution if available
if (this.referenceFn) {
const expected = this.referenceFn([...input]);
const isCorrect = JSON.stringify(result) === JSON.stringify(expected);
if (isCorrect) {
testResults.passed++;
} else {
testResults.failed++;
testResults.errors.push({
iteration: i,
input: input.slice(0, 10), // Store first 10 elements
expected: expected,
got: result,
message: 'Output mismatch'
});
}
} else {
testResults.passed++;
}
}
} catch (error) {
testResults.failed++;
testResults.errors.push({
iteration: i,
message: error.message,
stack: error.stack
});
}
}
testResults.avgTime = times.reduce((a, b) => a + b, 0) / times.length;
testResults.maxTime = Math.max(...times);
this.results.push(testResults);
return testResults;
}
// Memory stress test
runMemoryStressTest(inputSize, operations = 1000) {
console.log(`Running memory stress test: ${operations} operations`);
const memorySnapshots = [];
for (let i = 0; i < operations; i++) {
const input = this.generateMaxSizeInput(inputSize);
if (typeof performance.memory !== 'undefined') {
const before = performance.memory.usedJSHeapSize;
this.solutionFn(input);
const after = performance.memory.usedJSHeapSize;
memorySnapshots.push({
iteration: i,
allocated: after - before,
total: after
});
} else {
this.solutionFn(input);
}
// Periodic garbage collection hint
if (i % 100 === 0 && global.gc) {
global.gc();
}
}
return {
operations,
memoryAvailable: memorySnapshots.length > 0,
avgAllocation: memorySnapshots.length > 0
? memorySnapshots.reduce((s, m) => s + m.allocated, 0) / memorySnapshots.length
: 'N/A',
peakMemory: memorySnapshots.length > 0
? Math.max(...memorySnapshots.map(m => m.total))
: 'N/A'
};
}
// Comparative stress test
compareWithReference(sizes = [100, 1000, 10000]) {
if (!this.referenceFn) {
return { error: 'No reference function provided' };
}
const comparison = [];
sizes.forEach(size => {
const input = this.generateMaxSizeInput(size);
// Test solution
const solStart = performance.now();
const solResult = this.solutionFn([...input]);
const solTime = performance.now() - solStart;
// Test reference
const refStart = performance.now();
const refResult = this.referenceFn([...input]);
const refTime = performance.now() - refStart;
const isCorrect = JSON.stringify(solResult) === JSON.stringify(refResult);
comparison.push({
size,
correct: isCorrect,
solutionTime: solTime,
referenceTime: refTime,
speedup: refTime / solTime
});
});
return comparison;
}
// Generate report
generateReport() {
console.log('\n=== Stress Test Report ===');
this.results.forEach(result => {
console.log(`\nInput Size: ${result.size}`);
console.log(`Iterations: ${result.iterations}`);
console.log(`Passed: ${result.passed} (${(result.passed/result.iterations*100).toFixed(1)}%)`);
console.log(`Failed: ${result.failed}`);
console.log(`Timeouts: ${result.timeouts}`);
console.log(`Avg Time: ${result.avgTime.toFixed(2)}ms`);
console.log(`Max Time: ${result.maxTime.toFixed(2)}ms`);
if (result.errors.length > 0) {
console.log(`\nFirst Error:`);
console.log(result.errors[0]);
}
});
}
}
// Example usage
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...quickSort(left), ...middle, ...quickSort(right)];
}
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
// Stress test quick sort
const tester = new StressTester(quickSort, bubbleSort);
tester.runStressTest(1000, 50);
tester.runStressTest(10000, 10);
// Compare performance
const comparison = tester.compareWithReference([100, 500, 1000]);
console.log('\nPerformance Comparison:');
comparison.forEach(c => {
console.log(`Size ${c.size}: ${c.speedup.toFixed(2)}x faster, correct: ${c.correct}`);
});
tester.generateReport();
Warning: Stress testing reveals bugs that appear only with large inputs: stack overflow from deep recursion, timeout from poor complexity, memory exhaustion from leaks, integer overflow, and performance degradation. Always test at maximum problem constraints.
7. Corner Case Enumeration Methods
| Corner Case Type | Definition | Examples |
|---|---|---|
| Minimum Input | Smallest valid input | Empty array [], single element [1], n=0 |
| Maximum Input | Largest valid input | n = 10^5, max value array |
| All Same Elements | No variation in input | [5,5,5,5], "aaaa" |
| Extreme Values | Boundary of data type | MAX_INT, MIN_INT, Infinity, -Infinity |
| Special Patterns | Input with known structure | Sorted, reverse sorted, alternating |
| Invalid Input | Outside problem constraints | Negative when positive expected, null |
Example: Corner Case Generator and Validator
class CornerCaseEnumerator {
// Generate all corner cases for array problems
static enumerateArrayCornerCases(maxSize = 5, valueRange = [-10, 10]) {
const cornerCases = [];
// 1. Empty array
cornerCases.push({
name: 'Empty Array',
input: [],
category: 'MINIMUM_INPUT'
});
// 2. Single element
cornerCases.push({
name: 'Single Element',
input: [0],
category: 'MINIMUM_INPUT'
});
// 3. Two elements - same
cornerCases.push({
name: 'Two Same Elements',
input: [5, 5],
category: 'DUPLICATES'
});
// 4. Two elements - different
cornerCases.push({
name: 'Two Different Elements',
input: [1, 2],
category: 'MINIMUM_VARIED'
});
// 5. All same elements
cornerCases.push({
name: 'All Same Elements',
input: Array(maxSize).fill(7),
category: 'NO_VARIATION'
});
// 6. Already sorted ascending
cornerCases.push({
name: 'Already Sorted Ascending',
input: Array(maxSize).fill(0).map((_, i) => i),
category: 'SORTED'
});
// 7. Already sorted descending
cornerCases.push({
name: 'Already Sorted Descending',
input: Array(maxSize).fill(0).map((_, i) => maxSize - i),
category: 'REVERSE_SORTED'
});
// 8. Alternating values
cornerCases.push({
name: 'Alternating Pattern',
input: Array(maxSize).fill(0).map((_, i) => i % 2),
category: 'PATTERN'
});
// 9. All zeros
cornerCases.push({
name: 'All Zeros',
input: Array(maxSize).fill(0),
category: 'SPECIAL_VALUE'
});
// 10. Contains negative numbers
cornerCases.push({
name: 'Mix of Positive and Negative',
input: [-5, -1, 0, 3, 7],
category: 'MIXED_SIGNS'
});
// 11. Extreme values
const [min, max] = valueRange;
cornerCases.push({
name: 'Extreme Values',
input: [min, max, min, max],
category: 'EXTREME_VALUES'
});
// 12. Maximum size
cornerCases.push({
name: 'Maximum Size Random',
input: Array(maxSize).fill(0).map(() =>
Math.floor(Math.random() * (max - min + 1)) + min
),
category: 'MAXIMUM_INPUT'
});
return cornerCases;
}
// Generate corner cases for string problems
static enumerateStringCornerCases(maxLength = 10) {
return [
{ name: 'Empty String', input: '', category: 'MINIMUM_INPUT' },
{ name: 'Single Character', input: 'a', category: 'MINIMUM_INPUT' },
{ name: 'Two Same Chars', input: 'aa', category: 'DUPLICATES' },
{ name: 'Two Different Chars', input: 'ab', category: 'MINIMUM_VARIED' },
{ name: 'All Same Character', input: 'a'.repeat(maxLength), category: 'NO_VARIATION' },
{ name: 'Palindrome', input: 'racecar', category: 'PATTERN' },
{ name: 'Already Sorted', input: 'abcdefgh', category: 'SORTED' },
{ name: 'Reverse Sorted', input: 'zyxwvu', category: 'REVERSE_SORTED' },
{ name: 'Contains Spaces', input: 'hello world', category: 'SPECIAL_CHARS' },
{ name: 'Only Spaces', input: ' ', category: 'SPECIAL_CHARS' },
{ name: 'Mixed Case', input: 'HeLLo', category: 'MIXED' },
{ name: 'Numbers and Letters', input: 'abc123', category: 'MIXED' },
{ name: 'Special Characters', input: '!@#$%', category: 'SPECIAL_CHARS' },
{ name: 'Unicode', input: '你好世界', category: 'UNICODE' },
{ name: 'Emoji', input: '🚀🌟💻', category: 'UNICODE' },
{ name: 'Maximum Length', input: 'x'.repeat(maxLength), category: 'MAXIMUM_INPUT' }
];
}
// Test function against all corner cases
static validateAgainstCornerCases(fn, cornerCases, expectedBehavior = null) {
const results = {
total: cornerCases.length,
passed: 0,
failed: 0,
errors: 0,
details: []
};
cornerCases.forEach(testCase => {
try {
const result = fn(testCase.input);
let passed = true;
let message = 'Executed successfully';
// Check expected behavior if provided
if (expectedBehavior && expectedBehavior[testCase.name]) {
const expected = expectedBehavior[testCase.name];
passed = JSON.stringify(result) === JSON.stringify(expected);
message = passed ? 'Matches expected output' : 'Output mismatch';
}
if (passed) {
results.passed++;
} else {
results.failed++;
}
results.details.push({
name: testCase.name,
category: testCase.category,
input: testCase.input,
output: result,
passed,
message
});
} catch (error) {
results.errors++;
results.details.push({
name: testCase.name,
category: testCase.category,
input: testCase.input,
error: error.message,
passed: false,
message: 'Exception thrown'
});
}
});
return results;
}
// Generate test matrix (combinations of corner cases)
static generateTestMatrix(dimensions) {
// For functions with multiple parameters
const matrix = [];
function cartesianProduct(arrays, index = 0, current = []) {
if (index === arrays.length) {
matrix.push([...current]);
return;
}
arrays[index].forEach(value => {
current.push(value);
cartesianProduct(arrays, index + 1, current);
current.pop();
});
}
cartesianProduct(dimensions);
return matrix;
}
}
// Example usage
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Generate array corner cases
const arrayCornerCases = CornerCaseEnumerator.enumerateArrayCornerCases(10, [0, 100]);
console.log(`Generated ${arrayCornerCases.length} array corner cases:`);
arrayCornerCases.forEach(tc => {
console.log(` ${tc.name} (${tc.category}): [${tc.input.slice(0, 5)}...]`);
});
// Test binary search with corner cases
const searchCornerCases = arrayCornerCases.map(tc => ({
...tc,
input: tc.input.sort((a, b) => a - b) // Binary search needs sorted array
}));
const results = CornerCaseEnumerator.validateAgainstCornerCases(
arr => binarySearch(arr, arr.length > 0 ? arr[0] : 0),
searchCornerCases
);
console.log(`\nTest Results: ${results.passed}/${results.total} passed`);
console.log(`Failed: ${results.failed}, Errors: ${results.errors}`);
Note: Systematic corner case enumeration prevents missing critical tests. For each problem, enumerate: minimum input (empty, single element), maximum input (at constraints), special values (0, null, duplicates), patterns (sorted, reverse), and extreme values (MAX_INT, MIN_INT). Create test matrix for multi-parameter functions.
Summary: Testing and Debugging Patterns
- Equivalence Partitioning: Divide input domain into classes where behavior is identical - test one representative from each partition
- Boundary Testing: Test at boundaries: min-1, min, min+1, max-1, max, max+1 to catch off-by-one and range errors
- Property-based Testing: Define invariants (idempotence, commutativity, preservation) and verify they hold for random inputs
- Edge Case Categories: Always test empty/null, single element, duplicates, sorted/reverse, extreme values, and special characters
- Edge Case Identification: Use automated tools to detect null safety issues, bounds problems, special values, and large inputs
- Boundary Value Analysis: Systematically test numeric ranges, array sizes, string lengths, loop iterations, and index access at boundaries
- Time Complexity Profiling: Measure execution time vs input size, calculate growth ratios to infer O(1), O(n), O(n log n), O(n²)
- Benchmarking: Compare multiple implementations with warmup runs, multiple measurements, and median statistics to reduce noise
- Code Review Checklist: Check correctness (logic, edge cases), performance (complexity), readability (naming, structure), error handling, testing, security
- Automated Code Review: Detect common issues: null pointer access, bounds checking, off-by-one, nested loops, string concatenation in loops
- Stress Testing: Test at maximum constraints with random, sorted, reverse, and duplicate inputs to find scalability issues
- Memory Stress Testing: Run repeated operations to detect memory leaks and excessive allocations - monitor heap size growth
- Corner Case Enumeration: Systematically generate: empty, single, all-same, sorted, reverse, extreme values, special patterns
- Test Matrix: For multi-parameter functions, create cartesian product of corner cases for each parameter to test all combinations
- Validation Framework: Build automated test harness that runs all corner cases and reports pass/fail/error statistics