Skip to content

Archive

Testing

55 articles
Software Engineering 08 Sep 2026 9 min read

Reducing Test Combinations with Pairwise Testing

Configuration-heavy software creates a testing problem that grows faster than it first appears. A feature may behave differently by account type, payment method, region, browser mode, storage backend, or feature flag. Testing each choice separately can miss interaction bugs, while testing every combination can become impractical. Pairwise testing is a combinatorial test-design technique for this situation. Instead of requiring every complete combination, it constructs a set of tests in which every pair of parameter values appears together at least once.

Software Engineering 08 Sep 2026 10 min read

Metamorphic Testing When Exact Answers Are Hard

Some software is easy to test because the expected answer is obvious. If a function adds two numbers, a test can call it with 2 and 3 and assert that the result is 5. Other software has outputs that are expensive or awkward to predict. A route planner may examine thousands of possible paths. A search ranker may score hundreds of candidates. A numerical routine may produce a result that is difficult to calculate independently without reimplementing the same algorithm.

Software Engineering 08 Sep 2026 9 min read

Functional Core, Imperative Shell for Testable Business Logic

Business logic often becomes difficult to test for a reason that has little to do with the rule itself. A function that decides whether to approve a refund may also read a database, check the clock, call another service, write an audit record, and send a message. The decision is now mixed with the machinery required to obtain inputs and apply outputs. Tests must control all of that machinery just to ask, “What should happen for this refund?”

Software Engineering 08 Sep 2026 9 min read

Finding Regressions with Binary Search

A regression appears in the current build, but the same behavior worked two weeks ago. Since then, the team has merged 80 changes. Reading all 80 diffs is possible, but it is slow and gives every change equal attention even though only one boundary in history matters: the point where the behavior changed from working to broken. When revisions are ordered and you can classify a revision reliably as good or bad, you can search that history with the same idea as binary search. Test a revision near the middle. Its result tells you which half can still contain the first bad revision. Repeat until only the transition remains.

Software Engineering 08 Sep 2026 9 min read

Enforcing Architecture with Fitness Functions

A team can agree on a sensible architecture and still watch it erode one small change at a time. A developer imports an internal module because it is convenient. Another adds a direct dependency across layers. Months later, the diagram still shows clean boundaries, but the code no longer follows them. Code review can catch these changes, but reviewers must remember every architectural rule and notice every violation. For properties that matter repeatedly, memory is a weak enforcement mechanism.

Software Engineering 08 Sep 2026 10 min read

Differential Testing to Compare Implementations

Replacing an implementation creates an uncomfortable testing problem. You may know that the new code should preserve existing behavior, but writing an expected result for every possible input can be expensive. A parser may accept thousands of valid forms. A pricing engine may combine many rules. A rewritten library may have years of accumulated edge cases. In these situations, an implementation you already trust can help test another implementation. Differential testing runs the same input through two or more implementations that are expected to behave equivalently, then compares their observable results. A disagreement does not automatically prove which implementation is wrong. It does something more basic and extremely useful: it gives you a concrete case that requires explanation.

Software Engineering 08 Sep 2026 9 min read

Designing a Test Portfolio for Useful Feedback

A test suite can contain thousands of tests and still give poor engineering feedback. It may run slowly, fail for unrelated reasons, miss important integration mistakes, or make small implementation changes expensive because too many tests depend on internal details. The usual response is to argue about test categories: more unit tests, fewer end-to-end tests, or a particular shape such as a testing pyramid. Those models can be useful reminders, but a fixed ratio does not tell you whether a specific test earns its cost.

Software Engineering 08 Sep 2026 8 min read

Choosing Coverage Criteria for Conditional Logic

A test suite can report high coverage and still miss an important bug. The problem is often not the percentage itself, but what the percentage measures. Consider a decision such as isMember && hasCredit. A test may execute the if statement, both outcomes of the decision, or each individual condition in different ways. Those are different kinds of evidence. Treating them as interchangeable makes coverage numbers more reassuring than they should be.

