Testing and Quality Basics
Before you start
You need: acceptance criteria (Article 02) and environments (Article 04).
Time: about 45 minutes, plus the practice.
Learning objective
Design test scenarios that cover more than the happy path, and write a defect report a developer can act on immediately.
Topics
- Verification and validation
- Test levels
- The three error classes
- Designing scenarios
- Boundary and negative testing
- Regression
- Writing a defect report
- Severity and priority
- The pre-handoff checklist
Verification and validation
| Question | Answered by | |
|---|---|---|
| Verification | Did we build it right? | Tests against the acceptance criteria |
| Validation | Did we build the right thing? | UAT — the users |
Software can pass every test and still be wrong. The fee report may calculate exactly what was specified, and the specification may not be what the accounts team needed. Verification cannot catch that; validation can.
Both are needed. QA verifies; the office administrator validates.
Test levels
| Level | Tests | Who writes it | Speed |
|---|---|---|---|
| Unit | One method in isolation | Developer | Milliseconds |
| Integration | Several parts together — service and database | Developer | Seconds |
| System / end-to-end | The whole flow through the UI | QA | Minutes |
| User acceptance | Real users, real scenarios | Business users | Manual |
// Unit — the grading rule alone, no database
[Fact]
public void GetResult_AbsentStudent_ReturnsAbsent()
{
ExamResult result = new ExamResult();
result.IsAbsent = true;
result.MarksObtained = null;
Subject subject = new Subject();
subject.PassingMarks = 35;
string outcome = new GradingService().GetResult(result, subject);
Assert.Equal("Absent", outcome);
}
Most tests should be unit tests. They are fast, they pinpoint the failure, and they run on every build. End-to-end tests are valuable but slow and brittle — a hundred of them takes twenty minutes and one UI change breaks thirty.
A test that needs a database and a browser to check a grading rule is testing the wrong thing at the wrong level.
The three error classes
| Class | When it appears | Found by |
|---|---|---|
| Compiler error | Before the program runs | The compiler |
| Runtime error | While running | An exception, a crash |
| Logical error | Never announces itself | Testing, or a user complaint |
int marks = "85"; // compiler — never runs
int marks = int.Parse(rollNumber); // runtime — FormatException
decimal balance = total + paid; // logical — should be minus
The logical error is the dangerous one. It compiles, it runs, it produces a plausible number, and only a person checking the arithmetic notices. A student who has paid ₹12,000 against ₹15,000 shows a balance of ₹27,000 rather than ₹3,000.
Testing exists primarily for the third class. The first two announce themselves.
Designing scenarios
Most defects live outside the happy path. For "search students by roll number":
| Category | Scenario | Expected |
|---|---|---|
| Happy path | Search NCA-2024-0012, which exists | That student is returned |
| Empty | Search with an empty box | Validation message, no request |
| No match | Search NCA-9999-9999 | "No students found" |
| Whitespace | Search " NCA-2024-0012 " | Same as trimmed — found |
| Case | Search ravi and RAVI | Same results |
| Partial | Search Rav | All active students matching |
| Special characters | Search O'Brien | Found; no error |
| Very long input | 500 characters | Handled, no crash |
| Inactive student | Search a transferred student | Excluded unless requested |
| Other school | Search a roll number from School 2 | Not found |
| Unauthorised | Search as a Student role | 403 |
| Volume | 800 students | Under two seconds |
Twelve scenarios; one is the happy path. That ratio is normal.
"Other school" is the scenario nobody writes and everybody needs. Testing that something is not returned is as important as testing that something is — and it is the only way to catch a missing tenant filter, which produces no error.
The apostrophe test is not pedantry. O'Brien breaks string-concatenated SQL, and that is how SQL injection is discovered by accident.
Boundary and negative testing
Defects cluster at boundaries. For a passing mark of 35 out of 100:
| Input | Expected | Why test it |
|---|---|---|
| 34 | Fail | Just below |
| 35 | Pass | Exactly on |
| 36 | Pass | Just above |
| 0 | Fail | Minimum |
| 100 | Pass | Maximum |
| 101 | Rejected | Above maximum |
| -1 | Rejected | Below minimum |
| null | Absent | The absent case |
>= written as > fails only at exactly 35. A test at 90 and a test at 20 both pass, and a student on exactly the pass mark is failed. This is the single most common off-by-one defect, and one boundary test catches it.
Negative testing is checking that the wrong things are rejected:
- Letters in a marks field
- A negative fee payment
- A payment larger than the balance
- A duplicate roll number
- A teacher requesting the salary report
- A request with an expired token
A system that accepts a payment of −₹5,000 has passed every positive test.
Regression
A regression is something that used to work and no longer does.
Sprint 13: Fee receipt printing works
Sprint 14: Discount handling added
Sprint 14: Fee receipt printing now omits the discount line
The new feature broke the old one. Nobody tested the old one, because it was already working.
This is why automated tests matter. Manually retesting everything each sprint is impossible; a test suite does it in ninety seconds on every commit.
A regression checklist for the School system — the flows that must work in every release:
[ ] Sign in as each role
[ ] Student list loads and is filtered to the signed-in school
[ ] Student search by roll number and by name
[ ] Add a student; duplicate roll number is rejected
[ ] Record a fee payment; the balance updates
[ ] Fee receipt prints with the discount line
[ ] Enter exam marks, including one absent student
[ ] Result card shows Absent, not Fail, for that student
[ ] Class report average excludes absentees
[ ] Teacher role cannot open the salary report
Write this list once and run it every release. Automate what you can; the rest takes twenty minutes and prevents the phone call.
Writing a defect report
Title Class average includes absent students as zero
Environment QA, build 2.4.0-rc3
Role Signed in as principal@nca.test (School 1)
Steps 1. Open Exams → Sprint Assessment → 10th-A
2. Mark 5 of the 40 students absent
3. Enter marks for the remaining 35
4. Open the class result report
Expected Average calculated over the 35 students who appeared
Actual Average calculated over all 40, counting absentees as 0
Report shows 54.2; correct value is 61.9
Evidence Screenshot attached
SQL: SELECT AVG(MarksObtained) FROM ExamResult
WHERE ExamId = 5 AND IsAbsent = 0 → 61.9
Frequency Every time; reproduced 3 times
Scope Every class with at least one absent student
Since Build 2.4.0-rc1; worked in 2.3.2
Severity High — printed report cards will show wrong averages
| Field | Why it matters |
|---|---|
| Steps | Without them the developer cannot reproduce it |
| Expected | Without it they must guess what correct means |
| Actual | With the real numbers, not "it's wrong" |
| Evidence | A screenshot or a query turns opinion into fact |
| Frequency | Every time, or one in ten — different causes |
| Scope | One class or all of them |
| Since | Which build — makes it findable with git bisect |
"Since 2.4.0-rc1; worked in 2.3.2" is the most valuable line in the report. It converts an open investigation into a search through a known set of commits.
A defect report without "expected" is not a report; it is a complaint.
Severity and priority
| Means | Decided by | |
|---|---|---|
| Severity | How bad the impact is | QA |
| Priority | How soon it must be fixed | Product Owner |
They are independent.
| Example | Severity | Priority |
|---|---|---|
| Cross-school data exposure | Critical | Immediate |
| Class average wrong | High | High — report cards are due |
| Logo misaligned on the receipt | Low | Low |
| Typo on the login page | Low | High — every user sees it |
| Crash in a feature nobody uses yet | Critical | Low |
A high-severity, low-priority defect is a normal outcome, and so is the reverse. Arguing that severity should drive the order is arguing with the person whose job it is to decide.
The pre-handoff checklist
Before you hand anything to QA:
- Every acceptance criterion works — check them one by one
- The empty case, the no-match case and the error case
- At least one boundary value
- One negative case — invalid input is rejected
- Signed in as a role that should not have access
- Data from another school is not returned
- The absent case, wherever marks are involved
- No
Console.WriteLineor debug code left in - Tests written and passing
- Run once from a clean state, as a new user would
Ten minutes here saves a round trip that takes two days. A defect found by QA goes into the tracker, gets triaged, comes back, is fixed, is redeployed and is retested. The same defect found by you is a one-line change before you open the pull request.
"It works" usually means "the happy path works". Check the other eleven scenarios.
Where this goes wrong
| The mistake | What it misses |
|---|---|
| Testing only the happy path | Eleven of twelve realistic scenarios |
| No boundary test at the pass mark | > written for >= fails only at exactly 35 |
| Never testing that something is not returned | A missing tenant filter, which produces no error |
| Not testing an unauthorised role | A teacher reaching the salary report |
| A defect report with no "expected" | The developer has to guess what correct means |
| "It's wrong" instead of the numbers | Nobody can reproduce it |
| Not recording which build broke it | An open-ended search instead of ten commits |
| Assuming old features still work | Regressions ship every sprint |
Testing that something is absent is as important as testing it is present, and it is the check almost nobody writes.
Common mistakes
- Testing only the happy path
- No boundary test at the exact pass mark
- Never testing that something is not returned
- Not testing with another school's data
- Not testing an unauthorised role
- Defect reports with no expected result or no steps
- "It's wrong" instead of the actual numbers
- Not recording which build introduced the defect
- Confusing severity with priority
- Assuming existing features still work
- Handing over without running the acceptance criteria yourself
Practice
The course exercises are classify compiler, runtime and logical errors and read a simple API error response.
- Write one example each of a compiler, runtime and logical error in the School system.
- Explain why only the third needs testing to find.
- Design twelve test scenarios for "record a fee payment", covering happy path, empty, boundary, negative, unauthorised and cross-school.
- For a passing mark of 35, list every boundary value you would test and say what
>instead of>=would break. - Write five negative test cases for the fee payment form.
- Take a feature you have built and write its regression checklist.
- Write a defect report for the class-average bug, with every field filled in.
- Rewrite a report you would normally send — "the report is wrong" — into a usable one.
- Assign severity and priority to five defects, including one where they diverge sharply.
- Explain why a typo on the login page can be low severity and high priority.
- Run the pre-handoff checklist against your last piece of work and note what it catches.
- Find an API that returns a 400 and read its response body. Say what the caller did wrong.
- Test a form as an unauthorised role, using the browser's Network tab to call the endpoint directly.
Exercise 3 is the one to repeat for every feature you build. Exercise 11 is the habit that makes QA like you.
You can now
- Design scenarios covering boundaries, negatives and permissions
- Test that the wrong data is not returned
- Write a defect report with steps, expected, actual and scope
- Tell severity from priority
- Build a regression checklist for a feature
- Check your own work before handing it to QA
Review questions
- What does validation catch that verification cannot?
- Why does the boundary at exactly the passing mark deserve its own test?
- Why is testing that something is not returned as important as testing that it is?
- How can a defect be high severity and low priority at the same time?
Next: Release and support