Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Before changing legacy code, write characterization tests: small, repeatable tests that record what the system actually does today. Start at the narrowest observable boundary around the code you must change, break only the dependency that blocks control, and capture representative outputs and side effects. Then refactor in short, behavior-preserving loops. This gives you a safety net without pretending that every existing behavior is desirable.

What to test first in an untested legacy system

Do not begin by trying to test the whole application. Choose the specific function, class, workflow, or boundary that your refactoring will touch. A narrow target keeps setup, failures, and decisions understandable.

Use characterization tests as the initial safety net

A characterization test describes observed behavior rather than an ideal specification. It answers, “What does this implementation return, change, emit, or call for this input?” Michael Feathers describes their purpose as documenting “your system’s actual behavior, not check[ing] for the behavior you wish your system had.” His characterization-testing article was published on August 8, 2016.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This distinction matters. A test that records a surprising status code, awkward date format, or undocumented side effect is not approving the design. It is recording a contract that refactoring must preserve unless the team explicitly decides to change it.

#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter | 4Pack Type C Female to A Male Cable Converter for Apple Duo 17 16 15 14 Plus,iWatch Watch 12 11 10,iPad 9 8 Air,Airpods 5 4
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Pick a narrow change point

Write down the exact change you intend to make: for example, “extract tax calculation from the invoice renderer” or “replace the file-based cache used by the report command.” The first test should surround that change point, not every caller and database in the product.

  • Identify the function, constructor, command, HTTP route, message handler, or configuration boundary that exposes the behavior.
  • List the inputs the change can receive and the outputs or side effects a caller can observe.
  • Choose one representative path that currently works before adding edge cases.

Find a test point and a seam

A test point is the smallest public or observable boundary through which you can exercise the code. It may be a function, constructor, interface, CLI command, HTTP endpoint, environment variable, or configuration setting. Prefer a boundary that lets the test observe a result without duplicating internal implementation details.

A seam is a place where behavior can be altered without editing the code at that location. Seams let a test substitute or observe a dependency. Examples include a constructor parameter, an overridable method, a module import, a clock provider, a filesystem adapter, or a process environment variable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rank possible boundaries

Boundary What it reveals Typical setup When to choose it
Pure or mostly pure function Return value and raised errors Low Use when the target already has limited coupling.
Constructor or interface Collaborator calls and object state Low to medium Use when dependencies can be supplied or replaced.
CLI or HTTP entry point Parsing, wiring, status, and serialized output Medium Use when lower-level code cannot be reached reliably.
Configuration or environment boundary Behavior under deployment-specific settings Medium Use when a flag or variable controls the path.
Full integration or end-to-end flow Real wiring and external side effects High Use when no smaller stable boundary exists or wiring itself is the risk.

Choose the smallest boundary that still protects the refactoring. A unit-level test is not automatically better: if it requires extensive duplication of hidden internals, an integration test at a stable API may be more reliable.

Build the first characterization test step by step

  1. Record the target. Name the behavior and the entry point in the test description, such as “renders an invoice with a zero discount.”
  2. Prepare realistic input. Start with a production example, fixture, request trace, or a small hand-built case that reaches the target without unnecessary setup.
  3. Control one blocker. If a clock, network client, global, database, or filesystem prevents repeatability, replace only that dependency at a seam.
  4. Run the existing implementation. Capture the return value, thrown error, writes, emitted messages, collaborator calls, and other externally visible effects.
  5. Assert the observed contract. Assert stable, meaningful values first. Avoid asserting incidental object addresses, unordered map iteration, or timestamps that the test does not control.
  6. Run the test repeatedly. A characterization test is useful only when it is deterministic enough to distinguish a code change from environmental noise.

A language-neutral shape looks like this:

given input and controlled dependencies
actual = legacy_entry_point(input)
assert actual == observed_result
assert controlled_dependency.calls == observed_calls

The exact framework syntax is less important than the boundary and the evidence captured. Keep the test readable enough that a future maintainer can tell which behavior is intentional to preserve and which behavior is merely documented for now.

Cover risk-shaped cases, not arbitrary percentages

After the first passing case, add examples based on ways the refactoring could break:

Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter | USB C Dongle with 90W Max Power Delivery, 3×5Gbps USB A Data Ports, for MacBook, Dell, and More (Charger Not Included)
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
  • A normal, representative input.
  • Boundary values such as zero, an empty collection, the first or last allowed value, and maximum practical size.
  • Null, missing, malformed, or otherwise incomplete input where the interface permits it.
  • Expected error paths, including the error type, message, status, or fallback behavior that callers can observe.
  • State transitions, such as repeated calls, retries, partial updates, or a previously initialized object.
  • Externally visible effects: files written, records changed, messages sent, events emitted, or collaborator calls made.

There is no universal coverage percentage established for this process. Stop expanding the initial safety net when the specific refactoring risks have representative tests and the suite runs reliably.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Break dependencies with the smallest mechanical change