Software Engineering 07 Sep 2026 9 min read

Testing Failure Semantics, Not Just Errors

A test that expects an error can still miss the most damaging part of a failure. An order operation may return payment declined correctly while also marking the order as paid. A file import may report that parsing failed after writing half of its records. A retry may succeed but send the same notification twice. In each case, the visible error is correct. The failure semantics are not. Failure semantics describe what the system promises about state, side effects, and subsequent operations when something goes wrong. Testing them means asking more than “did this fail?” This article shows how to identify those promises and turn them into focused tests.

Software Engineering 07 Sep 2026 9 min read

Property-Based Testing for Behavioral Invariants

Example-based tests are excellent when you know the cases that matter. You choose an input, state the expected result, and protect that behavior from regression. The weakness is also clear: the test checks only the examples you thought to write. Some defects hide between those examples. A parser works for ordinary names but fails on an empty segment. A range-normalization function works for the three values in the test file but produces an invalid range for an unusual ordering. A serializer handles familiar records but loses information for one combination of optional fields.

Software Engineering 07 Sep 2026 8 min read

Adding Behavior Safely with Sprout Methods

A small feature can become a risky change when it must be added inside a large method that has weak tests. The obvious edit may be only ten lines, but those lines become mixed with old branches, mutable state, database calls, or other behavior you do not fully understand. Testing the new rule then requires exercising the entire method. One useful response is a sprout method: put the new behavior in a new, focused method and make the existing code call it. The old method changes only enough to connect the new behavior. The new method can often be tested directly with much less setup.

Software Engineering 06 Sep 2026 9 min read

Using Snapshot Tests Without Freezing Incidental Details

Some outputs are awkward to test one assertion at a time. A serializer may produce a structured document, a formatter may emit many related lines, or a component may build a nested representation. Writing an assertion for every field can obscure the behavior you are trying to protect. A snapshot test takes a representative output and compares it with a previously accepted copy, called the snapshot. When the output changes, the test shows the difference and asks the developer to decide whether the new output is correct.

Software Engineering 06 Sep 2026 9 min read

Building Focused Test Data with Test Data Builders

Tests become hard to understand when creating the object under test requires many values that are irrelevant to the behavior being checked. A test for an overdue invoice may need an identifier, customer, currency, issue date, due date, line items, tax settings, and status even though only the due date matters. Copying complete fixtures into every test makes that irrelevant detail visible everywhere. Sharing one mutable fixture hides the detail, but it can make tests depend on each other. A test data builder offers a middle path: it creates a valid object from sensible test defaults while letting each test override only the values important to its scenario.

Software Engineering 05 Sep 2026 7 min read

Separating Decisions from Side Effects with a Functional Core

Business logic often becomes difficult to test for a reason that has little to do with the rules themselves. A function decides what should happen while also reading the clock, querying storage, calling a service, sending a message, and writing logs. To test one decision, you must arrange all of those surroundings. A functional core, imperative shell design separates those concerns. The functional core receives ordinary data and computes decisions without performing external side effects. The imperative shell gathers inputs, calls the core, and carries out the resulting actions.

Software Engineering 05 Sep 2026 10 min read

Ports and Adapters for Testable Application Boundaries

A business rule often begins as a few lines of code and gradually becomes entangled with everything around it. A pricing decision reads directly from a database. An order workflow calls a payment SDK from the middle of its logic. Tests need a web server, network access, and several configuration files just to exercise one decision. The problem is not that databases, frameworks, or SDKs are bad. The problem is that application decisions have become dependent on details that change for different reasons.

Software Engineering 05 Sep 2026 9 min read

Keeping Framework Code Humble at System Boundaries

