본문으로 건너뛰기

Physical Hierarchy

This chapter explains how physical dependencies among components form a hierarchy that affects testability, maintainability, reuse, and development cost.

4.1 A Metaphor for Software Testing

Complex software systems should be tested at multiple levels, like complex physical products.

A well-designed system is tested:

  1. In isolation.
  2. Through partially integrated subsystems.
  3. As a fully integrated product.

Principle:
Low-level components should be tested directly rather than relying only on tests of the final system.

Component

Subsystem

Integrated System

Testing only at the highest-level interface makes it difficult to identify which lower-level component is responsible for a failure.

4.2 A Complex Subsystem

A subsystem can hide a large implementation behind a very small interface.

The chapter uses a point-to-point router as an example.

The router receives:

  • An enclosing region.
  • A set of obstructions.
  • A start point.
  • An end point.
  • A path width.

It determines whether a rectilinear path exists and may return a shortest path.

The important design point is:

Large Internal Implementation

Small Logical Interface

A component can therefore be easy to use while internally containing substantial complexity.

4.3 The Difficulty in Testing "Good" Interfaces

Good Interface:
A small, well-defined, easy-to-understand interface that hides implementation complexity.

Such interfaces are desirable for clients but can make high-level testing difficult.

Regression Testing

Regression Testing:
Comparing the output produced for a fixed input against previously established expected results to verify that behavior remains correct across versions.

Fixed Input

Run Program

Compare with Expected Result

Regression testing becomes difficult when:

  • Multiple correct outputs are possible.
  • Heuristic algorithms are used.
  • Results are not deterministic.
  • Asynchronous behavior produces different valid executions.

A high-level test can also miss defects in internal components when those defects merely degrade output quality rather than causing complete failure.

4.4 Design for Testability

Design for Testability (DFT):
Designing a system so that its internal functionality can be tested efficiently and directly.

The goal is to avoid relying exclusively on the top-level interface for testing.

Principle:
With respect to testing, a software class is analogous to a real-world instance.

If a class implementation is correct, all instances of that class share that implementation.

Principle:
Distributing testing throughout the design hierarchy can be more cost-effective than testing only from the highest-level interface.

System-Level Testing Only

Hard to isolate internal behavior

Hierarchical Testing

Directly test lower-level functionality

A good physical hierarchy allows internal components to be tested without adding test-only operations to the public interface of the final product.

4.5 Testing in Isolation

Isolation Testing:
Testing an individual component or subsystem independently of the rest of the system.

Principle:
Independent testing reduces part of the risk associated with software integration.

Principle:
Testing a component in isolation is an effective way to ensure reliability.

Isolation testing helps detect:

  • Boundary-condition errors.
  • Memory-management errors.
  • Low-level implementation bugs.
  • Portability problems.
  • Functionality not exercised by a particular client.

A reusable component should not depend on one specific application to exercise all of its behavior.

Big Bang Integration

Big Bang Approach:
Waiting until most or all components are complete before integrating and testing the system.

This makes it harder to distinguish:

  • Integration errors.
  • Specification errors.
  • Ordinary coding defects.

Testing components independently allows coding defects to be removed before integration.

Tests as Part of the Component

The testing effort can be grouped with the component itself.

Component
├── Source
├── Interface
├── Tests
└── Documentation

Well-written component tests also provide examples of correct usage.

Isolation testing should still be justified by cost and benefit.

Creating separate components solely for testing very small implementation details can increase physical complexity unnecessarily.

4.6 Acyclic Physical Dependencies

Directed Acyclic Graph (DAG):
A directed dependency graph containing no cycles.

An acyclic component dependency graph provides a natural testing order.

Example:

c3

c2

c1

Testing can proceed:

c1

c2

c3

Each component is tested after the components it depends on have already been verified.

Principle:
Acyclic physical dependencies make systems easier to test.

A cyclic dependency has the form:

c1 → c2
↑ ↓
└─────┘

In this case, neither component can be tested or reused independently of the other.

4.7 Level Numbers

Level Number:
A non-negative integer used to classify a component according to its physical dependencies.

Level numbers provide an objective testing order for acyclic dependency graphs.

4.7.1 The Origin of Level Numbers

The concept comes from levelized digital circuit simulation.

