Software Quality · Quality Engineering · 30 Days of Software Quality Awareness

Day 15/30: Putting It All Together - A Quality Engineering Case Study

A fintech transfer case study connecting requirements, risk, testability, prevention, testing, and automation to dependable product outcomes.

October 30, 2026 31 min read Written by Iyola Oyabiyi
Two banks connected by a line with a green checkmark representing a verified money transfer.

What happens when we apply everything we’ve learned about Software Quality to a single product?


Imagine we’re building a fintech application that allows customers to transfer money between bank accounts.

The product has been in development for months. The engineering team has implemented authentication, account management, transfers, transaction history, and notifications.

The interface looks beautiful. The application is responsive. The APIs work. The development team has written tests, and the product manager is pleased with the progress.

We’re almost ready to launch.

One afternoon, a customer named Ada opens the app to transfer NGN 50,000 to her friend, Tunde.

She enters his account details, confirms the amount, and taps Send.

A loading indicator appears briefly, followed by a green checkmark.

Transfer Successful.

Ada receives a receipt, takes a screenshot, and sends it to Tunde.

But Tunde hasn’t received the money.

Ten minutes later, nothing has changed.

Ada checks her balance. The NGN 50,000 has been deducted. Her transaction history still says Successful.

She contacts customer support.

The support agent checks the transaction reference but cannot establish whether the receiving bank accepted the transfer, whether the payment provider is experiencing delays, or whether the transaction needs to be reversed.

The engineering team investigates.

The API returned HTTP 200. The application’s database recorded the transaction as successful. The frontend displayed exactly what the backend returned.

From the perspective of the implemented code, everything behaved as expected.

From the customer’s perspective, the product failed at the one thing she trusted it to do.

And the problem is no longer confined to Ada’s experience.

Customer support now has an unresolved complaint. Finance has a transaction that requires investigation. Engineering must examine logs across multiple services. The business has incurred operational costs, and Ada has a reason to question whether she should trust the application with her money again.

Suppose this isn’t an isolated incident.

What happens when hundreds of customers experience similar problems on payday? What if some retry their transfers and get debited twice?

These are not merely technical defects; they are business risks with direct consequences for revenue, operations, reputation, and customer retention.

The uncomfortable question is:

How did a product that appeared to work so well become a product its customers couldn’t trust?

Over the past 14 days, we’ve examined the principles, practices, and engineering decisions that help us answer that question.

Today, we’re going to apply every one of them to this single product as connected decisions throughout the product’s development.

Let’s assume we’re involved from the beginning, before the first transfer feature is implemented.


Part One: Establishing What Quality Means

Day 1 - What Is Software Quality?

The first mistake would be assuming that implementing the transfer feature means we’ve delivered a quality transfer experience.

The product requirement might initially sound simple:

“Customers should be able to transfer money to other bank accounts.”

A developer could implement an interface that accepts account details and an amount, sends a request to a payment provider, and displays the response.

Technically, the feature exists.

But what does it mean for that feature to work?

If Ada transfers NGN 50,000 and the money is deducted twice, did it work?

If the money arrives but the application reports failure, did it work?

If the transfer takes 40 minutes without communicating its status, did it work?

If the transaction succeeds but nobody can retrieve a reliable record of it, did it work?

These questions expose the difference between implementing functionality and delivering quality.

For this product, a successful transfer experience must satisfy several conditions:

  • The intended recipient receives the correct amount.
  • The sender is charged the correct amount, without unintended duplicate debits.
  • The transaction status accurately reflects the known outcome.
  • The customer can retrieve reliable transaction records.
  • Failures and uncertain outcomes can be investigated and resolved.

Notice that none of these conditions concerns the appearance of the Send button.

That doesn’t make interface design irrelevant. It establishes that the purpose of the feature is much broader than the interaction used to initiate it.

Our first Quality Engineering decision is to define success in terms of the outcome the product is expected to deliver, not just the functionality it implements.

Day 2 - What Makes Software High-Quality?

Now that we understand the desired outcome, we need to examine the qualities required to deliver it consistently.