A user clicks Save. The handler reads text fields, validates an order, calculates a discount, writes data, chooses an error message, and updates the screen. Testing one business rule now requires constructing a UI framework and arranging several unrelated details. The rule itself is not difficult. It is difficult to reach because it is mixed with code that must talk to the outside world. The Humble Object pattern addresses this problem by keeping framework-dependent code deliberately small and moving important decisions into ordinary code that can be exercised directly. This article explains the mental model, shows how to find a useful boundary, and examines where the pattern helps and where it only adds indirection.

Software Engineering 05 Sep 2026 9 min read

Characterization Tests for Legacy Code

Refactoring unfamiliar code creates an uncomfortable problem: you want to improve the implementation, but you may not know which parts of its current behavior callers depend on. Existing documentation can be incomplete, and a thin test suite may describe only the obvious cases. A characterization test helps by recording what the software does now. Instead of starting from a specification of what the code should do, you exercise existing behavior, observe the result, and turn that observation into a test. The test then warns you when a later change alters that behavior.

Software Engineering 04 Sep 2026 10 min read

Snapshot Tests as Reviewed Contracts

Some outputs are easy to test with a few assertions. A price calculation can be checked against one number. A validation rule can be checked against one error code. Other outputs are structured and wide: a rendered document, a compiler diagnostic, a serialized configuration, or a formatted report may contain dozens of fields and lines. Writing an assertion for every detail can make the test harder to read than the behavior it protects.

Software Engineering 04 Sep 2026 9 min read

Separating Decisions from Side Effects

Business logic often becomes difficult to test for a simple reason: the code that decides what should happen is mixed with the code that makes it happen. A function reads the clock, queries storage, applies pricing rules, sends an email, and writes an audit record. To test one discount rule, the test must now arrange several unrelated dependencies. When a failure occurs, it is harder to tell whether the decision was wrong or an external operation failed.

Software Engineering 04 Sep 2026 10 min read

Separate Decisions from Side Effects

A function reads an order, checks inventory, chooses a shipping method, updates a record, sends an email, and writes a log entry. A small rule change arrives: express shipping is now allowed only when every item is in stock. The rule itself is simple. Testing it is not. To exercise the decision, a test may need a database, an inventory service, an email substitute, and careful setup for several unrelated operations.

Software Engineering 04 Sep 2026 7 min read

Making Time an Explicit Dependency

Code that asks the system clock for the current time looks harmless. The difficulty appears when behavior depends on the answer. A test for an expired reservation may pass now and fail later. A boundary such as midnight can become awkward to reproduce. Business logic becomes tied to an environmental input that callers cannot see or control. A useful design move is to treat the current time as a dependency. Instead of letting decision-making code reach for a clock whenever it needs one, obtain the time at a clear boundary and pass the relevant value or clock into the code that makes the decision.

Software Engineering 04 Sep 2026 9 min read

Keeping Framework Code Thin with Humble Objects

Some code is difficult to test for reasons that have little to do with the business rule it implements. A user-interface handler may need a framework event. A scheduled job may be created by a runtime. A message consumer may receive objects owned by a library. The code then mixes two jobs: interacting with an awkward environment and deciding what the application should do. When those jobs stay together, a small rule can require a large test setup. Developers may respond by skipping tests, mocking many framework details, or testing through slow integration paths even when the important logic is simple.

Software Engineering 04 Sep 2026 9 min read

Creating Seams for Safe Legacy Changes

Legacy code is often difficult to change for a reason that has little to do with the business rule you need to edit. The code may create its own database client, read the clock directly, call a remote service in the middle of a calculation, or depend on another component that is expensive or unreliable in tests. You may understand the desired change and still be unable to test it in isolation.

Software Engineering 03 Sep 2026 11 min read

Using Decision Tables to Tame Complex Rules

Conditional logic often becomes difficult before it becomes large. Three or four business conditions can interact in enough combinations that a developer can no longer tell, by reading nested if statements, whether every case is covered or whether two branches contradict each other. A decision table is a compact way to make those combinations explicit. It lists the conditions that affect a decision, the meaningful combinations of those conditions, and the outcome for each combination.