Overview
Software testing is not one thing — it's a family of techniques, each targeting a different risk and quality dimension. Knowing which test type to apply and when is a core skill that separates a good tester from a great one.
Integration • Regression
UAT • Feature • Component
Security • Usability
Scalability • Volume
Alpha • Beta • Gamma
Mutation • Accessibility
Functional Testing
Functional testing verifies that the software does what it is supposed to do. It is a black-box approach — testers interact with the system through the UI or API without knowledge of the internal code. Every functional test answers the question: "Does this feature work as specified?"
Unit Testing
The smallest type of test. A unit test verifies a single function, method, or class in isolation — without involving a database, UI, or network.
- Who does it: Developers (usually automated)
- When: During development, before code is merged
- Example: Testing a
calculateTax(price, rate)function returns the correct value for known inputs - Tools: JUnit (Java), pytest (Python), Jest (JavaScript)
Smoke Testing
A quick "is the build even testable?" check. Smoke testing runs the most critical paths of the application immediately after a new build is received from the development team.
- Who does it: QA engineers
- When: Immediately after receiving a new build — before full test execution begins
- Outcome: If smoke tests fail, the build is rejected and returned to development. No further testing happens.
- Example: Can the app launch? Can a user log in? Can the home screen load?
Sanity Testing
A focused, narrow check after a specific bug is fixed or a small feature is added. It confirms that the fix works and hasn't broken anything immediately adjacent to it.
- Who does it: QA engineers
- When: After receiving a bug fix build
- Scope: Narrow — only the area affected by the fix
- Example: Developer fixed a login error. Sanity test: log in with valid credentials, log in with invalid credentials, check error messages.
Integration Testing
Tests that individually working modules communicate correctly when combined. The focus is on data flow between modules — what one module sends, does the next module receive and process correctly?
Module
Module
Module
Module
Each arrow is an integration point — a potential failure zone where data can be lost, corrupted, or misunderstood
- Approaches: Big bang, Top-down (uses stubs), Bottom-up (uses drivers)
- Example: When a user places an order, does the inventory module correctly decrement stock?
Regression Testing
After any code change — bug fix, new feature, or refactor — regression testing confirms that existing functionality hasn't been broken. It re-runs previously passing test cases against the updated build.
- When: After every build / every code change
- Why it's expensive: The regression suite grows with the product, so this is the primary candidate for automation
- Example: A payment gateway change is made. Regression tests run all login, profile, checkout, and order history flows to confirm nothing broke.
User Acceptance Testing (UAT)
The final gate before a product goes live. UAT is performed by the end users or client — not the testing team — to verify the software meets their real-world needs.
- Who does it: Business users, clients, product owners
- Environment: Production-like (staging)
- Based on: Business requirements and user stories, not technical specs
- Types: Alpha testing (internal users), Beta testing (real external users, pre-release)
Non-Functional Testing
Non-functional testing covers the qualities of a system beyond features — how well it works, not just what it does. These tests validate reliability, speed, security, and user experience.
Performance & Load & Stress Testing
| Type | What It Tests | How | Example |
|---|---|---|---|
| Performance | Speed, stability & responsiveness under normal load | Measure response times at typical user volume | API responds in under 200ms with 100 concurrent users |
| Load | Maximum workload before degradation | Gradually increase users until system slows | At what user count does checkout response exceed 2s? |
| Stress | System behaviour beyond its limit | Push past the maximum — what breaks first? | 10× normal load — does it crash gracefully or corrupt data? |
| Volume | Handling large amounts of data | Inject massive datasets | Database with 10 million records — do queries still perform? |
| Endurance / Soak | System behaviour under sustained load over time | Run at 70% capacity for 24–72 hours | Does memory leak appear after 48 hours of use? |
| Scalability | Ability to handle growth | Test elasticity of architecture | Can it auto-scale from 100 to 10,000 users during a sale event? |
Security & Other Non-Functional Types
| Type | What It Tests | Key Focus |
|---|---|---|
| Security / Vulnerability | System defences against attacks | Authentication, authorisation, SQL injection, XSS, data exposure |
| Usability | How intuitive and easy to use the product is | Navigation, error messages, accessibility, learning curve |
| Accessibility (a11y) | Usability for people with disabilities | Screen reader support, contrast ratios, keyboard navigation |
| Compatibility | Works on different hardware/OS/browsers | Cross-browser, cross-platform, different screen sizes |
| Recovery | Recovery after crashes or failures | Network failure, hardware crash — how fast does it recover? |
| Interoperability | Works with other systems | API integrations, third-party services |
Exploratory & Ad-Hoc Testing
| Type | Test Cases? | Domain Knowledge? | Best For |
|---|---|---|---|
| Exploratory | No predefined cases — you explore and test simultaneously | Yes — you know the functionality | Finding defects that scripted tests miss; new feature discovery |
| Ad-Hoc | No test cases; no planned approach | Not required — random checking | Quick sanity of unfamiliar areas; breaking a fragile module |
Exploratory testing is the more structured of the two — the tester uses experience and domain knowledge to guide what to explore. Ad-hoc is entirely random and unstructured.
Special Testing Types
| Type | Definition | Who Runs It |
|---|---|---|
| Alpha Testing | Testing by in-house team before release; finds critical issues in a controlled environment | Internal QA / developers |
| Beta Testing | Real users use the product in real environments before general release; captures real-world issues | External users (selected) |
| Gamma Testing | Final-stage testing when a product is nearly release-ready; gathers last feedback on product specification | Selected external users |
| Mutation Testing | Source code is deliberately changed in small ways; if tests still pass, they're not sensitive enough — finds gaps in test quality | Developers / QA automation |
| API Testing | Validates an API's functionality, reliability, performance, and security | QA engineers |
| Back-to-Back Testing | Two versions of the same system are tested simultaneously; results are compared for divergences | QA engineers |
| Use Case Testing | Tests all end-to-end flows as they would actually be performed by a real user | QA engineers |
| Data-Driven Testing (DDT) | Test scripts read inputs from external data files rather than hardcoded values | Automation QA |
Quick Reference — Which Test, When
| Stage | Test Types to Apply |
|---|---|
| New build received | Smoke Testing |
| Bug fix delivered | Sanity Testing → Regression Testing |
| New feature added | Feature Testing → Regression Testing |
| Before release | UAT → Performance → Security |
| Any time (discovery) | Exploratory Testing |
| Infrastructure change | Load → Stress → Recovery |
Building a Practical Testing Strategy
Knowing each test type in isolation is only half the skill. The harder part is deciding which combination of test types to apply to a specific project, feature, or release cycle — and how to allocate the effort between them. That decision should always be driven by risk, not habit.
Risk-Based Approach to Test Type Selection
Start every new feature or sprint by asking: what could go wrong, and what would the impact be? High-risk areas — payment flows, authentication, data writes — deserve the full spectrum: unit tests by developers, integration tests at the service boundary, regression tests across the suite, and exploratory testing by experienced QA engineers. Lower-risk areas, such as a static content page or a cosmetic UI change, may only need a smoke check and a quick exploratory pass.
Risk-based testing is not about cutting corners. It is about directing finite testing effort where defects will hurt the most. A bug in a rarely-visited admin screen is a low-risk candidate for deferral; a bug in the checkout flow is a showstopper regardless of its severity label.
The Testing Pyramid in Practice
The testing pyramid is a widely cited model that recommends the following distribution of test effort across three layers:
- Unit tests — 70%: Fast, cheap, and developer-owned. They catch logic errors at the source. A well-tested codebase can have thousands of unit tests running in under a minute.
- Integration tests — 20%: Verify that modules and services communicate correctly. These are slower and more brittle than unit tests but catch an entire class of defects that unit tests miss — broken contracts between components.
- End-to-End (E2E) tests — 10%: Simulate real user journeys across the full stack. They are the most expensive to write, slowest to run, and most likely to produce flaky results. Reserve them for the critical happy paths your business cannot afford to break.
In practice, many teams invert this pyramid — heavy E2E suites with almost no unit coverage. This leads to slow pipelines, fragile tests, and long feedback cycles. Trimming E2E tests to only the critical journeys and investing in unit and integration coverage almost always improves both quality and deployment speed.
Why Regression Suites Bloat — and How to Manage It
Every new feature adds test cases to the regression suite but almost none are ever removed. Over time, the suite grows to thousands of cases, takes hours to run, and becomes so slow that teams stop running it on every build. The fix is to periodically audit your regression suite: remove tests that duplicate each other, retire tests for features that no longer exist, and convert low-value E2E checks into faster integration or unit tests. Aim for a suite that completes within 30 minutes — if it takes longer, it is too large to provide fast feedback.
Test Type Selection Matrix
When a new feature lands, use this quick decision guide to select which test types to run:
| Scenario | Test Types to Apply | Rationale |
|---|---|---|
| New feature, first build | Smoke → Feature → Exploratory | Confirm the build is stable before deep testing |
| Bug fix delivered | Sanity → Regression (related area) | Verify the fix and check for regressions nearby |
| Performance-sensitive change | Performance → Load → Soak | Catch latency regressions and memory leaks early |
| Third-party integration changed | Integration → Contract → E2E | Validate data contracts haven't broken |
| Pre-release checkpoint | Full regression → UAT → Security | Final gate before production |
At Viasat Europe, working on inflight connectivity systems, hardware builds arrived every two to three weeks and the test window was short. We developed a tiered approach: smoke testing ran on every single build within the first two hours, performance testing ran weekly on the latest stable build, and compatibility testing across device types was reserved for the two weeks before a scheduled release. Skipping this discipline once — approving a patch build without non-functional testing — allowed a memory leak to reach production that only surfaced after six or more hours of sustained passenger usage. It took three days to identify, isolate, and hotfix. After that, non-functional checks became mandatory even for patch builds.
References
- ISTQB Foundation Level Syllabus — Chapter 2: Testing Throughout the Software Development Life Cycle
- IEEE 829 Standard for Software Test Documentation
- Myers, G. J. — The Art of Software Testing, Wiley
- OWASP Testing Guide — owasp.org
Back to Blog