Consider six dimensions of this transfer feature.

Reliability: Can the system handle interrupted connections, delayed provider responses, duplicate requests, and service failures without creating incorrect financial outcomes?

Security: Can someone initiate a transfer from Ada’s account without proper authorization? Can an attacker manipulate the destination account or transfer amount? Are sensitive transaction details appropriately protected?

Performance: How quickly does the application acknowledge a transfer request? Can it maintain acceptable response times when transaction volumes increase?

Usability: Can Ada distinguish between a completed transfer and one still being processed? Does the interface tell her what to do when the outcome is uncertain?

Maintainability: If the business changes its payment provider or fee structure, can engineers make that change without introducing defects across unrelated payment workflows?

Compatibility and interoperability: Can the application communicate correctly with the different banking and payment systems it depends on, despite differences in their protocols and response behaviours?

A transfer can satisfy one dimension while failing another.

It might process transactions quickly but expose sensitive information.

It might be secure but repeatedly display misleading transaction statuses.

It might work correctly under ordinary conditions but become unreliable when a provider slows down.

This changes how we evaluate the product.

Instead of asking only if the transfer work, we also get clarity on the conditions that makes it work, the qualities it must preserve, and how would we know when those qualities are compromised.

That is the beginning of a meaningful quality strategy.

Day 3 - Quality Begins With Understanding the Problem

Before writing detailed requirements, we need to understand whose problems we’re solving.

Ada wants to send money conveniently and confidently.

Tunde wants to receive the correct amount.

Customer support needs to investigate complaints using trustworthy information.

Finance needs accurate transaction records and reconciliation.

Engineering needs predictable system behaviour and enough observability to diagnose failures.

The business needs a sustainable service that customers trust and that it can operate responsibly.

These needs intersect, but they are not interchangeable.

For example, a product manager might say they want transfers to feel instantaneous.

That is a reasonable product aspiration.

But does instantaneous mean the application should immediately acknowledge receiving the request, or that the money must reach the receiving bank instantly?

Those are different outcomes, potentially controlled by different systems.

If we don’t clarify the distinction, the team might implement a fast success screen while the actual payment is still being processed.

The interface would meet one interpretation of the requirement and violate the customer’s expectation.

A Quality Engineer working at this stage investigates those ambiguities before they become implementation decisions.

We ask what the customer should experience, what the external payment provider can guarantee, what the business must record, and what happens when those expectations conflict.

The value here is preventing the team from building the wrong interpretation of the right idea.

Day 4 - Requirements Are the Foundation of Quality

We now translate those expectations into explicit, verifiable obligations.

Instead of relying on a vague statement such as “Transfers should work correctly,” we establish requirements like these:

REQ-TRF-001: The system shall display a transfer as Successful only after receiving the payment provider’s contractually defined final-success confirmation.

REQ-TRF-002: Repeated submission of the same uniquely identified transfer instruction shall not initiate another transfer.

REQ-TRF-003: Every accepted transfer instruction shall have a unique transaction reference through which its processing state can be retrieved.

REQ-TRF-004: The system shall record sufficient transaction and provider-reference information (as provided by stakeholders) to support reconciliation and investigation.

REQ-TRF-005: The system shall allow an unresolved transfer to be identified for investigation and reconciliation.

Each requirement has a distinct purpose.

The first protects against false success messages.

The second addresses duplicate processing.

The third provides transaction traceability.

The fourth supports financial accountability.

The fifth ensures that an uncertain outcome doesn’t just disappear into the system.

We’re also establishing relationships between requirements.

If the system must display the correct status, it needs a reliable way to determine that status. If transactions must be reconciled, they need sufficiently complete and identifiable records.

These dependencies influence architecture, data models, API contracts, and operational processes.

A professional Quality Engineer helps establish these relationships before they become expensive to change.

And because each requirement has an identifier, we can later connect it to its acceptance criteria, tests, results, and defects.

A requirement is useful only when we can establish what it obligates the system to do and how we’ll determine whether that obligation has been satisfied.

