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.

  1. 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 unittest module or the third-party pytest framework 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.

  2. 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.

  3. 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:

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