Pattern validation in automated testing
The Core Role of Pattern Validation in Automated Testing
Pattern validation is a crucial means in automated testing to ensure code behavior aligns with expectations. By defining specific patterns and verifying whether the output matches them, it can effectively detect functional anomalies, data format errors, and logical vulnerabilities. JavaScript design patterns provide structured solutions for pattern validation. Combined with regular expressions, assertion libraries, and custom validation logic, they enable the construction of flexible and maintainable testing systems.
Strategy Pattern for Dynamic Validation Rules
The Strategy pattern allows switching between different validation algorithms at runtime. For example, when handling user inputs in various formats, multiple validation strategies can be defined:
const validationStrategies = {
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
phone: (value) => /^1[3-9]\d{9}$/.test(value),
password: (value) => value.length >= 8 && /\d/.test(value) && /[A-Z]/.test(value)
};
class Validator {
constructor(strategy) {
this.strategy = strategy;
}
validate(value) {
return this.strategy(value);
}
}
// Usage example
const emailValidator = new Validator(validationStrategies.email);
console.log(emailValidator.validate('test@example.com')); // true
This pattern is particularly suitable for testing scenarios requiring support for multiple validation rules, such as API response field validation and form input validation.
Observer Pattern for Real-Time Validation Systems
The Observer pattern enables automatic validation upon value changes, which is especially useful in UI automated testing:
class ObservableValue {
constructor(initialValue) {
this.value = initialValue;
this.observers = [];
}
setValue(newValue) {
this.value = newValue;
this.notifyObservers();
}
addObserver(observer) {
this.observers.push(observer);
}
notifyObservers() {
this.observers.forEach(observer => observer(this.value));
}
}
// Create a validation observer
const createValidationObserver = (validator) => {
return (value) => {
const isValid = validator(value);
console.log(`Value ${value} is ${isValid ? 'valid' : 'invalid'}`);
return isValid;
};
};
// Usage example
const username = new ObservableValue('');
const validateUsername = createValidationObserver(
value => value.length >= 5 && !/\d/.test(value)
);
username.addObserver(validateUsername);
username.setValue('admin'); // Triggers validation
Decorator Pattern for Enhanced Validation Functionality
The Decorator pattern allows adding new features without modifying existing validation logic:
function required(validator) {
return function(value) {
if (!value) return false;
return validator(value);
};
}
function maxLength(length, validator) {
return function(value) {
if (value.length > length) return false;
return validator(value);
};
}
// Basic validation function
const validateUsername = (username) => /^[a-zA-Z]+$/.test(username);
// Decorated validation function
const decoratedValidator = required(maxLength(20, validateUsername));
// Test cases
console.log(decoratedValidator('')); // false (required)
console.log(decoratedValidator('a'.repeat(21))); // false (maxLength)
console.log(decoratedValidator('validName')); // true
Factory Pattern for Unified Validator Creation
The Factory pattern abstracts the creation process of validation objects:
class ValidatorFactory {
static create(type, options = {}) {
switch(type) {
case 'string':
return new StringValidator(options);
case 'number':
return new NumberValidator(options);
case 'array':
return new ArrayValidator(options);
default:
throw new Error(`Unknown validator type: ${type}`);
}
}
}
class StringValidator {
constructor({ minLength = 0, maxLength = Infinity, pattern = null }) {
this.rules = { minLength, maxLength, pattern };
}
validate(value) {
if (typeof value !== 'string') return false;
if (value.length < this.rules.minLength) return false;
if (value.length > this.rules.maxLength) return false;
if (this.rules.pattern && !this.rules.pattern.test(value)) return false;
return true;
}
}
// Usage example
const usernameValidator = ValidatorFactory.create('string', {
minLength: 5,
maxLength: 20,
pattern: /^[a-z0-9_]+$/i
});
console.log(usernameValidator.validate('good_user')); // true
Composite Pattern for Complex Validation Rules
The Composite pattern combines multiple simple validators into complex validation logic:
class CompositeValidator {
constructor() {
this.validators = [];
}
add(validator) {
this.validators.push(validator);
}
validate(value) {
return this.validators.every(validator => validator.validate(value));
}
}
// Usage example
const passwordValidator = new CompositeValidator();
passwordValidator.add(new ValidatorFactory.create('string', { minLength: 8 }));
passwordValidator.add(new ValidatorFactory.create('string', { pattern: /\d/ }));
passwordValidator.add(new ValidatorFactory.create('string', { pattern: /[A-Z]/ }));
console.log(passwordValidator.validate('Weak1')); // false
console.log(passwordValidator.validate('Strong123')); // true
Template Method Pattern for Defining Validation Workflows
The Template Method pattern fixes the steps of the validation process:
class AbstractValidator {
validate(value) {
this.beforeValidation();
const result = this.doValidation(value);
this.afterValidation();
return result;
}
beforeValidation() {
console.log('Starting validation...');
}
// Abstract method to be implemented by subclasses
doValidation(value) {
throw new Error('You must implement doValidation method');
}
afterValidation() {
console.log('Validation completed');
}
}
class EmailValidator extends AbstractValidator {
doValidation(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
}
// Usage example
const emailValidator = new EmailValidator();
console.log(emailValidator.validate('test@example.com'));
Application Example in Real Testing Scenarios
Validating API response data structures in API testing:
const validateApiResponse = (response) => {
const schema = {
id: value => Number.isInteger(value) && value > 0,
username: value => typeof value === 'string' && value.length >= 3,
email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
createdAt: value => !isNaN(Date.parse(value)),
profile: {
age: value => typeof value === 'number' && value >= 18,
address: value => typeof value === 'string'
}
};
const validateObject = (obj, schema) => {
return Object.entries(schema).every(([key, validator]) => {
if (typeof validator === 'object') {
return validateObject(obj[key], validator);
}
return validator(obj[key]);
});
};
return validateObject(response, schema);
};
// Test data
const mockResponse = {
id: 123,
username: 'testuser',
email: 'user@example.com',
createdAt: '2023-01-01T00:00:00Z',
profile: {
age: 25,
address: '123 Main St'
}
};
console.log(validateApiResponse(mockResponse)); // true
Performance Optimization and Caching Strategies
Frequently executed validation logic can be optimized through memoization:
function memoize(validator) {
const cache = new Map();
return function(value) {
if (cache.has(value)) {
console.log('Returning cached result');
return cache.get(value);
}
const result = validator(value);
cache.set(value, result);
return result;
};
}
// Complex validation function
const complexValidation = (value) => {
console.log('Performing complex validation...');
// Simulate time-consuming operation
for (let i = 0; i < 1000000; i++) {}
return /^[a-zA-Z0-9]+$/.test(value);
};
const memoizedValidator = memoize(complexValidation);
// First call performs actual validation
console.log(memoizedValidator('test123'));
// Second call with same value returns cached result
console.log(memoizedValidator('test123'));
Asynchronous Validation Patterns
Modern frontend applications often require handling asynchronous validation scenarios:
class AsyncValidator {
constructor(validators) {
this.validators = validators;
}
async validate(value) {
const results = await Promise.all(
this.validators.map(validator => validator(value))
);
return results.every(Boolean);
}
}
// Mock async validation function
const checkUsernameAvailability = async (username) => {
// Simulate API call
return new Promise(resolve => {
setTimeout(() => {
resolve(!['admin', 'root'].includes(username));
}, 500);
});
};
const validateUsernameFormat = async (username) => {
return /^[a-z0-9_]{3,20}$/i.test(username);
};
// Create composite validator
const usernameValidator = new AsyncValidator([
checkUsernameAvailability,
validateUsernameFormat
]);
// Usage example
(async () => {
console.log(await usernameValidator.validate('newuser')); // true
console.log(await usernameValidator.validate('admin')); // false
})();
Detailed Error Handling in Validation
A robust validation system requires providing detailed error information:
class DetailedValidator {
constructor(rules) {
this.rules = rules;
}
validate(value) {
const errors = [];
this.rules.forEach(rule => {
if (!rule.validator(value)) {
errors.push({
field: rule.field,
message: rule.message,
value
});
}
});
return {
isValid: errors.length === 0,
errors
};
}
}
// Usage example
const userValidator = new DetailedValidator([
{
field: 'username',
validator: value => value.length >= 5,
message: 'Username must be at least 5 characters'
},
{
field: 'username',
validator: value => /^[a-z0-9]+$/i.test(value),
message: 'Username can only contain letters and numbers'
},
{
field: 'age',
validator: value => value >= 18,
message: 'User must be at least 18 years old'
}
]);
const result = userValidator.validate({
username: 'a$b',
age: 16
});
console.log(result);
/* Output:
{
isValid: false,
errors: [
{
field: 'username',
message: 'Username must be at least 5 characters',
value: 'a$b'
},
{
field: 'username',
message: 'Username can only contain letters and numbers',
value: 'a$b'
},
{
field: 'age',
message: 'User must be at least 18 years old',
value: 16
}
]
}
*/
Property-Based Testing Patterns
Property-based testing verifies code properties by generating random inputs:
const fc = require('fast-check');
// Function under test
function reverseString(str) {
return str.split('').reverse().join('');
}
// Property tests
describe('reverseString', () => {
it('should maintain length', () => {
fc.assert(
fc.property(fc.string(), (str) => {
return reverseString(str).length === str.length;
})
);
});
it('should be idempotent', () => {
fc.assert(
fc.property(fc.string(), (str) => {
return reverseString(reverseString(str)) === str;
})
);
});
it('should preserve character set', () => {
fc.assert(
fc.property(fc.string(), (str) => {
const originalChars = new Set(str.split(''));
const reversedChars = new Set(reverseString(str).split(''));
return (
originalChars.size === reversedChars.size &&
[...originalChars].every(c => reversedChars.has(c))
);
})
);
});
});
Visual Validation Patterns
Combining visual validation in end-to-end testing:
const { chromium } = require('playwright');
const pixelmatch = require('pixelmatch');
const PNG = require('pngjs').PNG;
const fs = require('fs');
async function visualRegressionTest() {
const browser = await chromium.launch();
const page = await browser.newPage();
// Capture baseline screenshot
await page.goto('https://example.com');
const baseline = await page.screenshot({ path: 'baseline.png' });
// Modified page screenshot
await page.evaluate(() => {
document.querySelector('h1').style.color = 'red';
});
const current = await page.screenshot({ path: 'current.png' });
// Compare images
const img1 = PNG.sync.read(fs.readFileSync('baseline.png'));
const img2 = PNG.sync.read(fs.readFileSync('current.png'));
const { width, height } = img1;
const diff = new PNG({ width, height });
const numDiffPixels = pixelmatch(
img1.data, img2.data, diff.data, width, height,
{ threshold: 0.1 }
);
fs.writeFileSync('diff.png', PNG.sync.write(diff));
console.log(`Number of differing pixels: ${numDiffPixels}`);
await browser.close();
return numDiffPixels < 100; // Allow minor pixel differences
}
visualRegressionTest().then(result => {
console.log('Visual test result:', result ? 'Passed' : 'Failed');
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:设计模式与代码覆盖率
下一篇:持续集成中的设计模式保障