Day 5 - What Does “Working as Expected” Actually Mean?

Let’s return to REQ-TRF-001.

The requirement says that the system must not report a transfer as successful without the required confirmation.

But we still need to agree on the observable behaviour.

Consider four scenarios.

Scenario A: Confirmed transfer

The provider returns its defined final-success status.

Expected outcome: The application displays Successful, records the confirmed outcome, and makes the appropriate receipt available.

Scenario B: Rejected transfer

The provider definitively rejects the transaction.

Expected outcome: The application displays Failed. If funds were already debited, the required reversal process is initiated and tracked. We must not imply that the money has been returned before the reversal is actually complete.

Scenario C: Unknown transfer outcome

The provider does not respond before the request times out.

Expected outcome: The application displays Pending Confirmation or an equivalent accurate status. It must not interpret the timeout itself as proof of success or failure.

Scenario D: Delayed confirmation

The provider confirms the transaction after the customer has left the application.

Expected outcome: The system records the new status, makes it available when the customer returns, and issues any appropriate notification.

These scenarios become acceptance criteria.

They also reveal decisions the business must make.

How long may a transaction remain pending? When should support intervene? What should the customer be told while the system investigates?

Verification asks whether the implementation satisfies the agreed requirements.

Validation asks whether those requirements and their resulting experience meet the intended needs.

A technically accurate Pending Confirmation message might pass verification but still fail validation if customers cannot understand what it means or what they should do next.

The Definition of Done should therefore cover more than code completion. It should include the agreed acceptance criteria, relevant quality checks, necessary reviews, and evidence that the feature is ready for its intended use.

The purpose of acceptance criteria is to remove dangerous assumptions about what success means.

Day 6 - Quality Is About Managing Risk

By now, we have identified numerous things that could go wrong.

But we cannot give every potential defect the same attention.

Consider these examples:

Potential failureConsequence
Transfer button is slightly misalignedMinor visual inconvenience
Customer is charged twiceFinancial loss, complaints, investigation
Transfer is reported successful before confirmationCustomer misinformation, disputes, loss of trust
Unauthorized user initiates a transferSecurity incident and potential financial loss
Provider confirmation is lostUnresolved transaction, reconciliation effort
Transaction history loads slowlyFrustration and potential support demand

These failures do not have equal consequences.

And their likelihood may differ depending on the architecture, integrations, and operational environment.

Risk-based Quality Engineering evaluates both the probability of failure and the impact if it occurs, while accounting for uncertainty in the evidence.

Suppose our transfer integration uses asynchronous callbacks from the payment provider.

We discover that callbacks may be delivered more than once.

That immediately raises an important question:

What happens if our application processes the same success notification twice?

If each callback independently updates financial records or issues another credit, a delivery behaviour that should be harmless could cause a serious financial inconsistency.

This risk deserves attention before a minor interface defect.

Risk assessment influences which scenarios we investigate first, which controls we introduce, how deeply we test, and what evidence we require before release.

A Quality Engineer isn’t just trying to find the largest number of bugs, we’re helping the business direct its limited engineering resources toward failures that would hurt it and its customers the most.

Day 7 - Functional Software Can Still Be Poor-Quality Software

Our application now handles the expected transfer states correctly.

But what happens when thousands of customers initiate transfers simultaneously?

What happens when the payment provider takes longer than usual to respond?

What happens when a transaction remains unresolved for an extended period?

These are questions that require measurable quality targets.

For example, suppose we agree on these targets:

API responsiveness: At least 95% of transfer-submission requests should receive an acknowledgement within two seconds under a specified peak workload.

Unresolved transactions: Transfers that remain in an indeterminate state for more than 15 minutes must be flagged for automated investigation or operational attention.

Duplicate processing: Replaying the same logical transfer instruction must not create an additional transfer, including under concurrent requests.

Operational traceability: Every accepted transfer must have a retrievable reference connecting the internal transaction record to the relevant provider interaction.

These are illustrative targets, not universal industry benchmarks. Actual thresholds must be negotiated against user needs, business risk, system architecture, and provider capabilities.

