Functional Testing in Software Testing: One Feature, Start to Finish
What is functional testing? Follow one checkout coupon feature from requirement to test conditions, test cases, results and bug reports, then see how it differs from non-functional testing.

Last updated 2026-11-01 · 12 min read · By SoftwareTestPilot
You can read ten definitions of functional testing and still not know what to do on Monday morning when a new feature lands. So this guide skips the glossary tour. We take one ordinary feature, a coupon code box at checkout, and test it the way a QA engineer would: read the rule, break it into conditions, write cases, run them, and report what fails. The definition, the types and the difference from non-functional testing all fall out of that example.
What is functional testing?
Functional testing checks what a system does against what it is supposed to do. You give it an input, observe the output or behaviour, and compare that with the expected result taken from a requirement, user story or business rule.
The ISTQB glossary defines it as testing performed to evaluate whether a component or system satisfies functional requirements. In practice it is mostly black box testing: you don't need to read the code, only to know the rule.
Step 1: read the requirement like a tester
Here is the user story our team received:
As a shopper, I can enter the code SAVE10 at checkout to get 10% off orders of ₹500 or more, up to a maximum discount of ₹200.
It looks simple. A tester reads it and immediately has questions:
- Is ₹500 included? ("₹500 or more" says yes.)
- Is the 10% calculated before or after delivery charges?
- What message does an expired code show?
- Can two codes be combined?
- What happens if the cart drops below ₹500 after the code is applied?
None of these is answered by the story. We took them to the product owner and got answers: subtotal before delivery, one code per order, specific messages for expired and unknown codes, and the discount must disappear if the cart drops below ₹500. Asking these questions is functional testing. It is cheaper to catch a gap here than after release.
Step 2: break the rule into test conditions
A test condition is one thing that can be checked. Writing them before test cases stops you from jumping straight to "try SAVE10 and see". For our coupon:
| Rule | Test condition | Technique |
|---|---|---|
| C1 | Valid, active code reduces the order total by 10% | Positive check |
| C2 | Code applies only when the cart subtotal is ₹500 or more | Boundary value analysis (₹499, ₹500, ₹501) |
| C3 | Discount is capped at ₹200 | Boundary value analysis (subtotal ₹2,000 vs ₹2,010) |
| C4 | Expired, unknown or empty codes are rejected with a clear message | Equivalence partitioning |
| C5 | Code is case-insensitive and ignores surrounding spaces | Equivalence partitioning |
| C6 | Only one code per order; a second code replaces the first | State transition |
| C7 | Removing items below ₹500 removes the discount | State transition |
The technique column matters. Every number in a rule (₹500, 10%, ₹200) has an edge, and edges are where developers write > instead of >=. Our guide to boundary value analysis and equivalence partitioning covers both techniques in more depth.
Step 3: write cases with exact inputs and expected results
Each condition becomes one or more test cases. Notice that every expected result is a value you can check, not "works correctly":
| ID | Input | Expected result | Actual | Status |
|---|---|---|---|---|
| TC-01 | Subtotal ₹1,000, code SAVE10 | Discount ₹100, total ₹900 | ₹900 | Pass |
| TC-02 | Subtotal ₹499, SAVE10 | Rejected: "Add ₹1 more to use this code" | Rejected | Pass |
| TC-03 | Subtotal ₹500, SAVE10 | Discount ₹50, total ₹450 | Rejected | Fail |
| TC-04 | Subtotal ₹2,010, SAVE10 | Discount capped at ₹200, total ₹1,810 | ₹1,810 | Pass |
| TC-05 | Code OLD5 (expired) | "This code has expired" | "Invalid code" | Fail |
| TC-06 | " save10 " (lower case, spaces) | Accepted, same as SAVE10 | Accepted | Pass |
| TC-07 | Apply SAVE10, then WELCOME15 | Only WELCOME15 applied | Only WELCOME15 | Pass |
| TC-08 | Apply SAVE10 at ₹600, remove an item to ₹400 | Discount removed, message shown | Discount still applied | Fail |
Eight cases, three failures. That ratio is not unusual for a first run of a rule with numbers in it.
Step 4: report what failed so a developer can fix it
The three failures, written up the way a developer wants to read them:
- Coupon rejected at exactly ₹500 (TC-03). Steps: add items totalling ₹500, enter SAVE10. Expected: ₹50 off. Actual: "Add ₹1 more to use this code". Likely cause: the check uses
subtotal > 500instead of>= 500. Severity: medium; real shoppers hit round numbers often. - Expired code shows "Invalid code" (TC-05). Expected the agreed message "This code has expired". Severity: low, but it creates support tickets from shoppers who know the code was real.
- Discount stays after cart drops below ₹500 (TC-08). Steps: subtotal ₹600, apply SAVE10, remove an item to reach ₹400. Expected: discount removed. Actual: ₹40 discount still applied. Severity: high; it gives money away on every such order.
Note that the most expensive bug (TC-08) only appears after a change of state. Happy-path testing would never have found it.
Step 5: retest, then protect it with regression tests
When the fixes arrive, you first retest the three failed cases (confirmation testing). Then you rerun the passing cases too, because the fix to the ₹500 check touched the same code as the cap. That rerun is regression testing. Cases TC-01 to TC-08 are good candidates for automation because the rule will be touched again every sale season. Our regression testing strategy guide explains how to decide which ones to keep running.
Types of functional testing, using the same coupon
The "types" are mostly the same activity at a different level or moment. Here is what each one means for our feature (see levels of testing for the full picture):
- Unit testing: a developer tests the
calculateDiscount(subtotal)function on its own, including ₹500 and ₹2,000. - Integration testing: the cart service and the coupon service talk correctly; the API returns the discounted total.
- System testing: the whole checkout, in a browser, with real-looking data: our eight cases above.
- Smoke testing: after a new build, one quick check that SAVE10 still applies at all before deeper testing starts.
- Sanity testing: after the ₹500 fix, a narrow check of just that rule.
- Regression testing: rerunning all coupon cases after any checkout change.
- User acceptance testing (UAT): the product owner tries the coupon and confirms it matches what the business wanted.
Functional vs non-functional testing
Everything above checked whether the coupon does the right thing. It said nothing about whether it is fast, secure or easy to use. That is non-functional testing: the ISTQB glossary describes it as testing how well a system behaves in terms of quality characteristics such as performance, security and usability.
| Functional testing | Non-functional testing | |
|---|---|---|
| Question it answers | Does it do the right thing? | Does it do it well enough? |
| Based on | Functional requirements, acceptance criteria, business rules | Quality targets: speed, load, security, usability, accessibility |
| Coupon example | SAVE10 gives 10% off above ₹500 | Applying a code responds in under 1 second with 500 shoppers at once |
| Result | Usually pass or fail against an expected value | Usually a measurement against a threshold |
| Typical types | Unit, integration, system, smoke, sanity, regression, UAT | Performance, load, stress, security, usability, compatibility |
| When | From the first build onward | Once the feature is functionally stable (security and accessibility checks can start earlier) |
For the coupon, non-functional checks would include: can someone guess valid codes by trying thousands quickly (security, rate limiting)? Does the box work with a screen reader (accessibility)? Does applying a code stay fast on a big sale day (load)? The ISO/IEC 25010 product quality model is the usual reference for the full list of quality characteristics.
Manual or automated?
Both. The first pass on a new feature is usually manual, because you are still discovering questions like the ones in Step 1. Once the rule is settled, stable checks with exact expected values (our TC-01 to TC-08) are ideal for automation with a browser tool or directly at the API. A tool like Playwright can run all eight cases in seconds on every build. Exploratory sessions stay manual: they find the TC-08-style surprises that no one wrote down. If you're preparing for interviews, our Playwright interview questions show how this is asked in practice.
A reusable functional testing checklist
Use this for any feature, not just coupons:
- Have I listed every rule in the requirement, and asked about the ones it doesn't state?
- Does every number have tests just below, on and just above its edge?
- Have I tried empty, invalid, wrong-format and unexpected-case inputs?
- Have I tested what happens after the user changes something (edit, remove, back, refresh)?
- Is every expected result an exact value or message?
- Does every failed case have steps, input, expected and actual result?
- Which cases should become regression tests?
Want feedback on your own test cases? Share them with other testers in the QA Community.
How to answer "What is functional testing?" in an interview
Give the one-line definition, then a tiny example: "It checks the system against its functional requirements. For a 10%-off coupon above ₹500, I'd test ₹499, ₹500 and ₹501, the ₹200 cap, expired codes, and what happens when the cart drops below ₹500 after applying it." Then add the contrast: "Non-functional testing would check how fast and secure that coupon is." A concrete example shows you have actually tested something, which is what the interviewer is really checking.
Key takeaways
- 1Functional testing compares actual behaviour with the behaviour the requirements describe.
- 2Turn each requirement into test conditions first, then into concrete test cases with exact inputs and expected results.
- 3Boundary values found two of the three bugs in our coupon example; always test the edges of every number in a rule.
- 4Functional = does it do the right thing. Non-functional = does it do it fast, safely and usably enough.
- 5Unit, integration, system, smoke, regression and UAT are all functional testing done at different levels or moments.
Common mistakes to avoid
- Writing expected results like "discount applied correctly" instead of an exact value such as "total ₹450".
- Testing only the happy path and calling the feature done.
- Skipping state changes: what happens after the user edits the cart, goes back, or refreshes.
- Treating an unclear requirement as the tester's guess instead of asking the product owner.
- Reporting "coupon broken" instead of steps, input, expected and actual result.
