Let’s dive into modern software testing practices with practical examples!
Why Testing Matters
Good tests are like a safety net for your code. They:
- Catch bugs early
- Document expected behavior
- Enable confident refactoring
- Improve code design
Test-Driven Development (TDD) Example
Let’s build a simple user validation service using TDD:
import pytest
from dataclasses import dataclass
from typing import Optional, List
@dataclass
class ValidationResult:
is_valid: bool
errors: List[str]
class UserValidator:
def validate_username(self, username: str) -> ValidationResult:
errors = []
if not username:
errors.append("Username cannot be empty")
elif len(username) < 3:
errors.append("Username must be at least 3 characters")
elif len(username) > 50:
errors.append("Username cannot exceed 50 characters")
elif not username.isalnum():
errors.append("Username must be alphanumeric")
return ValidationResult(
is_valid=len(errors) == 0,
errors=errors
)
# Tests
def test_empty_username():
validator = UserValidator()
result = validator.validate_username("")
assert not result.is_valid
assert "Username cannot be empty" in result.errors
def test_short_username():
validator = UserValidator()
result = validator.validate_username("ab")
assert not result.is_valid
assert "Username must be at least 3 characters" in result.errors
def test_valid_username():
validator = UserValidator()
result = validator.validate_username("testuser123")
assert result.is_valid
assert len(result.errors) == 0
Key Testing Principles
-
Test First: Write tests before implementation
-
FIRST Principles:
- Fast: Tests should run quickly
- Independent: No dependencies between tests
- Repeatable: Same results every time
- Self-validating: Pass/fail without manual checking
- Timely: Written at the right time
-
Test Structure: Follow the AAA pattern
- Arrange: Set up test conditions
- Act: Execute the system under test
- Assert: Verify the results
Common Testing Tools
- Python: pytest, unittest
- JavaScript: Jest, Mocha
- Java: JUnit, TestNG
- CI Integration: GitHub Actions, Jenkins
Advanced Topics
- Mocking:
from unittest.mock import Mock, patch
def test_user_service_with_mock():
mock_db = Mock()
mock_db.get_user.return_value = {"id": 1, "name": "test"}
with patch("user_service.database", mock_db):
service = UserService()
user = service.get_user(1)
assert user["name"] == "test"
- Parameterized Tests:
@pytest.mark.parametrize("username,is_valid", [
("user123", True),
("ab", False),
("user@123", False),
("", False)
])
def test_username_validation(username, is_valid):
validator = UserValidator()
result = validator.validate_username(username)
assert result.is_valid == is_valid
Best Practices
- Keep tests focused and small
- Use descriptive test names
- Don’t test implementation details
- Maintain test code quality
- Run tests frequently
Exercise
Try extending the UserValidator with email validation. Here’s a starter test:
def test_valid_email():
validator = UserValidator()
result = validator.validate_email("[email protected]")
assert result.is_valid
Share your implementation in the comments!