Notice the distinction between the first two.

A fast API acknowledgement does not mean the recipient has received the money.

We must measure the responsiveness of our application separately from the end-to-end completion time of a transfer.

Now we can design performance, reliability, and recovery checks around defined expectations.

And when the system cannot meet a target, we have a basis for determining whether the failure is acceptable, requires mitigation, or should block release.

Without measurable expectations, performance and reliability are just opinions. With them, we can evaluate evidence.


Part Two: Engineering Quality Into the Product

Our requirements are clearer. We understand the risks and have defined the outcomes that matter.

Now development begins.

This is where many teams make a costly mistake: they assume Quality Engineering can wait until there’s a finished application to test.

But some of the most important quality decisions are about to happen in the architecture and implementation.

Day 8 - Design for Testability, Not Just Functionality

Suppose the developer implements the transfer feature, and every request goes directly to a real payment provider.

We want to verify what happens when the provider responds after 30 seconds, returns an unexpected status, sends duplicate callbacks, or becomes temporarily unavailable.

How will we reproduce those conditions safely and consistently?

If we cannot control them, our testing will then be dependent on chance.

So we influence the design.

We introduce a configurable provider integration that can use a simulator or stub in controlled environments.

That allows us to reproduce specific conditions without moving real money.

We define observable transaction states such as:

CREATED -> PROCESSING -> SUCCESSFUL

or

CREATED -> PROCESSING -> FAILED

with unresolved transactions remaining in an explicitly pending state until authoritative evidence supports a transition.

We establish correlation identifiers that connect the customer’s request, internal transaction, provider interaction, callbacks, and relevant logs.

We also make sure the system exposes the evidence necessary to inspect these states without directly manipulating production data.

Now imagine a transfer times out.

Instead of searching blindly through unrelated logs, we can follow its reference and establish what happened at each boundary.

And instead of waiting for a real payment provider to fail, we can reproduce that failure deliberately during testing.

Testability is an architectural property. If we ignore it during design, we may discover too late that critical behaviours are difficult to verify.

A Quality Engineer brings these concerns into design discussions before they become permanent limitations.

Day 9 - Preventing Defects Is Better Than Finding Them

During implementation, a developer proposes this logic:

“If the payment provider returns HTTP 200, mark the transaction Successful.”

At first glance, that seems reasonable.

But we revisit the provider’s API contract.

What if HTTP 200 means only that the provider accepted the transfer request for processing?

The network request succeeded, but the financial transaction hasn’t necessarily completed.

Without this review, the implementation could pass ordinary API tests while giving customers false information.

Now consider another design decision.

The application automatically retries failed transfer requests. Every retry creates a new transaction reference.

What happens when the initial request actually reached the provider, but the response was lost?

The retry may initiate another payment.

Before this becomes a production defect, we recommend an idempotency mechanism that identifies the logical transfer and prevents its unintended repetition.

This requires more than attaching a random key to every request. The same logical transfer must retain its identity across retries, and the relevant processing boundaries must safely handle duplicates and concurrent submissions.

We also review callback authentication, state-transition rules, transaction-record updates, and failure recovery.

These activities may involve requirement reviews, API contract reviews, architecture discussions, threat modelling, and code reviews.

They happen before a test is executed against a finished feature.

The impact is significant.

A defect prevented during design does not need to be discovered by a customer, investigated under production pressure, corrected, retested, and potentially reconciled afterward.

A Quality Engineer’s contribution should not be measured only by how many defects they discover. It should also be reflected in the risks they help the team eliminate before those defects exist.

Day 10 - Different Tests Answer Different Questions

Our transfer feature is implemented.

Now we need evidence that it works.

But one test level cannot answer every question.

Component/Unit testing

Does the function calculating transfer fees return the correct amount?

Can a transaction move from Processing to Successful only through an allowed transition?

Does the balance validation reject transfers that exceed available funds?

These tests examine relatively small units of logic and can provide fast feedback.

Component/Unit Integration testing

Does our application correctly interpret the payment provider’s responses?

