Software Testing
Strategies, types, and tools for quality assurance
IT & Tech Reports | Class IM24A | 2026
1. Why Test?
Software testing verifies that a system behaves as expected, catches regressions, and builds
confidence for deployment. The cost of fixing a bug increases exponentially the later it is found: a bug
found in production can cost 100x more to fix than one caught during development.
2. The Testing Pyramid
The testing pyramid describes the ideal proportion of test types — more unit tests at the base, fewer
expensive E2E tests at the top:
• Unit Tests (base) — test individual functions/classes in isolation; fast, cheap, many
• Integration Tests (middle) — test how components interact (service + DB, API + auth)
• End-to-End Tests (top) — simulate a real user through the full stack; slow, fragile, few
3. Unit Testing with pytest (Python)
import pytest
from [Link] import add, divide
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
def test_divide_by_zero():
with [Link](ZeroDivisionError):
divide(10, 0)
@[Link]("a,b,expected", [(1,2,3),(0,0,0),(-1,-1,-2)])
def test_add_parametrised(a, b, expected):
assert add(a, b) == expected
4. Test Doubles
Type Description When to use
Dummy Placeholder passed but never used Filling required parameters
Type Description When to use
Stub Returns predefined responses Controlling indirect inputs
Mock Verifies specific calls were made Testing interactions/side effects
Spy Records calls to a real object Partial verification
Fake Simplified working implementation In-memory DB, fake email service
from [Link] import Mock, patch
def test_send_email():
email_service = Mock()
user_service = UserService(email_service)
user_service.register("ivan@[Link]")
email_service.send_welcome.assert_called_once_with("ivan@[Link]")
5. Test Coverage
Code coverage measures what percentage of your code is executed during tests. 80% is a common
target. Use pytest --cov=myapp to generate a report.
Coverage Type What it measures
Line coverage Which lines of code were executed
Branch coverage Which if/else branches were taken (more thorough)
Function coverage Which functions/methods were called
Mutation testing Whether tests detect artificial bugs (highest confidence)
6. Testing in JavaScript with Vitest / Jest
import { describe, it, expect, vi } from "vitest";
import { formatDate } from "./utils";
describe("formatDate", () => {
it("formats a date as [Link]", () => {
expect(formatDate(new Date("2026-03-15"))).toBe("15.03.2026");
});
it("throws on invalid input", () => {
expect(() => formatDate(null)).toThrow("Invalid date");
});
});
7. CI/CD Integration
Tests should run automatically on every push via a CI pipeline (GitHub Actions, GitLab CI):
# .github/workflows/[Link]
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -e ".[dev]"
- run: pytest --cov=myapp --cov-report=xml
8. Testing Best Practices
• Write tests before or alongside the code (TDD: Red-Green-Refactor)
• Each test should verify exactly one behaviour — keep them small and focused
• Use descriptive names: test_user_cannot_login_with_wrong_password
• Tests must be deterministic — no random data, no time-dependent logic without mocking
• Never test implementation details — test behaviour and public interfaces only
• Keep tests fast: mock I/O, databases, and external services in unit tests