Software Testing: Unit Tests, Integration, and QA Basics
Software testing is the structured practice of verifying that code does what it's supposed to do — and, just as importantly, that it doesn't do what it shouldn't. This page covers the three foundational layers of testing — unit tests, integration tests, and quality assurance — along with the decision logic for choosing which approach fits which situation. Whether a project is a solo Python script or a distributed enterprise system, these principles apply uniformly across the software development landscape.
Definition and scope
A unit test examines the smallest testable piece of code in isolation — typically a single function or method — without invoking databases, file systems, or network connections. An integration test checks that two or more components work correctly when combined. Quality assurance (QA) is the broader discipline that governs both, plus manual testing, performance testing, and release processes.
The International Software Testing Qualifications Board (ISTQB) defines testing as "the process of evaluating a component or system to detect differences between existing and required conditions." That definition is deliberately broad. It covers everything from a five-line assert statement in Python's built-in unittest module to a full regression suite run against a production mirror environment.
The scope distinction matters because conflating these layers is one of the more common and costly mistakes in software development. Unit tests run in milliseconds; integration tests may take minutes; full end-to-end QA suites can consume hours. Each layer has a different purpose, a different speed, and a different cost of failure.
How it works
The three testing layers stack on top of each other in a hierarchy sometimes called the testing pyramid, a model popularized by Mike Cohn in Succeeding with Agile (Addison-Wesley, 2009) and widely adopted by engineering organizations including Google.
-
Unit tests occupy the base of the pyramid — the largest volume, the fastest execution. A unit test isolates a function, passes known inputs, and asserts expected outputs. In Python, the standard library's
unittestmodule or the third-partypytestframework handles this. In Java, JUnit is the dominant standard, maintained under the JUnit 5 project. A well-structured unit test suite covers individual logic branches: the happy path, edge cases, and known failure conditions. -
Integration tests sit in the middle tier. They verify that a database query actually returns the right rows, that an API call parses the response correctly, or that a payment module interacts with a third-party gateway as specified. These tests are slower because they involve real (or realistic mock) external systems. The NIST Software Assurance program identifies integration testing as a critical phase for detecting interface defects that unit tests cannot surface.
-
QA (Quality Assurance) encompasses the full validation cycle. This includes system testing, user acceptance testing (UAT), regression testing, and performance benchmarking. QA processes are often governed by standards such as ISO/IEC 25010, which defines a software quality model across 8 main characteristics including functional suitability, reliability, and security.
A CI/CD pipeline — as described in practices endorsed by the IEEE Software Engineering Body of Knowledge (SWEBOK) — typically runs unit tests on every commit, integration tests on every pull request merge, and full QA suites before release candidates.
Common scenarios
Scenario 1: A function that calculates tax rates. This is a pure unit test target. It takes a number, applies a formula, returns a number. No database, no API. Write 6–8 assertions covering zero values, negative inputs, boundary thresholds, and standard cases. Done in under 50 milliseconds.
Scenario 2: A user authentication flow. This requires integration testing. The password hash function is unit-testable; the interaction between the hash function, the user database, and the session token system is not. An integration test spins up a test database (SQLite in-memory is a common choice), runs a registration and login sequence, and asserts that session state is correct end-to-end.
Scenario 3: An e-commerce checkout. This is QA territory. The individual cart functions have unit tests. The cart-to-payment handoff has integration tests. But the full scenario — user browses, adds items, applies a coupon, pays, receives a confirmation email — requires a coordinated QA suite that validates the entire workflow, including edge cases like insufficient inventory or expired credit cards.
Decision boundaries
The practical question is always: which type of test belongs here?
A clean decision framework follows three criteria:
- Isolation possible? If the code under test can be run without any external dependency, write a unit test. If it cannot — because it needs a real database, a message queue, or a third-party service — write an integration test.
- Does it cross a system boundary? Any interaction between two distinct systems (app server to database, microservice A to microservice B) requires integration coverage, not just unit coverage.
- Does business logic depend on the full flow? If correctness can only be verified by observing the complete user journey or multi-system outcome, that's a QA-level test.
One important contrast: unit tests and integration tests are automated by definition. QA testing may include manual steps, exploratory testing, and human judgment calls that automation cannot replicate. The ISTQB Foundation Level syllabus explicitly distinguishes static testing (reviewing code without executing it) from dynamic testing (executing code with inputs) — a distinction that shapes how teams structure their QA programs.
A common anti-pattern is writing 0 unit tests and relying entirely on integration or manual QA. A suite of 500 integration tests with no unit tests is slow, expensive to maintain, and produces test failures that are difficult to diagnose. Google's engineering practices documentation (available via Google Engineering Practices) recommends that roughly 70% of tests be unit tests, 20% integration, and 10% end-to-end — ratios that reflect the speed and diagnostic clarity trade-offs across the pyramid.
References
- ISTQB — International Software Testing Qualifications Board
- NIST Software and Supply Chain Assurance
- ISO/IEC 25010 — Systems and Software Quality Requirements and Evaluation
- JUnit 5 — Java Unit Testing Framework
- IEEE SWEBOK — Software Engineering Body of Knowledge
- Google Engineering Practices Documentation