What happens when the provider times out?

Are duplicate callbacks processed safely?

Can the system recover when a callback arrives before another expected event?

These tests examine interactions between components and external interfaces.

System testing

Does the assembled application behave correctly when a customer submits a transfer, its internal services communicate, and the resulting transaction state is displayed?

Here, we’re evaluating the integrated system end-to-end instead of a single component.

Acceptance testing

Does the delivered behaviour satisfy the agreed product and business expectations?

Can the product owner, operational stakeholders, or representative users establish that the transfer experience meets its acceptance criteria?

Different questions require different evidence.

A unit test can prove that a fee calculation works for selected inputs. It cannot prove that the recipient receives the intended amount.

An integration test can prove that a callback is handled according to a provider contract. It cannot, by itself, establish that the customer understands the resulting message.

A successful end-to-end scenario cannot establish that every boundary condition, failure mode, or security risk has been addressed.

The professional judgment is knowing which uncertainty each test level should reduce, rather than accumulating tests without understanding what they prove.

Day 11 - Good Testing Requires Good Test Design

Let’s assume our application allows individual transfers from NGN 100 to NGN 500,000.

A developer tests NGN 100, NGN 50,000 and NGN 500,000, and confirms that it works.

Is that sufficient evidence that the amount validation is correct?

No.

We can use equivalence partitioning to identify meaningful input groups:

  • Amounts below the minimum
  • Amounts within the permitted range
  • Amounts above the maximum
  • Invalid inputs, such as nonnumeric or improperly formatted amounts

Then boundary value analysis helps us focus on values near the limits.

For the NGN 100 minimum:

NGN 99.99 - below the minimum.

NGN 100.00 - exactly the minimum.

NGN 100.01 - immediately above the minimum.

For the NGN 500,000 maximum:

NGN 499,999.99 - immediately below the maximum.

NGN 500,000.00 - exactly the maximum.

NGN 500,000.01 - above the maximum.

The precise cases depend on the supported currency precision and the documented validation rules.

But amount validation is only one part of the design.

What happens if Ada has exactly enough money for the transfer amount but not the additional fee?

What if her balance changes between validation and debit?

What if she taps Send twice before the first request finishes?

What if two requests for the same logical transaction arrive at almost exactly the same time?

What if a delayed success callback arrives after the system has recorded another status?

Some of these require concurrency, state-transition, or interaction testing rather than boundary analysis alone.

Good test design combines appropriate techniques to explore meaningful conditions.

We might have hundreds of possible inputs and system states, but limited time to test them.

The objective of a proper test design is to select cases that systematically challenge our assumptions, cover meaningful behaviour, and expose consequential failures. We do not want to choose test cases randomly or attempt every possible combination.

Day 12 - Does the Entire User Journey Actually Work?

Now let’s return to Ada’s NGN 50,000 transfer.

We’ve tested individual components and integrations.

But what if they all work independently while the complete experience still fails?

Consider the journey:

  1. Ada authenticates and initiates the transfer.
  2. The system validates her account, balance, recipient, and applicable limits.
  3. The applicable fees are calculated and disclosed.
  4. Ada authorizes the transaction.
  5. The application creates a uniquely identifiable transfer instruction.
  6. The appropriate financial records are created, and the provider receives the instruction.
  7. The provider processes the transfer and reports its outcome.
  8. The application updates its transaction state.
  9. Ada receives an accurate confirmation or pending-status message.
  10. Her transaction history and receipt reflect the authoritative outcome.
  11. The financial records can be reconciled against the relevant provider records.

Now ask what could go wrong between these steps.

The provider confirms success, but our application fails to persist the response.

The ledger records a debit, but the transaction history shows nothing.

The application displays the correct amount, but the receipt includes an incorrect fee.

A callback is processed twice, causing duplicated downstream effects.

The transaction succeeds after the customer closes the app, but the displayed status remains outdated when they return.

Each component might pass its own tests while the complete journey remains inconsistent.

End-to-end testing helps expose these problems by evaluating the connected workflow.

But we also need to understand the limits of our environment.