A node can be evaluated only after all nodes on which it depends have been evaluated.

Principle:
Every directed acyclic graph can be assigned unique level numbers; a graph containing cycles cannot.

Levelizable:
A physical dependency graph that can be assigned level numbers.

Acyclic Graph

Levelizable

Cyclic Graph

Not Levelizable

4.7.2 Using Level Numbers in Software

The book defines component levels as follows.

Level 0:
A component external to the current package.

Level 1:
A component with no local physical dependencies.

Level N:
A component that depends on a component at level N - 1, but on no component at a higher level.

Equivalent rule:

level(component)
=
1 + maximum level of local dependencies

External dependencies are treated as level 0.

Leaf Component:
A level-1 component that depends only on external or compiler-supplied components.

Leaf components are testable in isolation.

Level of a Component:
The length of the longest path from the component through the local dependency graph to external or compiler-supplied components.

Example:

Level 4: A

Level 3: B

Level 2: C

Level 1: D

Level 0: External Library

Principle:
In most real-world situations, large designs must be levelizable if they are to be tested effectively.

Level numbers help determine:

  • Testing order.
  • Reuse requirements.
  • Dependency depth.
  • Overall physical structure.

4.8 Hierarchical and Incremental Testing

Hierarchical Testing

Hierarchical Testing:
Testing individual components at each level of the physical hierarchy.

Principle:
Hierarchical testing requires a separate test driver for every component.

component_a
└── a.t.c

component_b
└── b.t.c

A separate test driver verifies that a component can operate using only the dependencies shown in its dependency graph.

This also helps detect:

  • Missing definitions.
  • Hidden dependencies.
  • Violations of physical design rules.
  • Non-levelizable structures.

Incremental Testing

Incremental Testing:
Testing only the functionality implemented directly within the component under test.

Lower-level components are presumed to have already been tested.

Lower-Level Component
↓ already verified

Current Component

Test only added functionality

Principle:
Testing only the functionality directly implemented within a component keeps test complexity proportional to the complexity of that component.

This avoids repeatedly testing functionality already verified at lower levels.

White-Box Testing

White-Box Testing:
Testing a component using knowledge of its internal implementation.

White-box testing is useful for:

  • Internal code coverage.
  • Logic errors.
  • Memory errors.
  • Implementation-specific behavior.

It verifies that the implementation works as intended.

Black-Box Testing

Black-Box Testing:
Testing a component solely from its specification and public behavior, without relying on implementation details.

It verifies that the component satisfies its requirements.

White-Box Testing
└── Implementation

Black-Box Testing
└── Specification

Both techniques are complementary.

White-box testing asks:

Did we implement it correctly?

Black-box testing asks:

Did we implement the correct behavior?

4.9 Testing a Complex Subsystem

A complex subsystem should be factored into a levelizable hierarchy of independently testable components.

High-Level Component

Intermediate Components

Low-Level Components

Lower-level components should provide:

  • Predictable behavior.
  • Well-defined interfaces.
  • Manageable functionality.
  • Independent tests.

The highest-level component should primarily coordinate lower-level functionality rather than contain the entire implementation itself.

This reduces the testing burden at the top-level interface.

Principle:
Hierarchical physical implementations of complex subsystems can be more reliable and less expensive to test than monolithic alternatives.

4.10 Testability versus Testing

Testability:
The property of a design that allows its behavior to be verified effectively.

Tested:
The state in which a product has demonstrated conformance to its specification.

Testability
└── Design Property

Testing
└── Development Activity

Tested
└── Verified State

Testability must be designed into a system before systematic testing becomes necessary.

Principle:
Thorough regression testing is expensive but essential; the appropriate time to create thorough regression tests depends on the stability of the subsystem.

Early in development, interfaces may still change frequently.

As components mature and interfaces stabilize, more thorough regression suites become cost-effective.

4.11 Cyclic Physical Dependencies

Cyclic Physical Dependency:
A dependency relationship in which a path leads from a component back to itself.

Example:

A DependsOn B
B DependsOn A

Principle:
Cyclic physical dependencies inhibit understanding, testing, and reuse.

Guideline:
Avoid cyclic physical dependencies among components.

