SECTION 1
Why Testing Exists: A Short History of Software Bugs
Every app you use — from Instagram to Google Docs — has been tested thousands of times before it reached your screen. Software testing is the practice of deliberately trying to make your program fail so you can find and fix problems before real users encounter them. The concept might seem counterintuitive — why would you try to break something you just built? — but history shows that untested software can cause real damage, from lost data to crashed spacecraft.
1947 • The First Literal 'Bug'
1996 • Ariane 5 Rocket Explosion
2000 • The Y2K Scare
2010s • Rise of Automated Testing
Today • Testing in Modern JavaScript
These moments reveal a common thread: bugs are inevitable, but catastrophic failures are preventable. The key question this lesson addresses is: How do you systematically find the inputs and interactions that will break your web app before your users do?
SECTION 2
Core Principles of Testing Web Apps
Testing a web app isn't just randomly clicking around and hoping something breaks. Effective testing is guided by a set of principles that help you think like a detective — searching for the specific conditions that expose hidden problems. Let's break down the foundational ideas.
Edge Cases
UI Bugs vs. Logic Bugs
Manual vs. Automated Testing
Test Early, Test Often
Reproducibility
KEY TAKEAWAY
SECTION 3
The Testing Workflow: A Visual Guide
Testing follows a cycle: you identify what to test, run the test, observe the result, and either fix the bug or move on. The diagram below shows the complete testing workflow for a web app, from writing a feature to shipping it with confidence.
Notice the loop on the left side of the diagram. When a test fails, you don't just shrug and move on — you trace the problem back to your code, fix it, and then run all your tests again. This is crucial because fixing one bug can sometimes accidentally create another. The loop continues until every test passes cleanly.
SECTION 4
How Testing Works in JavaScript
In JavaScript web apps, testing happens at multiple levels. At the simplest level, you can test by using console.log() to print values and check them visually. But the real power comes from writing structured test cases — small functions that call your code with specific inputs and check that the output matches what you expect.
The Anatomy of a Test Case
Every test case has three parts, sometimes called Arrange, Act, Assert. First, you arrange the setup — prepare the input data and any initial state. Second, you act — call the function or interact with the UI element you're testing. Third, you assert — check whether the actual result matches the expected result.
BASIC TEST PATTERN
This pattern works for any function. Replace add(a, b) with whatever function you're testing, set expected to the correct answer, and the assert line tells you if it worked.
Generating Edge Case Inputs
The hardest part of testing isn't writing the test — it's choosing which inputs to test. A technique called boundary value analysis guides this process. For any input, you test the normal case, the boundaries (minimum and maximum valid values), and just outside the boundaries (invalid values). For a text field that accepts 1–50 characters, you'd test inputs of length 0, 1, 25, 50, and 51.
EDGE CASE CATEGORIES
These six categories cover the most common edge cases in web apps. Whenever you build a feature that accepts user input, run through each category and ask: What happens if someone enters this?
Pro Tip: console.assert()
console.assert(condition, message). It prints the message only when the condition is false. This is a quick way to add lightweight tests: console.assert(add(2,3) === 5, 'add(2,3) should be 5');SECTION 5
Classifying Bugs: UI Bugs vs. Logic Bugs
When you find something wrong in your web app, the first step is identifying what kind of bug you're dealing with. This matters because UI bugs and logic bugs require different debugging strategies. A UI bug means the code might be working correctly behind the scenes but the display is wrong. A logic bug means the underlying calculation or decision is flawed, even if the page looks fine.
When you encounter a bug, ask yourself: Is the right data getting to the screen in the wrong way, or is the wrong data getting to the screen? If you add console.log() statements to check the value of your variables and the values are correct, your bug is probably a UI bug. If the values themselves are wrong, you have a logic bug. This simple test saves you from looking in the wrong place.
SECTION 6
Worked Example: Testing a Tip Calculator
Let's walk through testing a simple tip calculator web app. The app has an input field for the bill amount, a dropdown for tip percentage (15%, 18%, or 20%), and a button that displays the tip amount and total. Our goal is to find and fix both UI and logic bugs by trying edge cases.
Testing & Debugging a Tip Calculator
- 1
Step 1 — Test the Normal Case
Enter a typical bill: $50.00, select 20% tip, click Calculate. We expect the tip to be $10.00 and the total to be $60.00. The app displays: Tip = $10.00, Total = $60.00.✓ PASS — Normal case works correctly. - 2
Step 2 — Test Edge Case: Empty Input
Leave the bill field empty and click Calculate. The app showsTip = $NaNandTotal = $NaN. This is a logic bug. The code doesparseFloat("")which returnsNaN, and the code doesn't check for this.✗ FAIL — Fix: addif (isNaN(bill) || bill <= 0) { alert('Please enter a valid bill amount'); return; } - 3
Step 3 — Test Edge Case: Negative Number
Enter −25 as the bill amount. The app calculates a tip of −$5.00. A negative tip doesn't make sense. This is the same logic bug — our validation should reject negative numbers too. The fix from Step 2 handles this because we addedbill <= 0to the check.✓ PASS (after fix) — Negative input is now rejected with an alert. - 4
Step 4 — Test Edge Case: Very Large Number
Enter 99999999.99 as the bill. The tip calculates correctly (the math works with large numbers), but the result text overflows the display box and gets cut off. This is a UI bug. The container has a fixed width that's too narrow for long dollar amounts.✗ FAIL — Fix: change CSS tooverflow-wrap: break-word;or increase the container width. - 5
Step 5 — Test Edge Case: Non-Numeric Input
Enter "abc" as the bill. After our Step 2 fix,parseFloat("abc")returnsNaN, which triggers our validation. The alert appears as expected.✓ PASS — Non-numeric input is properly caught by our validation. - 6
Step 6 — Re-run All Tests
After fixing the two bugs, we run all five test cases again. The normal case still works (our fixes didn't break it), empty input shows an alert, negative numbers show an alert, large numbers display correctly, and non-numeric input shows an alert. All tests pass.✓ ALL TESTS PASS — The tip calculator is ready for users.
Notice the Pattern
SECTION 7
Manual Testing vs. Automated Testing
So far, we've focused on manual testing — you the developer acting as a user. This is a great starting point, but as your apps grow larger, manual testing gets slow and error-prone. Professional developers use automated testing to complement manual testing. Let's compare both approaches.
| Feature | Manual Testing | Automated Testing |
|---|---|---|
| Speed | Slow — you click through each case by hand | Fast — runs hundreds of tests in seconds |
| Setup effort | None — just open the app and start clicking | High — you need to write test code first |
| Catches UI bugs | Excellent — your eyes see visual problems instantly | Limited — harder to test visual appearance |
| Catches logic bugs | Good, but easy to miss subtle errors | Excellent — checks exact values precisely |
| Repeatability | Low — humans forget steps or get tired | High — runs identically every time |
| Best for | Small apps, visual layout, user experience | Large apps, complex logic, regression testing |
Manual vs. automated testing comparison
KEY TAKEAWAY
SECTION 8
Connection to Professional Testing Practices
The manual testing skills you're learning now form the foundation for more advanced techniques used in the software industry. As you progress in your programming journey, you'll encounter formal testing frameworks that structure and automate the process. Here's how what you're learning connects to professional practices.
| What You're Learning Now | Professional Version |
|---|---|
| Trying edge cases by hand in the browser | Unit testing with Jest — functions test each edge case automatically |
| Checking if buttons and forms work correctly | End-to-end testing with Cypress — a robot browser clicks through your app |
| Using console.log() to check values | Debugger tools — set breakpoints and step through code line by line |
| Asking a friend to try your app | Code review — teammates read each other's code and suggest fixes before merging |
| Re-testing after every fix | Continuous Integration (CI) — automated tests run every time code is pushed to GitHub |
From beginner to professional testing practices
The key idea is that the thinking behind testing doesn't change as you advance — you still ask 'What could go wrong?' and 'What inputs haven't I tried?' The tools get more powerful, but the mindset you're building now is exactly what professional developers use every day. When you eventually learn a framework like Jest or Cypress, the transition will feel natural because you already understand why testing matters and how to choose good test cases.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
parseFloat("") returns NaN, which would display as 'NaN°C' on the page. (2) A very large negative number like −999999 — while mathematically valid, the result might overflow the display container (a UI bug) or the student's formula might not handle negative Fahrenheit values correctly if they used absolute value somewhere by mistake (a logic bug). Testing only 'happy path' normal cases leaves many potential failures undiscovered.Question 2: BASIC CALCULATION
calculateDiscount(price, percent) is supposed to return the discounted price. The code is: function calculateDiscount(price, percent) { return price - price * percent; }. A developer tests calculateDiscount(100, 0.2) and gets 80 — correct! But when a user enters 20 for 20%, the function returns −1900. What's the logic bug, and how would you fix it?Show answer
return price - price * (percent / 100);. Alternatively, you could validate and convert the input before calling the function. This is a classic edge case where the developer's assumptions about input format don't match the user's expectations.Question 3: INTERMEDIATE
function addTask(taskText) { let li = document.createElement('li'); li.textContent = taskText; document.getElementById('task-list').appendChild(li); }. List three edge-case inputs you would test, predict what would happen for each, and explain whether each result is a bug or acceptable behavior.Show answer
if (taskText.trim() === '') return; at the top. (2) A very long string of 1000+ characters — the task would be added and might overflow the list container or push other elements off screen (a UI bug). Fix: truncate the text or set overflow-wrap: break-word in CSS. (3) HTML tags like <b>test</b> — since the code uses textContent (not innerHTML), the tags would be displayed as literal text, which is actually safe and acceptable behavior. This is a good security practice.Question 4: APPLIED
Show answer
Question 5: CRITICAL THINKING
Show answer
SUMMARY
Lesson Summary
Summary
Testing a web app means systematically trying inputs — especially edge cases like empty strings, negative numbers, and extremely large values — to find problems before your users do. Bugs fall into two categories: UI bugs (visual problems with how things appear on screen) and logic bugs (errors in your code's calculations or decisions). You can distinguish them by using console.log() to check whether the data is correct — if it is, the bug is in the display; if not, the bug is in the logic.
Every test follows the Arrange, Act, Assert pattern: set up your input, run the code, and check the output against what you expected. Use boundary value analysis to choose smart test inputs — test the minimum, maximum, and just-outside-valid-range values. Remember to re-test everything after every fix, because a change that solves one bug can introduce another. These manual testing skills are the foundation for the automated testing tools you'll encounter later in your programming journey.