Legacy code is often difficult to isolate because it creates collaborators internally, reads globals, consults the current time, touches the filesystem, or calls a database or network service directly. Do not redesign all of those concerns before making progress. Make the smallest mechanical change that lets a test control one dependency.

Common dependency-breaking moves

Obstacle Minimal seam What the test controls
Current time read inside the method Pass a clock or expose a time-provider function Fixed instant and timezone
Hard-coded HTTP or database client Inject a client or wrap creation in one factory Response, timeout, and call count
Global configuration Read through a parameter or a narrow adapter One setting for one test
Direct filesystem access Route file operations through an adapter or temporary directory Contents, paths, and failures
Randomness or generated identifiers Inject a generator or seed a supported interface Repeatable values
Hidden collaborator construction Allow an optional constructor argument or factory seam Substitute collaborator and inspect calls

Keep the change mechanical: add a parameter with a production default, delegate one operation to an adapter, or expose one overridable factory. Avoid changing algorithms, names, formats, and dependency structure in the same commit as the first characterization test. The goal is control, not architectural perfection.

Do not over-mock

Mock only a dependency whose behavior or side effect is outside the test’s purpose. If the refactoring concerns a parser, a real in-memory value is usually clearer than a mock parser. If the concern is whether a payment gateway is called once with a particular request, a controlled fake or mock at that gateway boundary is appropriate. Excessive mocks can freeze call sequences that are not part of the observable contract.

Choose the right first test style

Compare a candidate test by four questions: How isolated is it? What can it observe? How much setup does it require? How stable is the contract it records?

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Test style Isolation Observability Setup cost Contract stability Best first use
Narrow unit characterization High when a seam exists Precise return values and calls Low Usually high for a small interface A focused function or component with controllable dependencies
Approval, golden-master, or snapshot Medium Large formatted or serialized output Low to medium Requires review of baseline changes Reports, documents, JSON payloads, and other bulky results
Integration test Low to medium Wiring, persistence, and real side effects Medium to high Stable at a system boundary When the important risk is integration or no smaller seam exists
End-to-end test Low User-visible workflow High Can be volatile A small number of critical flows after lower-level protection is in place

Start with a narrow unit characterization test when a seam makes it fast and precise. Use an approval or golden-master test when the contract is a large output that would be cumbersome to assert field by field. Use integration or end-to-end coverage when wiring and real side effects are the behavior you must protect, accepting the greater diagnosis and execution cost.

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter | 85W Max PD, 2 USB 3.0 & 1 USBC Data Ports, SD/TF Card Reader, for Laptops/Mac and Other Type C Devices (Charger Not Included)
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

Use snapshots and golden masters without freezing mistakes

Snapshot, approval, and golden-master tests all establish a stored baseline against which later output is compared. They are especially useful for HTML, reports, serialized payloads, generated source, or other structured results with many fields.

  1. Generate the baseline from a representative input set.
  2. Inspect the stored output manually before accepting it. Remove volatile fields or normalize ordering and timestamps where those details are not part of the contract.
  3. Label the fixture with the scenario it represents and the external effects that are intentionally excluded.
  4. During refactoring, review every baseline diff. Accept a change only when it is either an approved contract change or an accidental difference that must be fixed.

A snapshot is not a substitute for understanding. If a five-line output changes, inspect the semantic difference rather than blindly updating the file. For important fields, pair the snapshot with focused assertions so a failure explains what matters.

Run refactoring in short, reversible loops

Once the baseline tests pass, separate structural cleanup from behavior changes. A reliable loop is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Run the characterization suite and confirm a clean baseline.
  2. Make one behavior-preserving structural change, such as renaming a local, introducing a seam, or extracting a private helper.
  3. Run the smallest relevant test, then the broader suite.
  4. Inspect failures and determine whether they indicate an accidental regression, nondeterminism, or an intentional contract change.
  5. Keep the change only when the result is understood; otherwise revert and take a smaller step.
  6. Commit or otherwise checkpoint the passing state before the next structural change.

Keep intentional behavior changes in separate tests and changesets. A failing characterization test should then mean either that the refactoring altered existing behavior accidentally or that someone explicitly approved a new contract and must update the expectation.

Handle known defects deliberately

Legacy behavior often includes bugs. Do not blindly freeze a known defect just because the first test observed it.

  1. Document the observed behavior and why it is considered defective.
  2. Decide whether callers currently depend on it. If they do, plan the compatibility impact rather than silently changing it.
  3. Add a test for the corrected behavior as an explicit contract.
  4. Update or remove the characterization assertion in the same reviewed change that implements the fix.
  5. Retain a regression test for the corrected result so later structural work cannot reintroduce the defect.

This preserves the historical baseline while making the desired change visible to reviewers and future maintainers.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray | Data transfer & Fast charging for MacBook, iPhone 17/16/15, iPad, Samsung Galaxy, Apple Watch
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

