Basis Path Testing
A white-box testing technique proposed by McCabe. Ensures every statement executes at least
once. Uses program logic structure to derive a basis set of independent [Link] both
true and false outcomes of conditions are tested
1. Flow Graph Notation
A flow graph represents program control structure.
Nodes → one or more non-branching statements.
Edges (links) → control flow between nodes.
Predicate nodes → decision points (if, while) with two or more outgoing edges.
Regions → areas bounded by nodes and edges (outside area also counts).
Compound conditions increase complexity (each condition may form a separate node).
2. Independent Program Paths
An independent path introduces at least one new edge.
The collection of such paths is called the basis set.
Basis set ensures complete statement coverage.
The basis set is not unique.
Number of independent paths = Cyclomatic Complexity.
Cyclomatic Complexity (V(G))
Measures logical complexity and determines required number of test cases.
Can be calculated in three ways:
1. V(G) = Number of regions
1. 2. V(G) = E − N + 2 E = edges N = nodes
2. V(G) = P + 1
P = number of predicate nodes. Provides upper bound on number of test cases. Higher
V(G) → higher complexity → more testing needed.
3 .Deriving Test Cases
Steps:
1. Draw flow graph from code/design.
2. Compute cyclomatic complexity V(G).
3. Identify V(G) independent paths (basis set).
4. Design test cases to force execution of each path.
After testing all basis paths → all statements executed at least once
4. Graph Matrices
Square matrix representing flow graph connections.
Rows & columns = nodes.
Entries represent edges between nodes.
Can assign weights (e.g., probability, time, memory).
Helps automate and analyze basis path testing.
Core Idea Cyclomatic complexity determines how many independent paths must be tested to
achieve full logical coverage of a program.
2. Control Structure Testing
Extends basis path testing.
Strengthens white-box testing. Focuses on:
Conditions
Data usage
Loops
1. Condition Testing
Focuses on testing logical conditions in a module.
Tests both simple and compound conditions.
Simple condition: Boolean variable or relational expression (<, ≤, =, ≠, >, ≥) with possible
NOT (¬).
Compound condition: Combined using OR (|), AND (&), NOT (¬).
Conditions may include Boolean variables, relational operators, arithmetic expressions,
parentheses.
Detects errors in:
Boolean operators
Relational operators
Parentheses
Arithmetic expressions
2. Data Flow Testing
Based on definition and use of variables.
DEF(S): variables defined in statement S.
USE(S): variables used in statement S.
Definition-Use (DU) chain: [X, S, S′].
DU testing strategy: cover every DU chain at least once.
Helps detect improper variable usage and data handling errors.
Does not always guarantee full branch coverage.
3. Loop Testing
Focuses on validating loop constructs.
Types of loops:
1. Simple
2. Nested
3. Concatenated
4. Unstructured
[Link] Loop Tests:
0 iterations (skip loop)
2 iterations
m iterations (m < n)
n−1, n, n+1 iterations
1 iteration
2,Nested Loops:
Start with innermost loop.
Keep outer loops minimal.
Move outward step-by-step.
[Link] Loops:
If independent → test as simple loops.
If dependent → treat as nested loops.
[Link] Loops:
Should be redesigned.
3. Black-Box Testing
Black-box testing, or **behavioral testing**, evaluates software based on its functional
requirements without examining internal code. Testers derive input conditions to exercise all
specified functions and assess outputs from a user’s perspective. It complements white-box
testing by targeting different error classes rather than replacing structural analysis.
This approach seeks errors in:
Incorrect or missing functions
Interface faults
Data structure or external database access errors
Behavioral or performance issues
Initialization and termination faults
Typically applied in later testing stages, black-box testing ignores internal control structures and
concentrates on the information domain—inputs, outputs, and externally observable behavior.
Test design addresses functional validity, system performance, effective input classes,
sensitivity to specific values, data boundaries, data volume and rate tolerance, and the effects of
input combinations.
Effective test cases should (1) significantly reduce the number of additional cases needed for
adequate coverage and (2) reveal classes of errors rather than isolated defects tied to single
inputs.
1. Graph-Based Testing Methods
Graph-based testing begins by identifying software *objects* (broadly defined to include data
objects, modules, and object-oriented elements) and the relationships among them. A graph is
constructed with:
**Nodes** representing objects
**Links** representing relationships
**Node weights** describing object properties (e.g., states or values)
**Link weights** describing relationship characteristics (e.g., timing constraints)
Links may be directed, bidirectional (symmetric), or parallel (multiple relationships between
nodes). Test cases are derived by traversing the graph to ensure each object and relationship is
exercised, thereby uncovering interaction errors.
Behavioral methods using graphs include:
*Transaction flow modeling: Nodes represent transaction steps; links show logical
progression.
*Finite state modeling: Nodes represent user-observable states; links represent state
transitions.
*Data flow modeling: Nodes are data objects; links denote data transformations.
*Timing modeling: Nodes are program elements; links represent execution sequence
with time constraints.
Each method emphasizes verification of relationships and system behavior.
2. Equivalence Partitioning
Equivalence partitioning divides the input domain into **equivalence classes**, each
representing a set of inputs processed similarly by the system. A single well-chosen test case
can represent an entire class, increasing efficiency while preserving coverage.
Input conditions may involve numeric values, ranges, sets, or Boolean states. Equivalence
classes are defined as follows:
1. For a **range**, define one valid and two invalid classes.
2. For a **specific value**, define one valid and two invalid classes.
3. For a **set member**, define one valid and one invalid class.
4. For a **Boolean condition**, define one valid and one invalid class.
Test cases should exercise as many attributes of each class as possible to detect general
processing errors.
3. Boundary Value Analysis
Boundary Value Analysis (BVA) is based on the observation that errors frequently occur at input
boundaries rather than mid-range values. It complements equivalence partitioning by selecting
test cases at class edges and considering both input and output domains.
Guidelines include:
1. For a range bounded by *a* and *b*, test *a*, *b*, and values just below and above
each.
2. For specified numeric limits, test minimum and maximum values, including adjacent
values.
3. Apply similar boundary tests to output conditions (e.g., maximum report entries).
4. Test boundaries of internal data structures (e.g., maximum table size).
Systematic boundary testing increases the likelihood of detecting edge-related defects.
4. Orthogonal Array Testing
When input parameters are limited but combinations are numerous, exhaustive testing may be
impractical. Orthogonal array testing provides broad coverage with fewer test cases and is
particularly effective for detecting **region faults** caused by faulty logic in parameter
[Link] “one input at a time” strategies—which detect only single-parameter (single
mode) faults—orthogonal arrays distribute test cases uniformly across the input domain. For
example, an L9 orthogonal array balances combinations of multiple parameters, enabling
detection of:
**Single mode faults** (consistent errors tied to one parameter value)
**Double mode faults** (pairwise interaction faults)
Some **multimode faults**
This approach achieves strong interaction coverage with reduced testing effort compared to
exhaustive permutation testing.
[Link] Maintenance
Software maintenance is a continuous activity carried out throughout the software life cycle to
ensure the system functions correctly, efficiently, and securely while meeting user requirements.
Activities include bug fixing, feature enhancement, performance improvement, and compatibility
updates. Given its complexity and cost—particularly in large systems—maintenance
considerations must be integrated during planning and development. An effective maintenance
process requires structured testing and validation, version control, stakeholder communication,
and routine activities such as backups and defect resolution. Proper planning reduces risks and
supports long-term system stability.
[Link] Key Aspects of Software Maintenance
Key activities in software maintenance include:
Bug Fixing: Identification and correction of software defects.
Enhancements: Addition or refinement of features to address evolving user needs.
Performance Optimization: Improvement of system speed, efficiency, and reliability.
Porting and Migration: Adaptation to new hardware or software platforms.
Re-Engineering: Architectural and design improvements to enhance maintainability and
scalability.
Documentation: Creation and updating of user manuals, technical specifications, and
design records.
These aspects collectively ensure sustained functionality and adaptability.
[Link] Types of Software Maintenance
Software maintenance is commonly classified as:
Corrective Maintenance: Resolution of post-deployment defects.
Patching: Urgent corrective fixes, often implemented without full impact analysis and
potentially introducing future issues.
Adaptive Maintenance: Modifications to accommodate environmental changes such as
new platforms, policies, or business rules.
Perfective Maintenance: Enhancements to performance, functionality, or system
structure based on user feedback.
Preventive Maintenance: Proactive measures—such as optimization, documentation
updates, system reviews, and backups—to prevent future faults.
Maintenance may also be categorized as **proactive** (preventive) or **reactive** (problem-
driven). It may be conducted by original developers, in-house teams, or third-party providers,
and includes both scheduled tasks (e.g., updates, backups) and unscheduled responses (e.g.,
crashes, security incidents). Version or platform upgrades are also integral to maintaining
compatibility with evolving technologies. Effective stakeholder communication supports
successful maintenance outcomes.
4. Need for Maintenance
Software maintenance is necessary to:
Correct faults
Improve system design
Implement enhancements
Enable system integration
Adapt to new hardware, software, and telecommunications environments
Migrate legacy systems
Retire obsolete software
Accommodate user requirement changes
Improve execution speed
Without maintenance, software degrades in functionality, efficiency, and relevance.
[Link] in Software Maintenance
Maintenance presents technical, organizational, and financial challenges. Software systems
often remain operational for 10–15 years or longer, making maintenance an ongoing and
potentially costly process.
Major challenges include:
Incompatibility of legacy systems with modern hardware
Undocumented changes leading to conflicts
Structural degradation from poorly managed modifications
Lack of code comments and documentation
Outdated technologies requiring specialized expertise
System complexity
Evolving user requirements
Interoperability constraints
Insufficient test coverage
Shortage of skilled personnel
High maintenance costs
Addressing these challenges requires a structured maintenance process incorporating testing,
validation, version control, documentation, regular backups, and qualified personnel.
6. Categories of Software Maintenance
Software maintenance is broadly categorized into:
Corrective Maintenance: Correction of defects and performance issues.
Adaptive Maintenance: Adjustments for new platforms, operating systems, or interfacing
requirements.
Perfective Maintenance: Functional modifications or feature additions based on
customer demands.
Preventive Maintenance: Modifications intended to avert potential future problems.
These categories collectively ensure continued system reliability, adaptability, and performance.
[Link]-Level Design
Component-level design takes place after the first iteration of architectural design is finished. At
this point, the overall structure of the software—its data organization and program structure—
has already been defined. However, the design is still at a high level of abstraction. Component-
level design moves the system closer to actual code by defining the internal details of each
component. This transition from high-level design to low-level operational code can be difficult.
Because the abstraction level changes significantly, subtle errors may be introduced. These
errors are often hard to detect and fix later. As emphasized by Edsgar Dijkstra, reliable software
comes not from debugging after errors occur, but from designing carefully so that most bugs are
never introduced in the first place. Therefore, component-level design must follow strong design
principles to reduce error creation during translation into code.
[Link] Is It?
During architectural design, the system is divided into components. However, their internal data
structures, algorithms, and detailed processing logic are not yet specified at a code-like level.
Component-level design fills this gap.
It defines for each component:
Internal data structures
Algorithms and processing logic
Interface characteristics
Communication mechanisms with other components
In short, it transforms abstract architectural elements into detailed, implementable designs.
2. Who Does It?
A software engineer performs component-level design, using earlier analysis and architectural
models as guidance.
[Link] Is It Important?
Component-level design allows engineers to verify whether the software will work before
building it. It provides a detailed representation that can be reviewed for:
Correctness
Consistency with data, architectural, and interface designs
Proper functioning of algorithms and data structures
Without this step, coding may proceed with hidden design flaws that are costly to fix later.
4. What Are the Steps?
Component-level design builds on:
Data design
Architectural design
Interface design
Each component’s class definition or processing description is translated into a detailed design.
This may be represented using UML diagrams, structured text, or tabular forms. Procedural
logic is expressed using structured programming constructs.
In some cases, reusable components can be adopted instead of building new ones, reducing
effort and risk.
5. What Is the Work Product?
The main output is a detailed design specification for each component. This can be represented
graphically, textually, or in tabular form.
6. How Do I Ensure That I’ve Done It Right?
A formal design review is conducted. During review, engineers verify that:
Data structures are correct
Interfaces are properly defined
Processing logic is accurate
Logical conditions produce the intended transformations
This ensures consistency with earlier design stages.
1. What Is a Component?
A component is a modular building block of software. According to the Object Management
Group (OMG) Unified Modeling Language specification, a component is a modular, deployable,
and replaceable part of a system that encapsulates implementation and exposes interfaces.
Components exist within the software architecture and must communicate with other
components and possibly with external systems. The meaning of “component” varies depending
on perspective.
2. An Object-Oriented View
From an object-oriented perspective, a component consists of one or more collaborating
classes. Each class is fully defined with:
Attributes (data)
Operations (methods)
Interfaces for communication
Design elaboration begins from analysis classes. For example, in a print shop system, an
analysis class called PrintJob is refined into a design component. Its internal attributes become
more detailed, and its operations (such as computing job cost or initiating production) are
expanded into fully defined methods.
Interfaces such as computeJob or initiateJob enable collaboration with other components like
pricing or job queue systems. During elaboration:
Data structures for attributes are specified
Algorithms for operations are designed
Communication mechanisms between objects are defined
This process is iterative: analysis and design may be revisited to refine details further
3. The Traditional View
In traditional (non–object-oriented) software engineering, a component—often called a module
—is a functional unit containing:
Processing logic
Internal data structures
An interface for input and output
Modules may serve three roles:
Control components (coordinate system flow)
Problem-domain components (implement customer-required functions)
Infrastructure components (support system operations)
These modules are typically derived from data flow diagrams and arranged in a hierarchy
(structure chart). During component-level design:
Interfaces are explicitly defined
Internal data structures are detailed
Algorithms are refined using stepwise refinement
Behavior may be modeled using state diagrams
Design continues until enough detail exists to guide coding.
4. A Process-Related View
Both object-oriented and traditional views assume components are designed from scratch.
However, modern software engineering emphasizes reuse through component-based software
engineering (CBSE). Instead of building everything new, engineers select proven components
or design patterns from [Link] standards support this reuse by enabling
communication between components across systems or networks. Examples include:
CORBA by OMG
Microsoft COM
Microsoft .NET
JavaBeans
The availability of such standards greatly influences the success of component-based
development.
5. Designing Class-Based Components
Component-level design in object-oriented systems elaborates problem-domain and
infrastructure classes from the requirements and architectural models. Detailed attributes,
operations, and interfaces must be defined before construction.
Basic Design Principles Four key principles guide good component-level design:
*Open-Closed Principle (OCP): A component should be open for extension but closed
for modification. This means new functionality should be added without altering existing
code. Abstractions (like interfaces) help achieve this.
*Liskov Substitution Principle (LSP): Proposed by Barbara Liskov, this principle states
that derived classes must be usable wherever base classes are expected, without
breaking correctness. Derived classes must honor the base class “contract.”
*Dependency Inversion Principle (DIP): Components should depend on abstractions,
not concrete implementations. This reduces rigidity and improves extensibility.
*Interface Segregation Principle (ISP): It is better to create multiple client-specific
interfaces rather than one general-purpose interface. Clients should depend only on
methods they actually use.
[Link] Principles
When organizing components into packages, additional principles apply:
Release Reuse Equivalency Principle (REP) – Reusable components should be
released and version-controlled as a group.
Common Closure Principle (CCP) – Classes that change together should be packaged
together.
Common Reuse Principle (CRP) – Classes that are not reused together should not be
packaged together.
These principles improve change management and reduce unnecessary testing during updates.
7. Cohesion
Cohesion refers to how focused a component is on a single task. Higher cohesion makes
components easier to implement, test, and maintain.
Types of cohesion (from stronger to weaker include):
Functional cohesion – Performs one well-defined task
Layer cohesion – Higher layers use lower layers, but not vice versa
Communicational cohesion – Operations operate on the same data
Designers should aim for high cohesion whenever possible.
[Link]
Coupling measures how strongly components depend on one another. High coupling increases
complexity and maintenance difficulty. Designers aim to reduce coupling while maintaining
necessary communication.
Common types include:
Content coupling – One component modifies another’s internal data
Common coupling – Use of global variables
Control coupling – Passing control flags between components
Stamp coupling – Using complex objects as parameters
Data coupling – Passing long lists of parameters
Routine call coupling – One operation calling another
Type use coupling – One component depends on another’s data type
External coupling – Dependence on infrastructure (OS, databases)
Although some coupling is unavoidable, minimizing it improves maintainability and
[Link], component-level design bridges architecture and code. It refines high-level
structures into precise, reviewable designs while applying principles that promote reliability,
extensibility, cohesion, and low coupling.
6. DESIGN CONCEPTS
A set of fundamental software design concepts has evolved throughout the history of software
engineering. Interest in specific concepts has varied over time, yet each has endured, providing
designers with a foundation for applying more sophisticated methods. These concepts help
answer critical questions:
How can software be partitioned into individual components?
How can function or data details be separated from conceptual representations?
What uniform criteria define the technical quality of a design?
1. Abstraction
Abstraction enables designers to manage complexity by emphasizing essential features while
suppressing unnecessary details. At high levels, solutions are described using problem-oriented
terminology; at lower levels, implementation details are progressively added until the solution is
executable.
Procedural abstraction: Encapsulates a sequence of instructions performing a specific
function while hiding the details (e.g., `open` a door).
Data abstraction: Groups related information into named objects (e.g., a `door` with
attributes such as type, dimensions, and swing direction). Procedural abstractions
operate on these data abstractions to simplify problem-solving.
2. Architecture
Software architecture defines a system’s structure and component interactions. It serves as a
blueprint guiding detailed design while ensuring quality, performance, and maintainability.
Structural properties: Define components (modules, objects) and their interactions.
Extra-functional properties: Specify performance, reliability, security, and other system
characteristics.
Families of systems: Enable reuse of architectural patterns across similar systems.
Architectural models may include structural, framework, dynamic, process, and functional
perspectives. Architectural Description Languages (ADLs) support these models. While debates
exist on architecture’s role relative to design, it remains central to coherent, maintainable
software.
3. Patterns
Design patterns capture proven solutions to recurring design problems within a context. They
indicate applicability, enable reuse, and guide development of similar yet distinct designs,
saving time and improving consistency.
4. Separation of Concerns
Separation of concerns divides complex problems into smaller, manageable units, each
addressing a distinct feature or behavior. This facilitates development, reduces errors, and
improves maintainability. Over-segmentation, however, can complicate integration. Concepts
such as modularity, functional independence, aspects, and refinement are all manifestations of
this principle.
5. Modularity
Modularity realizes separation of concerns by dividing software into named, independent
components (modules). Each module addresses specific functionality, making software more
comprehensible, testable, and maintainable.
Smaller modules reduce individual complexity, but increase integration effort.
An optimal number of modules, **M**, minimizes total development cost.
Proper modularization allows incremental development, efficient testing, and easier long-
term maintenance.
6. Information Hiding
Information hiding ensures modules encapsulate internal details, exposing only necessary
interfaces. This limits inter-module dependencies and reduces error propagation. Combined
with abstraction, it produces robust, maintainable modular systems.
7. Functional Independence
Functional independence emphasizes designing modules with focused responsibilities and
minimal interactions.
Cohesion: Degree to which a module addresses a single task.
Coupling: Degree of interdependence between modules.
High cohesion and low coupling facilitate development, testing, maintenance, and reuse.
8. Refinement
Refinement is a top-down design strategy where high-level abstractions are gradually
elaborated into detailed design or code. Abstraction suppresses unnecessary details;
refinement progressively reveals them, supporting structured, incremental development.
9. Aspects
Aspects represent crosscutting concerns—requirements affecting multiple modules (e.g.,
security, logging). Identifying aspects ensures consistent treatment during modularization and
refinement, preventing scattered or tangled implementations.
10. Refactoring
Refactoring reorganizes code or design to improve clarity, reduce complexity, and enhance
maintainability without changing behavior. It identifies redundancy, inefficiency, or poor structure
and corrects it, complementing iterative refinement and supporting long-term quality.
11. Object-Oriented Design Concepts
Object-oriented (OO) design organizes software around objects and classes, leveraging
encapsulation, inheritance, polymorphism, and message passing. OO design reinforces
modularity, abstraction, and functional independence, providing a framework for structured,
maintainable systems.
12. Design Classes
Design classes refine analysis classes from requirements, adding detail to enable
implementation. Five types:
User interface classes: Handle human-computer interaction, often via metaphors.
Business domain classes: Define attributes and services for problem domain elements.
Process classes: Implement lower-level processes supporting domain classes.
Persistent classes: Represent long-term data storage (e.g., databases).
System classes: Manage system-level operations and communication.
A **well-formed design class** should be:
1. Complete and sufficient: Contains all necessary attributes and methods.
2. Primitive: Methods perform single services without duplication.
3. Highly cohesive: Focused responsibilities with aligned attributes and methods.
4. Low coupling: Limited dependency on other classes, simplifying development, testing,
and maintenance.
Following these principles ensures design classes form a robust foundation for maintainable,
high-quality software.