You can't open the box,
so you interrogate the outputs.
Black-box testing throws away the source code and treats the system as a sealed unit: feed it inputs, watch the outputs, and derive test cases straight from the specification. Five systematic techniques — equivalence partitioning, boundary value analysis, decision tables, state transition testing, and use case testing — turn "try everything" into "try the few inputs that actually matter."
From "what should we test?" to an executable test
Before you can pick a technique, you need to know what you're testing, what the inputs are, and what results should come out. That's the job of the three-stage test development process.
1. Test Analysis
what to testThe test basis documentation is analyzed to identify the test conditions. A test condition is an item or event that could be verified by one or more test cases — a function, a transaction, a quality characteristic, or a structural element (e.g. a web page's menus).
2. Test Design
test cases + dataA test case is specified as a set of:
Pre-conditions → Inputs → Expected results → Post-conditions, developed to
cover certain test condition(s).
3. Test Implementation
organize & scheduleTest cases are Developed → Implemented →
Prioritized → Organized into test procedures. A manual test procedure specifies
the sequence of actions for executing a test; an automated test procedure (test
script) does the same for a test execution tool.
- The order of execution of test procedures / scripts
- When they will be executed
- By whom they will be executed
- Risks, regression tests, prioritization
- Technical and logical dependencies
Knight Capital: what happens when nobody tested the deployment
The motivating "why testing matters" story for this chapter — a real high-frequency-trading firm, one bad deployment, $460 million gone in 45 minutes.
Knight Capital Group — largest U.S. equities trader
An American global financial services firm whose high-frequency trading (HFT) algorithms gave it a 17.3% market share on NYSE and 16.9% on NASDAQ.
Trading Program Ran Amok, With No 'Off' Switch
Knight's computers executed a series of unusually large automatic orders — "spit out duplicate buy and sell orders, jamming the market with high volumes of trades that caused wild swings in stock prices."
A dead function was inadvertently reactivated
A new algorithmic trading program was installed and began operation on Aug 1. A dormant legacy program was somehow "inadvertently reactivated," and once active it started multiplying stock trades by one thousand — sending 4 million orders when attempting to fill just 212 customer orders. Knight's staff looked through eight sets of software before determining what happened.
Root causes traced back to 2005
- Code changes in 2005 introduced defects. The defective function was not meant to be used — but it was kept in the codebase.
- New code deployed in late July 2012 triggered the defective function under new rules; it was unable to recognize when orders had been filled.
- Knight ignored system-generated warning emails.
- Inadequate controls and procedures for code deployment and testing.
Static vs. Dynamic, and where black box fits
Before zooming into black-box techniques, place them on the full map of test design techniques.
Static vs. Dynamic testing
recallManual examination and automated analysis of code without executing it — informal reviews, walkthroughs, technical reviews, inspections; plus static analysis (data flow, control flow).
Code is executed. Splits into three families: structure-based (white box — statement, decision, condition, multiple condition), specification-based (black box — this chapter), and experience-based (error guessing, exploratory testing).
Static
Reviews & static analysis — no execution at all.
Structure-based (white box)
Statement, decision, condition, multiple condition coverage.
Specification-based (black box)
Equivalence partitioning, boundary value, decision tables, state transition, use case — this chapter.
Experience-based
Error guessing, exploratory testing.
What "black box" actually means
Also called functional testing or specification-based testing — three names for the same idea, each highlighting a different angle.
Black Box Testing
no view of the codeTesting software against a specification of its external behavior, without knowledge of internal implementation details. Applies to software "units" (e.g. classes) or to entire programs, using API docs / functional specs / requirements specs as the source of truth. Because it purposely disregards control structure, attention focuses on the information domain — data that goes in, data that comes out. Goal: derive sets of input conditions (test cases) that fully exercise the external functionality.
- Incorrect or missing functions
- Interface errors (usability problems, concurrency/timing errors)
- Errors in data structures or external database access
- Behavior or performance errors
- Initialization and termination errors
- How is functional validity tested?
- How are system behavior and performance tested?
- What classes of input will make good test cases?
- Is the system sensitive to certain input values?
- How are the boundary values of a data class isolated?
- What data rates/volume can the system tolerate?
- What effect will specific combinations of data have?
The information domain
Inputs: individual input values (try many different values per input), combinations of inputs (inputs aren't independent — programs process several together), and the ordering & timing of inputs (which can also matter).
-99 ≤ N ≤ 99, integer/float, non-negative) · one of a fixed
enumerated set ({Jan, Feb, Mar...}, {Visa, MasterCard, Discover...})
· formatted strings (phone numbers, file names, URLs, credit-card numbers, regex).Why black box?
- Early — can start before code is written; even incomplete/informal specs work (though precise, complete specs build better test suites). Side benefits: reveals ambiguities in the spec, assesses testability, and can even improve the spec itself (in the extreme, as in XP, test cases are the spec).
- Effective — finds classes of defects like missing logic that structural (white-box) testing can never find, because it never focuses on code that isn't there.
- Widely applicable — any description of behavior counts as a spec, at any granularity from module to system.
- Economical — less expensive than structural testing. It's the base-line technique for designing test cases.
Functional vs. Structural: applicability
| Functional (black box) | Applicable at every granularity: unit test (module interface spec), integration test (API/subsystem spec), system test (system requirements spec), regression test (requirements + bug history). |
|---|---|
| Structural (white box) | Applicable mainly to relatively small parts of a system — chiefly unit test. |
From specification to test cases — 4 steps
- Decompose the specification — if it's large, break it into independently testable features.
- Select representatives — representative values of each input, or representative behaviors of a model (often simple I/O transformations don't describe a whole system, so we use models in program spec, design, and test design).
- Form test specifications — typically combinations of input values, or model behaviors.
- Produce and execute actual tests.
Equivalence Partitioning
The universe of possible test cases is too large to try them all — equivalence partitioning tells you which small subset to actually run.
Equivalence Classes
divide & conquer the input domainA black-box method that divides the input domain into classes of data from which test cases are derived. An ideal test case single-handedly uncovers a complete class of errors, reducing the total number of test cases needed. From each equivalence class, test cases are selected so the largest number of attributes of that class are exercised at once. Partitions should not share common elements — but don't worry if classes overlap with each other (better redundant than incomplete); they will, however, easily overlap with boundary-value test cases.
| Input condition specifies… | Classes defined |
|---|---|
| A range (e.g. 1–10) | 1 valid + 2 invalid: {1..10}, {x<1}, {x>10} |
| A specific value (e.g. 250) | 1 valid + 2 invalid: {250}, {x<250}, {x>250} |
| A member of a set (e.g. {-2.5, 7.3, 8.4}) | 1 valid + 1 invalid: {set}, {any other x} |
| A Boolean value | 1 valid + 1 invalid: {true}, {false} |
- Look for ranges of numbers or values
- Look for memberships in groups
- Some classes may be based on time
- Include invalid inputs
- Look for internal boundaries
- Test multiple values per class — you're often unsure the classes were defined completely, so multiple values are more thorough than one
int Add(n1, n2, n3, ...) could be
partitioned by number of inputs, by sign of operands, or by magnitude — all valid,
all different.Worked example — integer & phone number
| Input | Valid Equivalence Classes | Invalid Equivalence Classes |
|---|---|---|
Integer N such that:-99 ≤ N ≤ 99 |
[-99,-10], [-9,-1], 0, [1,9], [10,99] |
<-99, >99, malformed numbers (12-, 1-2-3), non-numeric strings (junk, 1E2, $13), empty value |
| Phone number Area code: [200,999] Prefix: (200,999] Suffix: any 4 digits |
555-5555, (555)555-5555, 555-555-5555, 200≤Area≤999, 200<Prefix≤999 |
Invalid formats (5555555, (555)(555)5555), area code <200 or >999, non-numeric characters in area code — similarly for prefix & suffix |
Boundary Value Analysis
A greater number of errors occur at the boundaries of the input domain than in the "center" — so that's exactly where you aim.
Boundary Value Analysis
extension of equivalence partitioningSelects test cases at the edges of a class, deriving them from both the input domain and the output domain. Use input values at their minimum, just above the minimum, at a nominal value, at the maximum, and just below the maximum. Robustness: how does the software react when boundaries are exceeded?
- If an input condition specifies a range bounded by a and b, design test cases at a and b, plus values just above and just below each.
- If an input condition specifies a number of values, exercise the minimum and maximum, plus values just above/below each.
Apply the same two guidelines to output conditions — produce output at the minimum, maximum, and just below/above each. If internal data structures have prescribed boundaries (e.g. an array), design a test to exercise them at their limits too.
if (200 < areaCode && areaCode < 999) — Wrong!if (200 <= areaCode && areaCode <= 999) — correctTesting area codes 200 and 999 would catch this error; a center value like 770 would not. In addition to center values, always test values right on a boundary and very close to it on either side.
Worked example — same inputs as before
| Input | Boundary Cases |
|---|---|
Integer N such that -99 ≤ N ≤ 99 | -100, -99, -98, -10, -9, -1, 0, 1, 9, 10, 98, 99, 100 |
| Phone number — Area code [200,999], Prefix [200,999], Suffix 4 digits | Area code: 199, 200, 201 and 998, 999, 1000 — Prefix: 200, 199, 198 and 998, 999, 1000 — Suffix: 3 digits, 5 digits |
int x = atoi(str);, which must
distinguish digits from non-digits. A real boundary case: will the program wrongly accept
/ (ASCII 47, one below '0'=48) or : (ASCII 58, one
above '9'=57) as digits?Limitations
- Doesn't require much thought — easy to apply mechanically, easy to apply mindlessly
- May miss internal boundaries
- Usually assumes variables are independent
- Values right at the boundary may not have meaning in every domain
Notes
- Applies at all test levels
- Relatively easy to apply, high defect-finding capability
- Detailed specifications help a lot
- Considered an extension of equivalence partitioning; boundary values are used for test data selection
Decision Table Testing
When behavior depends on combinations of Boolean conditions, decision tables force you to consider every combination systematically — like a truth table for business rules.
P ∧ Q): each
row/column is one combination of T/F inputs mapped to one output. Decision tables do the
same for business rules.Decision Table testing
a.k.a. cause-effect tableA good way to capture system requirements that contain logical conditions, to document internal system design, and to record complex business rules a system must implement. Useful whenever input conditions and actions can be stated as true/false (Boolean). The table contains the triggering conditions — all combinations of true/false for all input conditions — and the resulting actions for each combination. Its strength: it creates combinations of conditions that might not otherwise get exercised during testing, and it's effective at revealing faults hiding in the requirements themselves.
2ⁿ rules/columns, though many can be merged with "don't care"
(—) dashes when a condition doesn't affect the outcome.Worked example — credit card sign-up discount
Rules: new customers get 15% off; existing loyalty-card holders get 10% off; coupon holders get 20% off (but a coupon can't combine with the new-customer discount); discounts are added if applicable.
| Conditions | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
|---|---|---|---|---|---|---|---|---|
| New customer (15%) | T | T | T | T | F | F | F | F |
| Loyalty card (10%) | T | T | F | F | T | T | F | F |
| Coupon (20%) | T | F | T | F | T | F | T | F |
| Discount | ×(invalid) | ×(invalid) | 20% | 15% | 30% | 10% | 20% | 0% |
New=T, Loyalty=T, Coupon=— → invalid (new-customer discount can't combine with a
loyalty card at all in this rule set), Rules 3&7 collapse to Coupon=T, New=—
→ 20%, leaving six effective rules instead of eight raw combinations — this consolidation is
exactly what makes decision tables powerful for reasoning about business rules.State Transition Testing
For systems where the same input can produce different outputs depending on what happened before — the system's own history becomes part of the spec.
State Transition Testing
finite state machineA system can be in a finite number of different states — this can be described as a finite state machine, shown visually as a state diagram. Any system where you get a different output for the same input, depending on what happened before, is a finite-state system. Transitions from one state to another are determined by the rules of the "machine."
Start →(card inserted)→ Wait for PIN
→(enter PIN)→ 1st try →(PIN OK)→ Access to account, or 1st try →(PIN not
OK)→ 2nd try →(PIN not OK)→ 3rd try →(PIN not OK)→ Eat card.| Card inserted | Enter PIN | PIN OK | PIN not OK | |
|---|---|---|---|---|
| S1 Start | S2 | — | — | — |
| S2 Wait for PIN | — | S3 | — | — |
| S3 1st try | — | — | S6 | S4 |
| S4 2nd try | — | — | S6 | S5 |
| S5 3rd try | — | — | S6 | S7 |
| S6 Access to acct. | — | — | — | — |
| S7 Eat card | S1 | — | — | — |
Invalid or null
transitions are the dashes (—) — e.g. you cannot go straight from
"Start" to "PIN OK" without inserting a card first.
Because a system may respond differently depending on current conditions or previous history. It lets the tester view the software in terms of: its states, transitions between states, the events that trigger transitions, and the actions that may result.
Tests can be designed to- Cover a typical sequence of states
- Exercise specific sequences of transitions
- Cover every state
- Exercise every transition
- Test invalid transitions
Use Case Testing
Instead of testing one input at a time, walk a whole business transaction from start to finish, the way a real user would experience it.
Use Case Testing
actor ↔ system interactionsA use case describes interactions between actors (users and the system), which produce a result of value to a system user. Test cases are identified that exercise the whole system on a transaction-by-transaction basis, from start to finish, describing interactions between actor and system in the language of the business rather than technical terms — especially when the actor is a business user.
| Use case name | <name> |
|---|---|
| Actor(s) | <actor1>, ... |
| Pre-conditions | must be met for the use case to work |
| Main success scenario | numbered A: action / S: response steps |
| Extensions | alternative branches: <cause> → S: <response> |
| Post-conditions | observable results / final state after completion |
| Step | Description |
|---|---|
| 1 | A: Inserts card |
| 2 | S: Validates card and asks for PIN |
| 3 | A: Enters PIN |
| 4 | S: Validates PIN |
| 5 | S: Allows access to account |
| 2a | Ext: Card not valid → S: Display message, reject card |
| 4a | Ext: PIN not valid → S: Display message, ask retry (twice) |
| 4b | Ext: PIN invalid 3× → S: Eat card and exit |
Beyond correctness: the "-ility" tests
Functional black-box techniques ask "does it produce the right output?" This set of general testing types asks broader questions about how the system behaves under real-world conditions.
Race Conditions & Timing
Many systems run concurrent activities (OS interrupts, servers with many clients, multi-action apps). Test concurrency scenarios that share resources and may conflict. "Race conditions" only appear when activities interleave in particular ways — hard to reproduce. Test on hardware of varying speeds.
Performance Testing
Measure running times of tasks, memory usage (incl. leaks), network usage (bandwidth, open connections), disk usage (footprint, temp-file cleanup), and process/thread priorities. Tools: IBM Rational Performance Tester, Apache JMeter, WebLOAD, LoadRunner.
Limit Testing
Test the system at the limits of normal use — every documented limit (max concurrent users/connections, max open files, max request/file size). What happens just beyond the specified limit — graceful degradation, or a crash?
Stress Testing
Test under extreme conditions, beyond normal-use limits: low memory, disk faults (read/write failures, full disk, corruption), network faults, unusually high request counts/sizes/data rates. An excellent way to find bugs even if the system never needs to run this way in production.
Security Testing
Any system handling sensitive data/functions is a target. Ask: how feasible is it to break in? Learn hacker techniques, try attacks, or hire a security expert. If someone broke in — or an authorized user turned malicious — what damage could they do? Tools: Metasploit, W3af, Zed Attack Proxy (ZAP), Wapiti.
Usability Testing
Is the UI intuitive, organized, logical? Does it frustrate users? Are common tasks simple? Does it follow platform conventions? Get real users to perform tasks, watch for trouble, collect feedback, and log bugs. Tools: Crazy Egg, Optimizely, Usabilla.
Recovery Testing
Crash the program (or cut power) at arbitrary points. Does it restart correctly? Was persistent data corrupted? Was data loss within acceptable limits? Can it recover from corrupted/deleted config files? Does it need to survive hardware failures?
Configuration Testing
Test on all required hardware configs (CPU, memory, disk, graphics, network card) and all required OS versions — virtualization (VMWare, Virtual PC) helps here. Test as many hardware/OS combinations as feasible, including installers.
Compatibility Testing
Confirm the program works with the other programs it's supposed to interoperate with — e.g. can Word 12.0 load files from Word 11.0? Does "Save As…" really export to Word, WordPerfect, PDF, HTML, Plain Text? Test every stated compatibility requirement.
Documentation Testing
Test every instruction in the documentation for completeness and accuracy. "How To..." guides are notorious for drifting out of sync with UI changes. Test docs on real users to confirm they're clear and complete.
When the spec runs out, intuition takes over
Tests derived from the tester's skill, intuition, and experience with similar applications and technologies — best used to augment systematic techniques, not replace them.
Error Guessing
The most commonly used experience-based technique. Testers generally anticipate defects based on experience. A structured approach — enumerate a list of possible errors and design tests that attack them — is called fault attack.
Exploratory Testing
Concurrent test design, execution, logging, and learning, based on a test charter of objectives, carried out within time-boxes. Most useful where specs are few/inadequate, under severe time pressure, or to complement more formal testing — it can help ensure the most serious defects surface.
Special Value Testing
The most widely practiced form of functional testing. The tester uses domain knowledge, experience, or intuition to probe areas of probable errors. Other names: "hacking," "out-of-box testing," "ad hoc testing," "seat of the pants testing," "guerilla testing."
- Uses: complex mathematical/algorithmic calculations, worst-case situations (similar to robustness), problematic situations from past experience, "second guessing" the likely implementation.
- Characteristics: experience really helps, frequently done by the customer or user, defies measurement, highly intuitive, seldom repeatable — but often very effective.
Choosing test techniques
There's no single "best" technique — the right choice depends on: type of system · time and budget · development life-cycle · regulatory standards · customer requirements · contractual requirements · test objectives · level of risk · type of risk · documentation available · use case models · knowledge of the testers · previous experience with types of defects found.
The five techniques, in one table
| Technique | Core idea | Use when... | Worked example from the deck |
|---|---|---|---|
| Equivalence Partitioning | Divide input domain into classes that behave the same; test one representative per class | Input is defined as ranges or discrete sets of values | Integer -99..99, phone number, ZIP code |
| Boundary Value Analysis | Test min, just-above-min, nominal, just-below-max, max | Always — as a companion to equivalence partitioning | Area code boundary bug: < vs ≤ |
| Decision Table Testing | Truth-table every combination of Boolean conditions → resulting action | Business rules with several independent T/F conditions | Credit card discount rules (new/loyalty/coupon) |
| State Transition Testing | Model the system as states + events + transitions + actions | Same input gives different output depending on history | ATM PIN entry (3 tries then eat card) |
| Use Case Testing | Walk a full actor↔system transaction, main scenario + extensions | Testing end-to-end business workflows / acceptance testing | ATM card entry; online training website roles |
Where students actually lose marks
Seven confusions that show up again and again on exams for this chapter.
1. Treating equivalence partitioning and boundary value analysis as the same technique
2. Writing the boundary condition with the wrong comparison operator
if (200 < areaCode && areaCode < 999) for a valid range of [200, 999]if (200 <= areaCode && areaCode <= 999) — using strict < silently excludes the boundary values themselves. Testing exactly at 200 and 999 (not just a mid-range value like 770) is what catches this.3. Mixing up how many valid/invalid classes a condition produces
4. Listing 2ⁿ raw decision-table rules instead of consolidating with "don't care"
5. Reading a dash ("—") in a state/event table as a valid transition
6. Calling black-box testing "random" or "guessing"
7. Confusing use case testing with state transition testing
Quick self-check — click a question to reveal the answer
No grading, no pressure. If you get one wrong, jump back to that section above before moving on.
[18,65]. Invalid: age < 18 and age > 65 — a range always produces one valid class and two invalid classes (below and above).Apply the right technique to the requirement
Realistic exam-style questions built from the exact examples used in this deck — drill these until picking the right technique becomes automatic.