Extract new logic only after behavior is protected

When the old path has a dependable safety net, extract a pure function or a new component around the behavior you need to improve. Write ordinary intent-focused tests for that new unit: these tests should express rules, invariants, and desired outcomes rather than merely repeating the old implementation’s output.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the original characterization test at the integration boundary while the replacement is being proven. Route the existing caller through the new component in a small step, run both layers, and remove the old test only when the boundary and the new unit tests together cover the agreed contract.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Patterns for especially difficult legacy code

Global state and singletons

First capture the state-dependent behavior at the public entry point. Then introduce one accessor or parameter that the test can replace. Reset the global state in teardown and add a test for repeated execution if state can leak between calls.

Time, time zones, and daylight-saving transitions

Control the clock and timezone at the seam. Include a normal instant and at least one boundary relevant to the business rule, such as midnight, month-end, or a daylight-saving transition. Assert the externally visible date or offset, not the machine’s current timezone.

Files, databases, and network calls

Decide whether the risk is transformation logic or integration. For transformation logic, use a controlled adapter or temporary resource. For integration behavior, keep a focused test against the real dependency or a representative test instance and assert the side effect that matters. Avoid turning every unit test into a slow environment-dependent test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Huge methods with no obvious seam

Start at the public method or command and record a small, realistic scenario. Identify the first dependency or branch that blocks repeatability, then create one seam there. Repeat only as needed. A sequence of small seams is safer than a wholesale rewrite of the method.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display | 85W Max PD, 1 Powered USB-C 5Gbps & 2 Powered USB-A 3.0 5Gbps Data Ports, for MacBook Pro, MacBook Air, Dell and More (Black)
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.

Non-deterministic output

Control randomness, ordering, clocks, and generated identifiers where possible. If ordering is not contractual, normalize it before comparison. If a value is intentionally variable, assert its format or relationship to other values instead of a literal instance.

Know when the safety net is sufficient

You have enough initial protection for a refactoring target when:

  • The intended entry point can be exercised repeatedly in an automated test.
  • Representative success, boundary, error, and side-effect cases are covered for the change you will make.
  • External dependencies that would make results flaky are controlled at a documented seam.
  • Large-output baselines have been reviewed rather than accepted automatically.
  • The suite distinguishes an approved behavior change from an accidental structural regression.
  • The tests run quickly enough to support frequent refactoring loops, or their slower scope is clearly reserved for integration checks.

This is a risk-based stopping rule, not a target coverage number. Broaden coverage when a new risk appears, not simply to reach an arbitrary percentage.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot common failures

Symptom Likely cause Corrective action
The same test passes and fails without code changes Uncontrolled time, randomness, ordering, global state, or an external service Control the source at a seam, reset state, or move the check to an appropriate integration layer.
Every refactor causes dozens of unrelated failures Tests assert implementation details or share fragile fixtures Move assertions to observable boundaries and split shared setup into scenario-specific fixtures.
The snapshot changes on every run Volatile fields or nondeterministic serialization Normalize only non-contractual values and make ordering explicit.
Adding a seam requires a large redesign The proposed seam is too broad Expose one dependency or factory with a production default, then defer larger design work.
A characterization test preserves an acknowledged bug Observed behavior was mistaken for desired behavior Mark the defect, add the corrected contract, and update the old assertion with review.
The test takes minutes to diagnose The first test is too high in the stack Add a narrower test at a stable seam while retaining a smaller number of integration checks for wiring.

A complete example workflow

Suppose a report command reads a database, applies date rules, writes an HTML file, and emails a link. You need to replace the date-rule code.

  1. Choose the command’s report-generation boundary rather than the whole deployment.
  2. Capture one production-like request and the resulting HTML, file path, and mail request.
  3. Introduce a clock parameter with the current clock as its production default.
  4. Add a characterization test for a normal date range, then cases for an empty range, month-end, and a database error.
  5. Use a temporary directory or file adapter so the test can inspect output without touching a developer’s filesystem.
  6. Use a fake mailer that records the recipient, subject, and link; do not send real mail.
  7. Review the HTML baseline and normalize only generated timestamps that are not part of the report contract.
  8. Extract the date-rule calculation into a pure component and give it intent-focused tests for the approved rules.
  9. Route the command through the component, run the characterization and new unit tests, and investigate every diff.
  10. If the date rules intentionally change, update the explicit rule tests and the boundary characterization expectation in one reviewed change.

The result is a small, observable safety net around the risky workflow plus precise tests for the replacement logic, without requiring a full-system test campaign before the refactoring begins.

Further reading

Readers who need a deeper treatment of seams, dependency breaking, and legacy-code techniques can consult Michael Feathers’ Working Effectively with Legacy Code, listed in Pearson’s catalog. The practical guidance above follows the same principle: make behavior observable, change one thing at a time, and keep the safety net honest.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.