When two components are mutually dependent, possible responses include:

  1. Repackage them to remove the cycle.
  2. Merge them into one component.
  3. Treat them as one physical unit.

The preferred solution is to eliminate the cycle when possible.

Closely related logical entities with intimate access needs may belong naturally in the same component.

Mutually Dependent Components

Repackage / Merge / Restructure

4.12 Cumulative Component Dependency

Cumulative Component Dependency (CCD):
The sum, over all components in a subsystem, of the number of components required to test each component incrementally.

For each component:

Component Dependency Count
=
Component itself
+
All components it DependsOn

Then:

CCD
=
Sum of all component dependency counts

CCD approximates the cumulative cost of incremental linking and testing.

Cyclic System

For a fully interdependent system with N components:

CCD ≈ N²

Every component may require all N components to test.

Balanced Tree-Like System

For a balanced binary dependency tree:

CCD = O(N log N)

The book gives:

CCD_balanced(N)
=
(N + 1) log2(N + 1) - N

for the theoretical balanced binary-tree case.

Principle:
Acyclic physical dependencies can dramatically reduce link-time costs associated with developing, maintaining, and testing large systems.

CCD Meaning

Lower CCD generally means:

  • Less physical coupling.
  • Lower incremental link cost.
  • Lower disk-space cost for test executables.
  • Better opportunities for isolated testing and reuse.

CCD is primarily useful for comparing alternative physical organizations of the same or similar subsystem.

4.13 Physical Design Quality

Physical design quality is strongly related to the amount and shape of physical coupling.

The book describes a continuum:

Cyclic

Vertical

Tree-Like

Horizontal

Cyclic Design

High coupling
Poor independent reuse
Poor testability
High CCD

Vertical Design

Vertical Design:
A levelizable hierarchy in which components form a long dependency chain.

A

B

C

D

A vertical design is acyclic but highly coupled.

For N components, the maximum CCD for an acyclic vertical chain is:

CCD = N(N + 1) / 2

Tree-Like Design

A tree-like design distributes dependencies across branches.

A
/ \
B C
/ \ / \
D E F G

It usually provides better:

  • Parallel development.
  • Incremental testing.
  • Reuse.
  • Maintenance.

Horizontal Design

Horizontal Design:
A subsystem whose components are independent of one another.

A B C D

A horizontal system has minimal coupling.

Each component can be:

  • Tested independently.
  • Reused independently.
  • Modified with minimal propagation.

Average Component Dependency

Average Component Dependency (ACD):
The ratio of CCD to the number of components.

ACD = CCD / N

ACD represents the average number of components involved when incrementally testing a component.

Normalized Cumulative Component Dependency

Normalized Cumulative Component Dependency (NCCD):
The ratio of a subsystem's CCD to the CCD of a theoretical balanced tree of the same size.

NCCD
=
CCD(subsystem)
/
CCD_balanced(N)

Interpretation:

NCCD < 1
└── More horizontal / loosely coupled

NCCD ≈ 1
└── Tree-like coupling

NCCD > 1
└── More vertical / tightly coupled

NCCD >> 1
└── Possible significant cyclic coupling

NCCD characterizes coupling but is not by itself a measure of overall design quality.

A design can achieve a lower NCCD simply by introducing unnecessary components or removing valid reuse.

Principle:
The primary purpose of CCD is to quantify changes in overall coupling caused by changes to an architecture.

Principle:
Minimizing CCD for a given set of components is a design goal.

The important objective is not a specific numerical CCD value, but keeping physical dependency lower than necessary.

4.14 Key Concepts

Physical Hierarchy

DependsOn Graph

Testing Strategy
├── Isolation Testing
├── Hierarchical Testing
├── Incremental Testing
├── White-Box Testing
└── Black-Box Testing

Acyclic Dependencies

DAG

Levelizable

Level Numbers

Ordered Testing

Levels
├── Level 0 = External Component
├── Level 1 = No Local Dependencies
└── Level N = 1 + Max Dependency Level

Cycles

Poor Testing
Poor Understanding
Poor Reuse

Physical Coupling Metrics
├── CCD
├── ACD
└── NCCD

Dependency Shapes
├── Cyclic
├── Vertical
├── Tree-Like
└── Horizontal

Design Goal

Acyclic
Levelizable
Low CCD
Independently Testable Components