Software Testing Essentials Explained
Software Testing Essentials Explained
Imagine a world where a single line of code can cost millions of dollars or even human lives. That isn't a dystopian
future; it is the reality of the software-driven world we live in today. From the finance apps on your phone to the control
systems of airplanes, software is ubiquitous. Because we rely on it so heavily, the quality of that software is non-
negotiable.
History is filled with cautionary tales that illustrate why testing is not just a "nice-to-have," but a critical necessity.
• The 36-Second Flight: In 1996, the Ariane 5 rocket exploded just 36 seconds after launch. The cause? A
software exception where a 64-bit floating-point number was dangerously converted into a 16-bit integer.
• The Silent Killer: The Therac-25 medical accelerator, designed to treat tumors, tragically killed six patients
due to a radiation overdose. The culprit was a race condition in the control software.
• The Expensive Math Error: Intel once lost an estimated $475 million because of a defective Pentium
chip that couldn't divide floating-point numbers correctly.
There is a golden rule in software engineering: Timing is everything. If you catch a bug during
the Requirements phase, it might cost $6 to fix. If that same bug slips through to Post-Deployment, the cost could
skyrocket to $100,000 or more. This is why testing shouldn't be saved for the end; it must be a continuous heartbeat
throughout the project.
Software doesn't just appear; it evolves through a Software Development Life Cycle (SDLC). Think of this as the
"life story" of an application, from its conception to its retirement.
• The Waterfall Model: The traditional, step-by-step approach. You finish one phase before starting the next.
• The V-Model: This model emphasizes Verification and Validation. Picture a "V" shape where every
development step on the left (like Requirements) is directly mirrored by a testing step on the right (like
Acceptance Testing).
• Agile: The modern favorite. Instead of one long cycle, Agile breaks the project into tiny, repeating loops
called iterations. It is fast, adaptive, and focuses on quick delivery.
To understand testing, we must agree on the vocabulary. There are subtle but vital distinctions in the terms we use.
• Validation: "Are we building the right product?" (Does this software actually solve the user's problem?)
1
• Verification: "Are we building the product right?" (Does the software match the technical requirements we
wrote down?)
• Note: Most technical testing focuses on Verification.
When we say something "went wrong," we break it down into a chain reaction:
1. Fault: The static defect in the code (e.g., a typo or wrong logic). It lives in the file, waiting.
2. Error: The incorrect internal state that happens when the code runs the Fault.
3. Failure: The external crash or wrong output that the user actually sees.
How a tester thinks determines their effectiveness. We measure this in Maturity Levels:
• Black-Box Testing: Imagine the software is a sealed black box. You don't care how the gears turn inside; you
only care that if you put "A" in, you get "B" out. (Used in System Testing).
• White-Box Testing: You tear the box open and inspect the gears. You look at the internal code structure. (Used
in Unit Testing).
1. Unit Testing: Checking the smallest individual bricks (functions/methods). Done by developers.
2. Integration Testing: Checking if the bricks hold together when cemented into a wall (modules).
3. System Testing: Checking the entire house (the full application).
4. Acceptance Testing: The homeowner walks through to see if they want to buy it (Customer validation).
Maintenance Testing
• Regression Testing: This is done after you modify or fix code. The idea is to ensure that your new fix didn't
break something else that was previously working. It is crucial during the maintenance phase.
Manual testing is slow and prone to human error. To keep up with modern speed, we use Test Automation—writing
code to test our code.
A computer needs precise instructions to run a test. We structure every test case with four parts:
1. Prefix Values (Setup): Getting the software ready. (e.g., "Open the calculator app").
2. Test Case Values: The actual input. (e.g., "Type 2 + 2").
3. Verification Values: The expected result. (e.g., "Expect 4").
4. Postfix Values (Teardown): Cleaning up. (e.g., "Close the app").
Meet JUnit
2
JUnit is the standard tool (framework) for automating Java tests. It acts as a referee for your code.
By combining these tools, we ensure that our software is robust, reliable, and ready for the real world.
Week 2
To rigorously test software, we often need to draw a map of it. In computer science, this map is called a Graph.
• $V$ (Vertices/Nodes): These are the locations. In software, they might represent statements, methods, or
states.
• $E$ (Edges): These are the roads connecting the locations. They represent the flow of control or data.
• Directed vs. Undirected: On a one-way street (Directed Graph), order matters ($u \to v$). On a two-way street
(Undirected Graph), the connection is mutual.
1. Adjacency Matrix: A giant grid (2D array). Good for "dense" cities with many roads, but wastes memory if
the map is empty.
2. Adjacency Lists: A list for each location showing where you can go next. This is memory-efficient for "sparse"
maps and is the standard for most testing tools.
Once we have a map, we need algorithms to explore it. These are essential for determining Reachability—can we
actually get from the start of the program to that specific line of code?
Imagine dropping a stone in a pond. The ripples expand outward in perfect circles. This is BFS.
Imagine exploring a maze. You run down a path until you hit a dead end, then you backtrack to the last intersection
and try a different path. This is DFS.
3
• Time Stamps: It records when a node is first seen (Discovery time, d) and when we are done with it (Finish
time, f).
• Parenthesis Theorem: The lifespan of a node in DFS is perfectly nested. If Node u discovers Node v, then v's
entire processing time happens inside u's time.
Now that we have the map, how much of it do we need to drive on to say we are "done" testing? These rules are
called Coverage Criteria.
Before we start, we define Test Requirements (TR). Think of these as the specific "To-Do List" items generated by
the rules below. For example, if the rule is "Node Coverage," the TR is the set of all nodes $\{1, 2, 3...\}$. If you visit
all items in the TR set, you have satisfied the criteria.
The Basics
• Node Coverage: Your test cases must visit every single city (statement).
• Edge Coverage: Your test cases must drive on every single road (branch). This is slightly stronger than Node
Coverage.
• Edge-Pair Coverage: You must drive every combination of two consecutive roads.
Ideally, we would want Complete Path Coverage (driving every possible route). But if your map has a Loop, the
number of paths is infinite. You can drive around the loop 1 time, 2 times, 500 times... you can never test them all.
• Simple Path: A path with no internal loops (you don't cross your own track).
• Prime Path: A Simple Path that is as long as possible—you can't extend it any further without repeating a
node.
• The Strategy: By testing all Prime Paths, we visit all nodes and edges, and we test loops "just enough"
(skipping them, entering them, and iterating them) without getting stuck in infinity.
• Simple Round Trip: Keeps at least one loop path for each reachable node that starts/ends a loop.
• Complete Round Trip: Keeps all loop paths.
Sometimes, the rules say "Drive this Prime Path," but the map logic says "You can't turn left here." This is an Infeasible
Test Requirement. To handle this, we allow flexibility:
• Sidetrips: You can leave the required path briefly (like stopping for gas) and come back to the same spot, as
long as you eventually finish the tour.
• Detours: You can leave the path and rejoin it at a later point (skipping a section).
• Best Effort: The goal is to satisfy as many requirements as possible without breaking the laws of physics (or
logic).
Week 3
If testing is the art of navigation, we first need a good map of the actual code. We call this map a Control Flow Graph
(CFG). It translates messy source code into a clean, traversable structure.
To build a CFG, we don't map every single semicolon. We group code into Basic Blocks:
• Basic Block: A chunk of code with one entry and one exit. If the first line runs, all the lines inside run. No
branching allowed inside a block.
• The Nodes: Each Basic Block becomes a single Node in our graph.
• The Edges: These represent the jumps—where the code could go next (e.g., the 'True' and 'False' branches of
an ifstatement).
• If/Else: Splits the path into two branches that usually rejoin later.
• Loops (For/While): Create circles (cycles) in the graph. We often add "dummy nodes" to cleanly represent the
loop's start or end conditions.
• Switch-Case: A hub with many spokes. Watch out for cases without break statements—they "fall through" to
the next case, creating extra edges!
• Exceptions: Hidden wormholes. A try-catch block creates invisible edges from the try block directly to
the catch block (and finally block), bypassing normal logic.
So far, we've only looked at control (which line runs next). Now, we look at data (what values are we carrying?). This
is Data Flow Analysis. We trace the life of a variable from its birth to its usage.
We can create test rules based specifically on these data journeys. These are Data Flow Coverage Criteria.
Just like with structural coverage, sometimes a specific data path is impossible to execute (infeasible). We use Best
Effort Touring, allowing for "def-clear side trips" (wandering off path but not changing the variable) to make the test
possible.
We now have many ways to test code. How do they compare? Some criteria are stricter than others. If you satisfy a
strict one, you automatically satisfy the weaker ones. This is called Subsumption.
Key Takeaway: Prime Path Coverage is generally superior to All-du-Paths Coverage because every simple data path
is also a simple structural path. If you test all Prime Paths, you have likely tested all data paths too.
Week 4
After we have tested individual pieces of code (Unit Testing), we must stick them together. This phase is Integration
Testing. Its goal is not to check the logic inside the modules, but to check the Interfaces—the connections between
them.
The Interfaces
Why do they fail? Interfaces are fragile. You might pass parameters in the wrong order, assume the wrong data type, or
create "Race Conditions" where two modules fight over the same memory at the same time.
You often can't test a module in isolation because it relies on other missing parts. We use fake code to fill the gaps:
• Stubs: Fake "Callees." If Module A calls Module B (which isn't built yet), we replace B with a Stub that just
returns a simple "OK."
• Drivers: Fake "Callers." If we want to test Module B, but the main program isn't built, we write a Driver script
that just calls B to see if it works.
Integration Strategies
• Top-Down: Start with the main menu and work down. Uses Stubs for lower levels.
• Bottom-Up: Start with the utility functions and work up. Uses Drivers to trigger them.
• Sandwich: Do both at once (meet in the middle).
• Big Bang: Wait until everything is finished, glue it all together, and pray. (Not recommended).
• Last-def: The last place a variable is defined in the Caller before it is handed off.
• First-use: The first place the variable is used in the Callee.
• Coupling Coverage: We want to ensure that every Last-def reaches every First-use across the interface.
Sometimes, the logic isn't about variables, but about Order (Sequencing Constraints).
• Example: You must open() a file before you write() to it, and you must open() it before you close() it.
• Violating these constraints creates bugs. We test this by generating paths that specifically check if these rules
are obeyed.
Before modern graph theory took over, testers used "Classical" terms. They map directly to what we've learned:
7
Cyclomatic Complexity
$$M = E - N + 2P$$
This number tells you the number of Linearly Independent Paths through the code. Ideally, if your complexity is 5,
you need at least 5 test cases to cover the "Basis Paths."
Week 5
Software decisions are ruled by Logic. Every if statement is a gatekeeper that decides where the program goes next.
To test effectively, we must understand the language of these gates: Propositional Logic.
• Propositions: Statements that are strictly True or False. (e.g., "The user is logged in," or "x > 5").
• Connectives: The glue that holds propositions together:
o $\land$ (AND): Both must be true.
o $\lor$ (OR): At least one must be true.
o $\neg$ (NOT): The opposite.
o $\rightarrow$ (IMPLIES): If A, then B.
o $\leftrightarrow$ (IFF): A if and only if B (Equivalent).
• Truth Tables: The ultimate map of logic. By listing every possible combination of True/False inputs, we see
exactly how a logic formula behaves.
• Satisfiability (SAT): Is there any set of inputs that makes this formula True?
• Validity: Is the formula always True, no matter the inputs? (A Tautology).
• Challenge: For complex software with hundreds of variables, finding these "satisfying assignments" (Test
Cases) is a hard problem (NP-complete). We often rely on tools called SAT Solvers to help us.
When testing, we don't just want to know if the result is True. We want to know if a specific clause is working.
• Predicate Coverage (PC): Run tests where the whole predicate evaluates to True, and where it evaluates to
False. (Weak—misses internal logic).
• Clause Coverage (CC): Run tests where every individual clause is True at least once, and False at least once.
(Weak—doesn't guarantee the predicate result flips).
• Combinatorial Coverage (CoC): Test every single row of the Truth Table ($2^n$ combinations). (Thorough,
but impossible for large formulas).
We want to check if the Major Clause actually controls the outcome. We force the Major Clause to determine the
predicate.
• General ACC (GACC): The Major Clause determines the predicate, but the Minor Clauses can change values
freely between tests.
• Correlated ACC (CACC): The Major Clause determines the predicate, and we ensure the full predicate result
is True in one test and False in the other. (This is the industry standard!).
• Restricted ACC (RACC): The Major Clause determines the predicate, and the Minor Clauses must
stay exactly the same between tests. (Strict and often infeasible).
Sometimes we want to ensure a clause doesn't affect the outcome (e.g., a safety override switch shouldn't matter during
normal operation). This tests the negative case.
• Example: You want to test if (Temp > Threshold). You can't just set Temp directly. You have to call
functions (e.g., setWeather()) that indirectly change Temp.
• Thermostat Example: To test if the heater turns on, we must manipulate CurrentTemp, Threshold,
and Override settings via the API to force the logic gate ((Temp < Threshold) || Override) to behave the
way we want.
Week 6
Logic isn't just for code; it lives in Specifications and Finite State Machines (FSMs) too.
Requirements often come in English, but we must translate them into logic to test them rigorously.
• Pre-conditions: Conditions that must be true before a function runs (e.g., month >= 1 && month <= 12).
• CNF & DNF: We often convert these requirements into standard forms:
o Conjunctive Normal Form (CNF): ANDs of ORs (e.g., (A or B) and (C or D)).
9
o Disjunctive Normal Form (DNF): ORs of ANDs (e.g., (A and B) or (C and D)).
o Testing Strategy: For CNF, to test a clause, we make all other clauses in the AND group TRUE (so they
don't block us). For DNF, we make other OR groups FALSE.
• Example: A subway door FSM moves from "Closed" to "Open" only if Speed == 0 AND EmergencyStop ==
False.
• The Strategy: We treat each transition guard as a Predicate. We apply our coverage criteria (like CACC) to
these guards to ensure every condition that allows or blocks a state change is tested.
Sometimes, a logical predicate in the code is just too messy or "expensive" to test directly. We might try to rewrite the
code to make it simpler. This is Predicate Transformation.
The Idea
The Problem
• Verdict: Usually, it's better to test the original complex predicate than to mangle the code just to make testing
"easier."
We've talked about "Satisfiability"—finding inputs that make a formula True. For huge programs, humans can't do this.
We use SMT Solvers (Satisfiability Modulo Theories).
• SAT Solvers: Work with pure Boolean logic (True/False). They are fast but dumb about numbers.
• SMT Solvers: Are smarter. They understand "Theories" like Arithmetic, Arrays, and Strings.
o Example: A SAT solver sees x > 5 as just a variable A. An SMT solver understands that if x=6, then x
> 5 is True.
10
Week 7
Between the extremes of simple "Testing" (checking one input) and "Program Proving" (mathematically proving it
works for all inputs), there lies a powerful technique called Symbolic Execution.
Normal execution runs a program with concrete data (e.g., x = 5). Symbolic execution runs the program
with symbols(e.g., x = α) representing arbitrary values. This allows us to reason about all possible inputs
simultaneously.
To make this work, we maintain two special data structures during execution:
1. x = a + b;
2. y = b + c;
3. z = x + y - b;
4. return z;
Handling Disadvantages
To overcome the limits of pure Symbolic Execution, modern tools use Concolic Testing. The name is a portmanteau
of Concrete and Symbolic.
The Strategy
11
We run the program Concretely (with real numbers) and Symbolically (with formulas) side-by-side.
DART is a specific, influential framework designed to automate unit testing using Concolic principles. It specifically
targets C programs.
1. Interface Extraction: DART automatically parses source code to find all external inputs (function arguments,
external variables). It handles complex C types like pointers, structs, and arrays.
2. Test Driver Generation: It automatically writes a "Test Driver"—a wrapper program that mimics the
environment and feeds random inputs to the function under test.
o Deep Initialization: random_init() recursively allocates memory for pointers and fills structs with
random data.
3. Directed Search: This is the dynamic analysis engine.
o It maintains a stack of branch decisions.
o It detects when random testing is "stuck" in a specific path.
o It uses symbolic constraints to calculate the exact inputs needed to flip a specific branch (e.g.,
"Find x such that f(x) == x + 10 triggers an error").
Robustness Features
• Completeness Flags: It tracks if the search is exhaustive (all_linear, all_locs_definite). If these flags
remain 1, DART can mathematically prove that all feasible paths were explored.
• External Functions: If the code calls a library function (e.g., sin()), DART treats it as a black box returning
a random value, ensuring the test doesn't crash just because it can't analyze the library code.
Week 8
We have spent weeks looking inside the code (White-Box). Now, we treat the software as a "Black Box." We don't
care how it works, only what it does. This is Functional Testing.
Core Concept
Most input domains are effectively infinite (e.g., "Enter any integer"). We cannot test every value. We must choose a
smart subset of inputs that are most likely to find bugs.
12
The input domain is too big, so we divide it into Partitions (or Equivalence Classes).
• The Logic: If inputs A and B are in the same partition, the program should treat them exactly the same way.
If Apasses, B will probably pass. If A fails, B will probably fail.
• The Strategy: Pick just one representative value from each partition to test.
• Partitions:
1. $[0, 10,000]$
2. $[10,001, 50,000]$
3. $[50,001, \infty)$
4. (Invalid) $< 0$
• Test Cases: We select one value from each: 5000, 25000, 100000, and -100. (4 tests instead of billions).
Programmers make mistakes at the "edges" of logic (e.g., writing > instead of >=). Boundary Value Analysis targets
these edges.
The Strategy
Instead of picking a random value inside the partition, we pick values on and just next to the boundary.
Two Approaches
If you have 3 parameters with 5 partitions each, testing all combinations is $5 \times 5 \times 5 = 125$ tests. That gets
expensive fast.
When business rules get complex (many conditions interacting), plain text requirements are confusing. Decision
Tables organize logic clearly.
13
Structure
Testing Strategy: Each column (Rule) becomes one Test Case. This ensures no combination of business rules is
overlooked.
Week 9
So far, we have tested software based on its logic (graphs, predicates) and its function (inputs/outputs). Now, we look
at its Syntax. Every software artifact—whether it's source code, a design model, or an input file—follows a strict
grammar. Syntax-based testing uses this grammar to generate test cases.
1. Lexical Level: How characters form words (tokens). Defined by Regular Expressions.
2. Phrase Level: How words form sentences (statements). Defined by Context-Free Grammars (BNF).
3. Context Level: How sentences make sense (type checking, scope). Defined by Context-Sensitive Grammars.
• Terminal Symbol Coverage (TSC): Have we used every keyword and operator (e.g., while, if, +) at least
once?
• Production Coverage (PC): Have we used every rule in the grammar (every production) at least once?
• Derivation Coverage (DC): Have we generated every possible string the grammar can produce? (Usually
impossible as it's infinite).
The most powerful application of syntax-based testing is Mutation Testing. Instead of using the grammar to
generate valid strings, we use it to generate invalid but "almost correct" strings to test our test suite.
Types of Mutants
14
• Trivial: Killed by almost any test case.
• Equivalent: Functionally identical to the original program. Impossible to kill.
o Example: Changing for (int i=0; i<10; i++) to for (int i=0; i!=10; i++).
• Dead: A valid mutant that was successfully killed by a test case.
• Strong Mutation: The fault must propagate all the way to the final output (the user sees the error).
• Weak Mutation: The internal state must be incorrect immediately after the mutated statement (Infection), but
we don't require it to propagate to the final output. This is easier to check but less rigorous.
We generate mutants using specific rules called Mutation Operators. These are designed to mimic common
programmer errors.
1. IPVR (Integration Parameter Variable Replacement): Replace a parameter in a call with another variable
(e.g., sum(a, b) $\to$ sum(a, x)).
2. IUOI (Integration Unary Operator Insertion): Add operators to arguments (e.g., sum(a, b) $\to$ sum(a,
-b)).
3. IPEX (Integration Parameter Exchange): Swap arguments (e.g., max(a, b) $\to$ max(b, a)).
4. IMCD (Integration Method Call Deletion): Delete the method call entirely.
Subsumption
The Cost
The main drawback of mutation testing is cost. A large program can generate thousands of mutants. Running the full
test suite against every single mutant is computationally expensive.
Week 10
Object-oriented (OO) software introduces specific testing challenges because it shifts complexity from the algorithms
to the connections between components.
1. Abstraction: Classes hide data. We need to test the interface, not just the internals.
2. Inheritance: Classes reuse code from parents. Testing a parent class doesn't guarantee the child class works
(because of overriding).
3. Polymorphism: A variable declared as type Animal might actually hold a Dog or Cat at runtime. We must
test allpossible dynamic bindings.
Testing isn't just "Unit vs. System" anymore. For classes, we have levels:
Visualizing execution flow in OO systems is hard. The execution can "bounce" up and down the inheritance hierarchy.
The Yo-Yo Graph helps us see this.
• Nodes: Methods (New, Inherited, and Overridden) for each class in the hierarchy.
• Edges: Method calls.
• The "Yo-Yo" Effect:
o You call d() in the Child.
o Child inherits d() from Parent. (Bounce Up).
o Parent's d() calls print().
o Child overrides print(). (Bounce Down).
o Execution jumps between levels, making it hard to track data flow anomalies.
OO features introduce specific types of bugs (faults) that procedural code doesn't have.
• Scenario: A child class overrides a method (e.g., calc()) but fails to define a variable (e.g., total) that the
parent's version defined.
• Result: Other methods expecting total to be set will fail when called on the child object.
16
2. State Definition Inconsistency (SDIH)
• Scenario: A child class introduces a local variable with the same name as an inherited variable (Variable
Shadowing/Hiding).
• Result: The child method accidentally updates the local version, leaving the object's state (the inherited variable)
undefined or stale.
• Scenario: A child class overrides a method and tries to access a variable that is private in the parent.
• Result: This is usually a compile-time error, but can be subtle if scope changes during evolution.
• Scenario: An object is used as a Base type, then cast to a Child type, then back.
• Result: If the Child relies on state that the Base methods don't maintain, the object becomes corrupt.
Testing how classes talk to each other (Coupling) is trickier with Polymorphism.
Coupling Sequence
If $o$ is polymorphic (e.g., declared as Shape but could be Circle or Square), we don't know which version
of $m$ and $n$will run until runtime.
• Polymorphic Call Set: The set of all possible methods that could execute for a given call.
OO Coverage Criteria
1. All-Coupling-Sequences (ACS): Test every coupling sequence ($m$ then $n$) at least once.
2. All-Poly-Classes (APC): Test the sequence for every possible type the object can bind to (e.g., test
with Circle AND Square).
3. All-Coupling-Defs-Uses (ACDU): Ensure every definition of a coupling variable reaches every use.
4. All-Poly-Coupling-Defs-and-Uses (APDU): The strongest. For every polymorphic type, ensure every def
reaches every use.
17
Week 11
Testing web apps is unique because of their architecture. They are distributed (Client vs. Server), stateless (HTTP), and
heterogeneous (HTML, JS, Java, PHP, Databases mixed together).
Key Characteristics
Levels of Testing
• Static Testing: Checking HTML for dead links and validation errors.
• Dynamic Client-Side: Testing the browser interface and inputs.
• Dynamic Server-Side: Testing the backend logic (Servlets, PHP, etc.).
The browser is the first line of defense, often validating inputs (e.g., "Age must be > 18"). But a malicious user can
bypass this. Bypass Testing checks if the server is robust enough to handle invalid inputs even if the client tries to stop
them.
How it Works
• Goal: The server should gracefully reject the data, not crash or get corrupted.
1. Capture: Log every request a real user makes (URL, parameters, clicks).
2. Replay: Use a tool to re-send these requests to the server to see if it still behaves correctly.
• Benefit: This creates realistic test suites based on actual usage patterns.
Testing the server is white-box testing. But Control Flow Graphs (CFGs) are too low-level for web pages. We
use Atomic Sections instead.
An Atomic Section is a chunk of HTML/Code that is either all sent to the client or none of it is. It's the smallest unit
of web output.
18
Component Interaction Model (CIM)
Week 12
Once you fix a bug or add a new feature, how do you know you haven't broken something else? Regression Testing is
the process of re-testing the software to ensure that changes haven't introduced new errors.
The Process
1. Select: You don't always have to run every test. You select a subset of tests that are relevant to the changes
made.
2. Test Modified Code: Run the tests on the new version ($P'$).
3. Create New Tests: If the new feature ($P'$) has new logic, write new tests for it.
4. Maintain: Update the old test suite. Remove obsolete tests (e.g., if a feature was deleted) and add the new ones.
Running all tests ("Retest-All") is safe but slow. We use smarter techniques:
• Minimization: Select the smallest set of tests that cover the modified code. (Fast, but risky).
• Data-Flow Techniques: Select tests that exercise "def-use pairs" that were modified.
• Safe Techniques: Guarantee that you select every test that could possibly reveal a fault in the modified code
(e.g., selecting all tests that reach the modified statement).
Traditionally, we code first and test later. Test Driven Development (TDD) flips this.
1. Red: Write a test case for a small piece of functionality before there is any code. It will fail (compile error or
assertion error).
2. Green: Write just enough code to make that specific test pass. Don't worry about quality yet.
3. Refactor: Clean up the code. Remove duplication, improve structure. Ensure the test still passes.
19
• Pros: You build a high-coverage test suite automatically. Debugging is minimal because you catch errors
instantly.
• Cons: It feels slow at first. It requires discipline. It is hard to apply to legacy code or complex GUIs.
Functional testing checks what the system does. Non-Functional Testing checks how well it does it.
The "-ilities"
Categories of Metrics
20