Bug Life Cycle & Bug Reports
Finding a bug is only half the job. If the developer cannot understand or reproduce it, the bug will not be fixed. A bug report (also called a defect report or incident report) turns what you saw into something a developer can act on, and the bug life cycle tracks that report from the moment you log it until it is closed.
In this lesson you will learn the statuses a bug moves through, who moves it, how to write a report developers love to receive, and the common reasons reports bounce back.
The bug life cycle
Every bug tracker (Jira, Azure DevOps, GitHub Issues, Bugzilla, Redmine) lets a team define its own workflow, but most follow a pattern like this:
| Status | Meaning | Usually set by |
|---|---|---|
| New | Just reported, not yet reviewed | Tester |
| Assigned | A lead has reviewed it and given it to a developer | Test or dev lead |
| Open (In progress) | The developer is working on it | Developer |
| Fixed (Resolved) | The developer has changed the code | Developer |
| Retest (Ready for QA) | The fix is in a build the tester can use | Developer or build team |
| Verified | The tester confirmed the fix works | Tester |
| Closed | Nothing more to do | Tester or lead |
| Reopened | The fix did not work, or the bug came back | Tester |
| Rejected (Not a bug) | Behaviour is correct, or cannot be reproduced | Lead or developer |
| Duplicate | Already reported in another ticket | Lead |
| Deferred | Real, but will be fixed in a later release | Product owner or lead |
The happy path is New, Assigned, Open, Fixed, Retest, Verified, Closed. The side paths exist because not every report leads to a code change. A rejected bug is not a failure for the tester; often it reveals a gap in the requirements that the team must clarify.
Modelling the workflow
A workflow is a state machine: each status allows only some next statuses. This model follows one bug through a fix that failed retest, was reopened and was finally closed:
from dataclasses import dataclass, field
FLOW = {
"New": ["Assigned", "Rejected", "Duplicate", "Deferred"],
"Assigned": ["Open"],
"Open": ["Fixed", "Rejected", "Deferred"],
"Fixed": ["Retest"],
"Retest": ["Verified", "Reopened"],
"Reopened": ["Assigned"],
"Verified": ["Closed"],
"Rejected": ["Closed", "Reopened"],
"Duplicate": ["Closed"],
"Deferred": ["Assigned"],
"Closed": [],
}
@dataclass
class Bug:
id: str
title: str
status: str = "New"
history: list = field(default_factory=list)
def move(self, new_status, by):
if new_status not in FLOW[self.status]:
raise ValueError(f"{self.id}: cannot go from {self.status} to {new_status}")
self.history.append(f"{self.status} -> {new_status} ({by})")
self.status = new_status
bug = Bug("BUG-214", "Coupon FEST10 applied twice on refresh")
for status, who in [("Assigned", "Meera, lead"), ("Open", "Rohan, dev"), ("Fixed", "Rohan, dev"),
("Retest", "Rohan, dev"), ("Reopened", "Priya, QA"), ("Assigned", "Meera, lead"),
("Open", "Rohan, dev"), ("Fixed", "Rohan, dev"), ("Retest", "Rohan, dev"),
("Verified", "Priya, QA"), ("Closed", "Priya, QA")]:
bug.move(status, who)
print(bug.title, "is now", bug.status)
print("Times reopened:", sum("Reopened" in h.split(" -> ")[1] for h in bug.history))
try:
bug.move("Open", "Rohan, dev")
except ValueError as error:
print("Refused:", error)Coupon FEST10 applied twice on refresh is now Closed Times reopened: 1 Refused: BUG-214: cannot go from Closed to Open
The history list is what real trackers show in a ticket's activity log. Teams watch numbers such as "times reopened" because a high count points to rushed fixes or unclear reports.
Anatomy of a good bug report
A useful report answers four questions: what happened, where, how to make it happen again, and why it matters. Its fields are:
- ID: given by the tracker.
- Title (summary): what goes wrong, where, and under which condition, in one line.
- Environment: device, operating system, browser or app version, build number, test server, network.
- Preconditions and test data: the account, cart contents or settings needed.
- Steps to reproduce: numbered, one action each, starting from a known state.
- Expected result: what should happen, ideally quoting the requirement.
- Actual result: what really happened, with exact messages and values.
- Severity and priority: impact and urgency (see Severity vs Priority).
- Attachments: screenshot, screen recording, logs, API response.
- Reproducibility: always, sometimes (for example 3 of 10 tries), or once.
Compare two titles for the same bug:
Bad: Coupon not working
Good: Coupon FEST10 is applied twice when the cart page is refreshedThe bad title could mean a dozen different problems. The good one tells the developer where to look before they even open the ticket.
Many teams check reports before submitting them. This script does a quick quality check on a report stored as data:
const report = {
id: "BUG-214",
title: "Coupon FEST10 is applied twice when the cart page is refreshed",
environment: "Android 15, app 4.2.1, build 2026.10.28, 4G",
steps: [
"Log in as priya.test@example.com",
"Add 'Kashmir willow bat' (₹2,499) to the cart",
"Apply coupon FEST10",
"Pull down to refresh the cart page",
],
expected: "Total stays ₹2,249.10 (10% off once)",
actual: "Total becomes ₹2,024.19 (10% off applied twice)",
severity: "Major",
priority: "High",
attachments: ["cart-refresh.mp4", "cart-response.json"],
};
const required = ["title", "environment", "steps", "expected", "actual", "severity", "priority"];
const missing = required.filter((f) => !report[f] || report[f].length === 0);
const vagueTitle = /not working|broken|issue|bug/i.test(report.title);
console.log(missing.length ? `Missing: ${missing.join(", ")}` : "All required fields present");
console.log(vagueTitle ? "Title is vague" : "Title says what, where and when");
console.log(`Steps: ${report.steps.length}, attachments: ${report.attachments.length}`);All required fields present Title says what, where and when Steps: 4, attachments: 2
Notice the expected and actual results use exact amounts. "Total is wrong" would force the developer to redo your work to discover how it is wrong.
Tips that get bugs fixed
- Reproduce before reporting. Try at least twice. If it happens only sometimes, say how often and what you varied.
- Find the smallest steps. Remove every step that is not needed to trigger the bug.
- One bug per report. Two problems in one ticket get half fixed and fully closed.
- Search for duplicates first. Add your details to an existing ticket instead of creating a new one.
- Stay factual and polite. Describe the behaviour, not the developer. "The total is doubled" is helpful; "careless coding again" is not.
- Attach evidence. A 20-second screen recording often replaces a page of text. For APIs, attach the request and response, with passwords and tokens removed.
Common mistakes
- Vague titles such as "App crash" or "Login issue".
- Missing environment details. A bug on Android 15 with app 4.2.1 may not exist on iOS or on build 4.2.0.
- "Expected: it should work." Write what working looks like.
- Closing your own bug without retesting. Always verify the fix in the build where it was delivered.
Key points
- The bug life cycle typically runs New, Assigned, Open, Fixed, Retest, Verified, Closed, with side paths for Rejected, Duplicate, Deferred and Reopened.
- Each team configures its workflow in its tracker; learn your team's statuses and who sets them.
- A good report has a specific title, environment, preconditions, numbered steps, expected and actual results, severity, priority and evidence.
- Reproduce, minimise, and report one bug per ticket.
- Reopen a bug only when the fix really fails, and explain what you saw in the retest.
Exercise
You find that the "Download receipt" button on a school fees portal produces a PDF showing the previous month's fee (₹4,500) instead of this month's (₹4,800). It happens every time on Chrome on Windows 11, portal version 3.8.2, for the test account parent.isha@example.com. Write a complete bug report with a title, environment, preconditions, steps, expected result, actual result, severity and priority.
Show solution
Title: Fee receipt PDF shows previous month's amount after paying current month's fee
Environment: Windows 11, Chrome (current stable), portal v3.8.2, test server
Preconditions: Account parent.isha@example.com has paid the October fee of Rs 4,800
Steps to reproduce:
1. Log in as parent.isha@example.com
2. Open Fees > Payment history
3. Click "Download receipt" on the October payment
4. Open the downloaded PDF
Expected result: The receipt shows October 2026 and amount Rs 4,800
Actual result: The receipt shows September 2026 and amount Rs 4,500
Reproducibility: Always (5 of 5 tries)
Severity: Major (wrong financial document, no workaround in the portal)
Priority: High (parents need receipts for school and tax records)
Attachments: receipt-october.pdf, screenshot-payment-history.pngThe title names the feature, the wrong behaviour and the condition, and the expected and actual results quote exact values so the developer can confirm the fix quickly.
Quick Quiz
1. A developer has changed the code to fix a bug. Which status usually comes next?
The fix must reach a testable build and be retested before the bug can be verified and closed.
2. When should a tester reopen a bug?
Reopen only with evidence from a retest in the build that contains the fix.
3. Which bug title is best?
A good title states what goes wrong, where and under which condition.
4. What does "Deferred" mean?
Deferred bugs are accepted as real but postponed, usually by the product owner.
Learn Software Testing with a mentor
Free lessons get you started. Our Software Testing training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