A simulated receiving bank can verify how our application handles a reported credit. It cannot independently prove that a real bank has credited a real account.

Evidence about actual external settlement requires an appropriate provider integration, controlled test arrangement, or reconciliation against authoritative external records.

This matters because professional testing must not claim more than its evidence supports.

The customer experiences one product, not a collection of independently functioning services. Our quality strategy must account for that complete experience.

Day 13 - Yesterday’s Working Feature Can Become Today’s Defect

After launch, the business introduces a transfer fee.

Previously, customers could send NGN 50,000 without an additional charge.

Now, some transfers attract a fee of NGN 50.

The change appears straightforward.

Calculate the fee, display it, and debit the appropriate total.

But consider everything this change might affect.

Does the balance validation now account for both the transfer amount and fee?

If Ada has exactly NGN 50,000 available, should the transaction proceed when the total debit is NGN 50,050?

Does the confirmation screen clearly distinguish the amount Tunde will receive from Ada’s total charge?

Are the fee and transfer amount correctly represented in the ledger?

Does the receipt show the right figures?

Does a failed transfer require the fee to be reversed? That depends on the agreed fee policy.

What happens when the transaction is retried?

Can the same fee be charged twice?

The change affects much more than the fee-calculation function.

Regression testing helps us verify that the new implementation has not broken previously working behaviour.

Change-impact analysis identifies the requirements, components, interfaces, and workflows that may be affected.

We don’t necessarily rerun every test indiscriminately.

We select appropriate checks based on the change, its dependencies, and the risks involved.

And where the potential impact is high, we broaden our verification accordingly.

Software quality is not a permanent state achieved once. It must be reassessed as the product changes.

Day 14 - Automation Is a Strategy, Not a Testing Shortcut

Our product now has numerous checks covering transfer rules, provider integrations, financial records, user journeys, and regression risks.

Should we automate everything?

Not necessarily.

Some checks are deterministic, frequently repeated, and inexpensive to execute.

Others require judgment, exploratory investigation, specialised environments, or considerable maintenance.

For our transfer application, a sensible automation strategy might include:

Fast checks executed frequently

Unit tests for fee calculations, amount validation, transaction state transitions, and idempotency rules.

Integration checks executed on relevant changes

Tests for provider response handling, callback authentication, retries, duplicate events, and ledger interactions.

Targeted end-to-end checks

A limited set of critical customer journeys, including successful transfers, declined transfers, and unresolved outcomes.

Specialised checks

Performance, resilience, security, and recovery assessments executed at appropriate stages and in suitable environments.

We also retain exploratory testing where human investigation can uncover assumptions, inconsistencies, and behaviours that predefined scripts may overlook.

A useful automation strategy considers execution speed, maintenance cost, failure diagnosis, business risk, and the reliability of the test environment.

A test that fails unpredictably because of unstable data or dependencies may create noise rather than confidence.

A test that always passes because it verifies the wrong thing is even more dangerous.

And a dashboard displaying thousands of passing tests tells us very little unless we understand which risks those tests address.

Automation increases the speed and consistency with which we collect evidence. It does not automatically make that evidence meaningful.


Up Next - Part 3: Building Reliable Engineering Processes

We’ve established what quality means, how to engineer it into a product, and how testing helps us evaluate the result.

But there’s a problem we haven’t fully addressed.

What if our tests are correct, but the environment in which we execute them is unreliable?

What happens when tests pass locally but fail in CI? When shared test data changes unexpectedly? When a third-party service causes inconsistent results? Or when teams begin ignoring failed checks because they assume the tests are flaky?

Day 16/30: Your Tests Are Only as Reliable as Their Environment.

We’ll explore test environments, test data management, dependencies, isolation, deterministic execution, and why unreliable test infrastructure can undermine even a carefully designed quality strategy.

Then, on Day 17, we’ll connect those reliable checks to the software delivery process through continuous integration, quality gates, and release readiness.

Because designing effective tests is one challenge.

Building an engineering process in which their results remain trustworthy, actionable, and relevant to every change is another.