COMPUTER PROGRAMMING 1 (JAVASCRIPT) • PROBLEM SOLVING & MINI-PROJECTS

Testing Web Apps — I can test a web app by trying edge cases and fixing UI/logic bugs.

Learn to break your own code on purpose so your users never have to.

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'
Grace Hopper's team found an actual moth stuck in a relay of the Harvard Mark II computer. They taped it into their logbook, coining the term debugging — a word programmers still use every day.
1996
Ariane 5 Rocket Explosion
A $370 million rocket self-destructed 37 seconds after launch because of a single software conversion error that was never tested with the new rocket's flight data. This became a famous case study in why edge-case testing matters.
2000
The Y2K Scare
Programmers had stored years as two digits (e.g., '99'). Billions of dollars were spent testing and fixing systems before the year 2000 to prevent worldwide failures — a massive lesson in thinking about boundary values.
2010s
Rise of Automated Testing
As web apps became more complex, developers adopted frameworks like Jest, Mocha, and Selenium to run hundreds of tests automatically every time code changes. Testing became a standard part of the development workflow.
Today
Testing in Modern JavaScript
Professional teams write tests alongside their code. Companies often require that new features include tests before they can be merged. Learning to test is now a core programming skill, not an afterthought.

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?

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.

1

Edge Cases

Edge cases are unusual or extreme inputs that sit at the boundaries of what your app expects. Think: an empty string, a negative number, or a name with 500 characters. Most bugs hide at the edges, not in the middle.
2

UI Bugs vs. Logic Bugs

UI bugs are visual problems — a button that doesn't appear, text that overflows its container, or a form that looks broken on mobile. Logic bugs are errors in your code's reasoning — a calculation that returns the wrong answer or a condition that never triggers.
3

Manual vs. Automated Testing

Manual testing means you interact with your app like a user would, trying different inputs and checking results visually. Automated testing means writing code that checks your code — functions that run inputs and compare outputs to expected values.
4

Test Early, Test Often

The earlier you find a bug, the easier it is to fix. Testing after writing every small feature is far more effective than building the entire app and testing at the end. This principle is called incremental testing.
5

Reproducibility

A useful bug report includes exact steps to reproduce the problem. If you can't make a bug happen again on demand, it's nearly impossible to fix. Always note what you typed, clicked, or selected when you found the issue.
✦ KEY TAKEAWAY
Think of testing like proofreading an essay — but instead of checking for typos and grammar, you're checking for broken buttons and wrong calculations. Just as a good proofreader reads the essay from the reader's perspective (not the writer's), a good tester uses the app from the user's perspective, asking 'What weird thing might someone type here?' at every input field.

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.

The testing workflow is a cycle. After writing a feature (step 1), you identify test cases including edge cases (step 2), run each test (step 3), and observe the result (step 4). If a test fails, you debug and fix (step 5a), then loop back and re-test. If all tests pass, your feature is ready to ship (step 5b).

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.

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
function testAddition() { // Arrange let a = 5, b = 3; let expected = 8; // Act let result = add(a, b); // Assert console.log(result === expected ? '✓ PASS' : '✗ FAIL'); }
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
Empty → "" | Zero → 0 | Negative → −1 Very Large → 999999 | Special Chars → "<script>" Wrong Type → undefined, null, NaN
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()
JavaScript has a built-in assertion method: 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');

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.

This diagram contrasts the two main categories of web app bugs. UI bugs (left) are visual problems fixed with CSS and HTML tools, while logic bugs (right) are computational errors fixed by reviewing and correcting your JavaScript code.

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.

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 CaseEnter 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 InputLeave the bill field empty and click Calculate. The app shows Tip = $NaN and Total = $NaN. This is a logic bug. The code does parseFloat("") which returns NaN, and the code doesn't check for this.
✗ FAIL — Fix: add if (isNaN(bill) || bill <= 0) { alert('Please enter a valid bill amount'); return; }
3
Step 3 — Test Edge Case: Negative NumberEnter −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 added bill <= 0 to the check.
✓ PASS (after fix) — Negative input is now rejected with an alert.
4
Step 4 — Test Edge Case: Very Large NumberEnter 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 to overflow-wrap: break-word; or increase the container width.
5
Step 5 — Test Edge Case: Non-Numeric InputEnter "abc" as the bill. After our Step 2 fix, parseFloat("abc") returns NaN, 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 TestsAfter 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
We found two bugs with just five test cases. The empty-input test revealed a logic bug (no validation), and the large-number test revealed a UI bug (overflow). Both are common categories that appear in almost every web app. Building a habit of testing these edge cases will catch the majority of bugs before they reach your users.

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.

Manual vs. automated testing comparison
FeatureManual TestingAutomated Testing
SpeedSlow — you click through each case by handFast — runs hundreds of tests in seconds
Setup effortNone — just open the app and start clickingHigh — you need to write test code first
Catches UI bugsExcellent — your eyes see visual problems instantlyLimited — harder to test visual appearance
Catches logic bugsGood, but easy to miss subtle errorsExcellent — checks exact values precisely
RepeatabilityLow — humans forget steps or get tiredHigh — runs identically every time
Best forSmall apps, visual layout, user experienceLarge apps, complex logic, regression testing
✦ KEY TAKEAWAY
Manual and automated testing are like spell-check and proofreading — you need both. Spell-check (automated testing) catches typos instantly and consistently, but it can't tell you if your sentence sounds awkward. Proofreading (manual testing) catches style and flow issues that tools miss. In this course, you'll mostly do manual testing, but knowing that automated testing exists prepares you for the next level.

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.

From beginner to professional testing practices
What You're Learning NowProfessional Version
Trying edge cases by hand in the browserUnit testing with Jest — functions test each edge case automatically
Checking if buttons and forms work correctlyEnd-to-end testing with Cypress — a robot browser clicks through your app
Using console.log() to check valuesDebugger tools — set breakpoints and step through code line by line
Asking a friend to try your appCode review — teammates read each other's code and suggest fixes before merging
Re-testing after every fixContinuous Integration (CI) — automated tests run every time code is pushed to GitHub

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.

Practice Problems

PROBLEM 1 — CONCEPTUAL
A student builds a web app that converts temperatures from Fahrenheit to Celsius. They test it with 32°F (which should give 0°C) and 212°F (which should give 100°C), and both work. They declare the app 'fully tested.' Name two edge cases they should also try, and explain why each could reveal a bug.
PROBLEM 2 — BASIC CALCULATION
A function 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?
PROBLEM 3 — INTERMEDIATE
You have a to-do list app. The following function adds a task: 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.
PROBLEM 4 — APPLIED
You're building a quiz app that tracks a student's score. After answering 10 questions, the app shows a results screen with the message: 'You scored [X] out of 10. Your grade is [A/B/C/D/F].' The grading logic is: A ≥ 90%, B ≥ 80%, C ≥ 70%, D ≥ 60%, F < 60%. Design a complete set of test cases (at least 6) that tests both normal cases and edge cases. For each test, specify the score input and the expected grade output.
PROBLEM 5 — CRITICAL THINKING
A classmate says: 'I tested my web app with 50 different inputs and everything works, so my app is bug-free.' Explain why this claim is problematic, even with extensive testing. Then describe a strategy for deciding when an app has been tested 'enough' even though you can't test every possible input.

Lesson 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.

Varsity Tutors • Computer Programming 1 (JavaScript) • Testing Web Apps — I can test a web app by trying edge cases and fixing UI/logic bugs.