Software Testing Strategies
Unit Testing · Regression · Debugging — Simplified with Examples
1. Introduction — What is a Testing Strategy?
A testing strategy is a roadmap that tells your team what to test, when to test it, and how much
effort it will take. It's not random — it's planned from the very beginning of development.
• It combines test planning, test case design, test execution, and evaluation of results.
• It gives developers a step-by-step guide and gives managers clear milestones to track
progress.
• Because of time pressure, problems must surface as early as possible — not at the last
minute.
■ Example: Imagine building a mobile banking app. Without a strategy, a developer might only
test the login button right before launch and miss a bug in the money transfer module. A strategy
would have scheduled transfer tests in week 3, catching the bug early when it's cheaper to fix.
2. Verification vs. Validation
These two words are often confused. Here's the simple difference:
Term Question it answers Focus
Verification "Are we building the product right?" Code is correct and matches the
design/algorithm
Validation "Are we building the right product?" Software meets what the customer
actually needs
■ Example: Verification: A function that calculates tax is verified to ensure the formula in the code
matches the spec. Validation: The whole tax system is given to real accountants to confirm it
actually handles all the tax rules they need.
3. General Characteristics of Good Testing
• Conduct formal technical reviews before testing — catch errors early in reviews, not in
tests.
• Testing starts at the smallest level (individual functions) and grows outward to the full
system.
• Different techniques are used at different times — no single method fits all situations.
• Large projects use an independent test group separate from the developers.
• Testing and debugging are different activities — testing finds bugs, debugging fixes them.
■ Example: A team building a hospital patient management system does code reviews in week 2,
unit tests in week 4, integration tests in week 8, and full system tests in week 12 — each phase
using different tools. An independent QA team runs the final tests so developers don't overlook
their own mistakes.
4. Organizing for Testing — Who Does the Testing?
The goal of testing is to try to break the software. This conflicts with the developer's instinct to
protect their own work.
Common misconceptions (wrong ideas):
• The developer should do zero testing — WRONG. Developers must test their own code first.
• A secret team will come in and test everything later — WRONG. Testers should be involved
from day one.
• Testers only appear at the end of the project — WRONG.
The right approach — Independent Test Group (ITG):
• A separate team tests the software without the bias of the people who built it.
• They work closely with developers during design and coding so they understand the system.
• They remove the conflict of interest — a developer is unlikely to admit their own code is
broken.
■ Example: Google uses a dedicated Software Engineer in Test (SET) role. When Gmail was
being built, a separate SET team tried to crash it with millions of simulated users before launch —
the developers alone would never have tested at that scale.
5. The Four Levels of Testing
Testing happens in layers, from small to large:
Level What it tests Example
1. Unit Testing One function or component Test the login() function alone
2. Integration Testing How components work together Test login() connecting to the
database
3. Validation Testing Does it meet user Real users try the login process
requirements? and confirm it works as expected
4. System Testing Entire system as one whole Test the full app including login,
product payments, profile, all together
6. How Testing Strategy Flows (Inside-Out)
Think of testing like peeling an onion. You start at the center and work outward:
• Center: Code is written based on design.
• First ring: Unit Testing — test each piece of code by itself.
• Second ring: Integration Testing — put pieces together and test.
• Third ring: Validation Testing — confirm it matches user needs.
• Outer ring: System Testing — test everything as one product.
■ Example: You're building a food delivery app. First, test the 'calculate delivery fee' function
alone. Then test it connected to the cart. Then have real users place orders. Finally, test the
whole app including payments, maps, and notifications together.
7. How to Make Your Testing Strategy Succeed
• Write measurable requirements before testing starts. Don't say 'the app must be fast' —
say 'the page must load in under 2 seconds.'
• State testing goals in numbers. 'Zero critical bugs at launch' is measurable.
• Know your users. Create use-case scenarios for different user types — beginners, power
users, etc.
• Use rapid-cycle testing. Test often and get feedback quickly so you can adjust.
• Build software that can test itself. Self-diagnostic code reports its own errors.
• Review test plans before testing. Have other people check if your tests are complete.
• Collect metrics. Track number of bugs found per week to improve over time.
■ Example: Netflix measures app loading time in milliseconds, tests on 100+ device types
automatically, and has a 'chaos engineering' tool that randomly kills servers during testing to
ensure the system recovers — all part of their formal testing strategy.
8. Unit Testing — Testing One Piece at a Time
A unit test focuses on a single function or module in your code. It's the most detailed level of
testing.
• Tests the internal logic and data structures of the function.
• Easier when the module is designed with high cohesion (does one thing well) — fewer test
cases needed.
• When resources are limited, prioritize modules with high cyclomatic complexity — these
are the most complex and error-prone parts of the code.
■ Example: You have a function called `calculate_discount(price, member_type)`. Unit testing
would: send in price=100, member_type='gold' and check the output is 80. Send in price=0 and
check it doesn't crash. Send in price=-50 and check it rejects the invalid input. Each scenario is
one unit test.
9. What to Check in Unit Tests
When writing unit tests, check these five things:
Target What to check Example
Module Interface Data flows in and out Function receives price, returns discount
correctly — verify both are correct types
Local Data Temporary data stays intact A list being built inside a loop doesn't get
Structures during execution corrupted mid-way
Boundary Extreme values work Price = 0, Price = 999999, and exactly at
Conditions properly the discount threshold
Independent Paths Every line of code runs at Test both the IF and the ELSE branch of
least once every if-statement
Error Handling Bad inputs are handled Pass in a text string instead of a number
gracefully — should show error, not crash
10. Common Errors to Look For in Unit Tests
Computational (Math) Errors:
• Wrong order of operations — e.g., writing `2 + 3 * 4` when you meant `(2+3) * 4`
• Mixing data types — adding an integer to a float and getting unexpected results
• Variables not initialized — using a variable before assigning it a value
• Rounding errors — 0.1 + 0.2 ≠ 0.3 in floating point math
■ Example: In 1994, Intel's Pentium chip had a floating-point division bug. A unit test checking
4195835/3145727 would have caught it — the chip returned the wrong answer due to a math
error in hardware.
Other Errors to Watch For:
• Comparing two different types — e.g., comparing a string '5' with an integer 5
• Wrong logical operators — using OR when AND was needed in a condition
• Loop never ends or exits too early
• Off-by-one errors — loop runs 9 times instead of 10
■ Example: A bank transfers $100 but loops one extra time due to an off-by-one error —
suddenly the customer gets $200. This exact type of error caused real banking bugs where
duplicate transactions were processed.
11. Problems in Error Handling to Uncover
When your code encounters an error, how it handles that error is just as important as the error
itself. Test for:
• Error messages that are vague or confusing — the user can't understand what went
wrong.
• The error message shown doesn't match the actual error that occurred.
• The operating system crashes the program before your error handler even runs.
• Exceptions are caught but handled incorrectly — the wrong recovery action is taken.
• Error message doesn't give enough info to locate the cause.
■ Example: You enter a wrong password. Bad error handling: 'Error 403'. Good error handling:
'Incorrect password. You have 2 attempts remaining before your account is locked.' The second
message is clear, actionable, and doesn't expose security details.
12. Drivers and Stubs — Testing Tools
When unit testing a module, the modules it depends on may not be ready yet. So you use two
helpers:
Tool What it does Example
Driver A fake "main program" that feeds You're testing a payment module. The
test inputs to the module you're driver pretends to be the app and sends
testing and prints results in "pay $50" to test it.
Stub A fake version of a module your Your payment module calls a bank API.
code calls — it pretends to work The stub pretends to be the bank and
without actually doing anything real always returns "approved."
■ Note: Drivers and stubs are temporary test code — they must be written but are never shipped
as part of the final software. They are overhead costs of testing.
13. When is Testing Complete?
There is no perfect answer to this question — you can never test everything. In reality:
• Every time a user runs the software, they are effectively testing it.
• Testing usually stops when the team runs out of time or money — not when all bugs are
gone.
• A practical approach: classify bugs by severity level (Critical, Major, Minor). Testing is
'done' when no more Critical or Major bugs are being found.
■ Example: Microsoft ships Windows with a known list of 'deferred bugs' — bugs that exist but
are considered low-risk or low-impact. They stop testing when the rate of new critical bug
discovery drops below a set threshold (e.g., less than 1 critical bug per 1000 hours of testing).
14. Integration Testing — Testing Modules Together
After each module passes its unit tests, you combine them and test how they work together. The
main goal is to catch errors at the interfaces — the points where one module hands data to
another.
• Take unit-tested modules and build a complete program structure.
• Two approaches exist: Non-incremental (all at once) and Incremental (piece by piece).
■ Example: Module A (login) passes unit tests. Module B (dashboard) passes unit tests. But
when A tries to pass the user's session ID to B, it sends it in the wrong format and B crashes.
That's an interface bug — only caught during integration testing.
15. Non-Incremental Integration (Big Bang) — What NOT to Do
The Big Bang approach combines all modules at once and tests everything together. It sounds
efficient but causes chaos:
• All components are merged at once — no gradual building.
• Many strange, seemingly unrelated errors appear all at once.
• It's nearly impossible to figure out which module caused which error.
• Fixing one set of errors reveals more errors — feels like an endless loop.
■ Example: A team of 10 developers each write a different module separately for 3 months. On
the last week, they combine everything. Suddenly the system crashes on startup. Is it the
database module? The authentication module? The config file? Nobody knows — it takes weeks
just to isolate one bug. This is why Big Bang is avoided.
16. Incremental Integration Testing — The Right Way
Instead of combining everything at once, you add one module at a time and test after each
addition. Three types:
• Top-down: Start from the main control module and work downward.
• Bottom-up: Start from the lowest level modules and work upward.
• Sandwich: Do both top-down and bottom-up at the same time.
Advantages of Incremental:
• Errors are easy to isolate — you just added one module, so it's probably the culprit.
• Interfaces get fully tested one at a time.
• A systematic, organized approach.
■ Example: You're building an e-commerce site. Incremental approach: Start with product listing
(test it). Add shopping cart (test it + listing together). Add checkout (test all 3). Add payment (test
all 4). If payment breaks something, you know exactly where to look — the newest addition.
17. Top-Down Integration
Start from the top module (main controller) and gradually add lower-level modules. Stubs are
used to replace not-yet-integrated lower modules.
✓ Advantage: Major decision-making logic is tested early.
✗ Disadvantage: Data flow can't be tested until lower modules are added later. Many stubs need
to be built.
■ Example: An ATM software system: test the main menu controller first (which has stubs for
balance check, withdraw, deposit). Add the balance check module next, replace its stub. Then
add withdraw. This way, the most critical user flows (withdraw money) are tested early.
18. Bottom-Up Integration
Start from the lowest-level modules (utility functions, database access) and work upward.
Drivers are used to test them since the upper modules don't exist yet.
✓ Advantage: Low-level data processing is verified early. No stubs needed.
✗ Disadvantage: High-level control logic is not tested until late. Drivers must be built.
■ Example: Testing a hospital system bottom-up: first test the database query functions (lowest
level), then test the patient record service that calls them, then test the doctor dashboard that calls
the service. By the time the UI is integrated, the data layer is rock-solid.
19. Sandwich Integration — Best of Both
Sandwich integration does top-down and bottom-up at the same time, meeting in the middle:
• High-level modules are tested top-down.
• Low-level modules are tested bottom-up simultaneously.
• The two teams meet in the middle.
• Minimizes the need for both stubs and drivers.
■ Example: Building a university portal: Team A tests the admin dashboard top-down (main
menu → course management → scheduling). Team B tests the database and student record
modules bottom-up. They merge when both sides reach the 'course enrollment' module in the
middle.
20. The Full Testing Strategy in One View
Phase Focus Real Example
Unit Testing Individual functions — internal Test the `send_email()` function with
logic valid and invalid addresses
Integration Testing How modules connect and Test `send_email()` connected to the
exchange data email server module
Validation Testing Functional, behavioral, Users confirm the right email is sent,
performance needs formatted correctly, and arrives fast
System Testing Everything together — hardware Full product tested on real servers
+ software + users with real users logging in, buying,
emailing
21. Smoke Testing — Is the Build Even Usable?
Before doing full integration testing, do a quick smoke test to see if the build works at all. The
name comes from hardware: plug it in and check if smoke comes out!
• Compile and link the software into a build.
• Run quick, broad tests to see if anything is catastrophically broken ('show-stopper' errors).
• The whole product is smoke-tested every day so problems are caught within 24 hours.
• After smoke test passes, detailed test scripts are run.
■ Example: Every night at Microsoft, the entire Windows codebase is compiled and run through a
smoke test. If the desktop doesn't appear or the task bar crashes, the build is 'broken' and the last
developer to commit code must fix it before morning. This is called a 'daily build' strategy.
22. Benefits of Smoke Testing
• Reduces integration risk: Problems are caught daily, not in the final week.
• Improves end product quality: Both functional and design errors are spotted early.
• Easier to fix: The newest component added is almost always the one that broke things.
• Progress is visible: Managers can see exactly how much of the system is working.
■ Example: A startup launches a new feature for their app. Instead of waiting until all 5 new
modules are done to test, they smoke-test each module as it's added. When Module 3 breaks the
login screen, they fix it the same day instead of discovering it a month later when all modules are
intertwined.
23. Regression Testing — Did We Break Something That Already
Worked?
Every time you change code, you risk breaking something that previously worked. Regression
testing re-runs a set of old tests to make sure.
• Re-execute a small but representative subset of previous tests after any change.
• Ensures new code doesn't cause unexpected side effects in old features.
• Can be done manually or using automated tools (record-and-playback software).
A regression test suite has 3 types of test cases:
• Tests that cover all main software functions.
• Tests specifically targeting the features likely to be affected by the change.
• Tests that directly test the changed code itself.
■ Example: Facebook updates their photo upload feature. Their regression suite automatically
re-runs 500 previous tests. It catches that the change accidentally broke the 'tag a friend' feature
— a function nobody touched, but which was affected by a shared image-processing function that
changed.
24. Types of System Testing
Type What it tests Example
Recovery Testing Can the system recover from Pull the power plug during a
crashes, power loss, or data database write — does the data
corruption? survive?
Security Testing Can hackers break in? Are Try SQL injection attacks on the
protection mechanisms working? login form — does the system
block them?
Stress Testing What happens under extreme load Simulate 1 million users hitting the
— huge volume, abnormal usage? server at once to find the breaking
point
Performance How fast does the system run in Measure API response time under
Testing real conditions? normal traffic vs. peak traffic
25. Validation Testing — Does It Do What the Customer Wants?
Validation testing comes after integration testing and checks the software against the actual
customer requirements.
• Focuses on things the user can see and actions they can perform.
• Checks functional, behavioral, performance, documentation, and usability requirements.
• After each test: either it passes (accepted) or a deficiency list is created.
• A configuration audit checks that all parts of the software are cataloged correctly.
■ Example: A university student portal requirement: 'Students must be able to view their grades
within 3 clicks from the home page.' Validation testing has real students try this. If it takes 5 clicks,
the requirement is not validated — it goes on the deficiency list and must be redesigned.
26. Alpha and Beta Testing — Real User Testing
Type Where Who controls it Example
Alpha Testing At the Developer watches, Early access testers visit the
developer's site controlled office and try the app while
environment engineers watch
Beta Testing At the end-user's Developer is NOT Spotify Beta — thousands of
site present, real-world users install and report bugs
environment through a feedback form
After beta testing, engineers fix remaining bugs and the product is released to all customers.
■ Example: Apple gives early iPhone builds to a small group of employees (alpha). Then invites
developers to test through TestFlight (beta). Their feedback shapes the final iOS release. The
same cycle happens every year.
27. Testing Object-Oriented Software (OO)
OO software uses classes and objects, which changes how you test:
• You can't test a single operation alone — it depends on the class state (what data the object
holds).
• Traditional top-down/bottom-up integration barely applies — OO structure is different.
• Class testing is the equivalent of unit testing — test all operations and states of one class.
• Drivers and stubs still work: drivers test operations; stubs replace unfinished collaborating
classes.
Two OO integration strategies:
• Thread-based testing: Test all classes needed to respond to one specific event/input. Each
thread is tested and then regression-tested.
• Use-based testing: Start with independent classes (no dependencies), then add dependent
classes layer by layer.
■ Example: You're testing a ride-sharing app with OO design. Thread-based: test all classes
involved in the 'Book a Ride' event — User class, Location class, Driver class, Payment class —
as one thread. Use-based: test Location class first (it depends on nothing), then test RideMatcher
that depends on Location.
28. The Debugging Process
Debugging starts when a test finds a bug. The goal is to find and fix the root cause — not just
hide the symptom.
• A test case reveals unexpected behavior — the difference between expected and actual
output is a symptom.
• The symptom points to an underlying cause that is often hidden.
• Debugging tries to match the symptom to its cause.
• Good debugging is more art than science — it requires experience and intuition.
■ Example: Your app crashes every time a user uploads a file larger than 10MB. The symptom:
crash. The debugging process: check the upload function → find it doesn't handle file sizes above
10MB → find the variable storing file size is an 'int' that can't hold large values → fix by changing
to 'long'. The crash was just the symptom; the integer overflow was the cause.
29. Why is Debugging So Hard?
• Symptom and cause are far apart: A bug in module A only shows up as a crash in module
Z.
• Symptom disappears when you fix another bug: Two bugs cancel each other out — fix
one and the other surfaces.
• Symptom caused by non-errors: Floating point rounding makes 0.1+0.2 ≠ 0.3 — it's not a
bug, it's how computers work.
• Hard-to-trace human errors: A developer's typo 10,000 lines away from the crash.
• Timing problems: The bug only happens when two functions run at exactly the same
millisecond.
• Intermittent symptoms: The bug appears once a week and can't be reliably reproduced.
• Distributed causes: Multiple tasks running in parallel all contribute to the bug.
■ Example: The famous 'Heisenbug' — a bug that disappears when you try to observe it. Adding
a print statement to debug changes the timing of the program, and the bug stops occurring.
Remove the print statement and it comes back. This is a real phenomenon in
concurrent/multithreaded programs.
30. The Three Debugging Strategies
Strategy How it works Example
1. Brute Force Dump all memory, add print Add print("here1"), print("here2")
statements everywhere, trace throughout the code until you find
every line. Most common but least where it stops working.
efficient.
2. Backtracking Start from where the symptom App crashes on line 850. Go back to
appears and manually trace line 800... 750... 700... find a null
backward through the code until variable at line 623 that propagated
you find the cause. forward.
3. Cause List all possible causes, form a Bug only happens on Tuesdays?
Elimination hypothesis, then test to Hypothesis: Is it a scheduled job?
prove/disprove each one until only Test by disabling it. If bug disappears,
the true cause remains. cause found.
31. Three Questions to Ask Before You Fix a Bug
Before writing the fix, always stop and ask:
# Question Why it matters
1 Is the same bug likely to exist in If you used the same bad pattern in 5 functions, fix
other parts of the code? all 5 now — not one at a time as they fail.
2 What new bug might my fix Check if the fix affects data structures or logic
accidentally introduce? shared with other modules — a "fix" that breaks 3
other things is not a fix.
3 How could we have prevented this If the bug came from missing input validation, add a
bug in the first place? rule to always validate inputs — this prevents the
same class of bug forever.
■ Example: Amazon found a bug in their recommendation engine. Before fixing it: (1) They
checked if the same calculation error existed in other recommendation types — it did, in 4 places.
(2) They reviewed whether the fix would affect the A/B testing module — it would, so they
coordinated. (3) They added an automated test to prevent that class of math error permanently.
Software V&V — Testing Strategies (Unit, Regression & Debugging) | Simplified with Examples