Module 2: Operators & Control Flow

Java for Testers · Module 2

Operators & Control Flow: Making Decisions and Repeating Work

So far your programs have run straight down the page, one line after another. Real programs—and real tests—need to make choices (“if the login succeeded, check the dashboard; otherwise record a failure”) and repeat work (“for each row in the results table, verify the price”). This module gives you both powers. You will learn the operators that let Java calculate and compare, then the control-flow statements—if/else, switch, and the three kinds of loop—that let a program branch and repeat. These are the structures that turn a static script into something that reacts to what it finds, which is the heart of every automated test.

Learning objectives

By the end of this lesson you will be able to:

  • Use arithmetic, relational, logical, and assignment operators correctly.
  • Reason about operator precedence and use parentheses to make intent clear.
  • Write if, else if, and else decisions, and use the ternary operator for short choices.
  • Use a switch statement (and the modern switch expression) for multi-way branching.
  • Repeat work with for, while, and do-while loops.
  • Control loops with break and continue, and combine them with nested loops.
  • Connect these structures to real test logic, such as iterating over elements on a page.

Operators

An operator is a symbol that performs an operation on one or more values (called operands) and produces a result. Java groups them into a few families.

Arithmetic operators

These do maths: + (add), - (subtract), * (multiply), / (divide), and % (modulus—the remainder after division).

int a = 17, b = 5;
System.out.println(a + b);   // 22
System.out.println(a - b);   // 12
System.out.println(a * b);   // 85
System.out.println(a / b);   // 3   (integer division: the remainder is discarded)
System.out.println(a % b);   // 2   (17 divided by 5 leaves a remainder of 2)

The modulus operator % is more useful than it first looks. number % 2 == 0 is the standard way to test whether a number is even. In testing you might use it to do something on every third row, or to split work across parallel threads.

Relational (comparison) operators

These compare two values and always produce a boolean (true or false): == (equal to), != (not equal to), >, <, >=, <=.

int score = 72;
System.out.println(score >= 50);   // true  (did they pass?)
System.out.println(score == 100);  // false
System.out.println(score != 72);   // false

Note the double equals == for comparison. A single = means assignment (“put this value in the box”), which is a different thing entirely—mixing them up is a classic bug.

Logical operators

These combine boolean values: && (AND—true only if both sides are true), || (OR—true if either side is true), and ! (NOT—flips true to false).

boolean loggedIn = true;
boolean isAdmin  = false;

System.out.println(loggedIn && isAdmin);   // false (needs both)
System.out.println(loggedIn || isAdmin);   // true  (either will do)
System.out.println(!isAdmin);              // true  (flips false to true)

Both && and || are “short-circuiting”: Java stops evaluating as soon as the answer is certain. In a && b, if a is false, b is never checked. This is not just an optimisation—it is a safety tool. You can write if (element != null && element.isDisplayed()) knowing the second check is skipped when the element is null, avoiding a crash.

Assignment operators

Beyond the basic =, Java has shorthand combinations: +=, -=, *=, /=, and the increment/decrement operators ++ and --.

int total = 10;
total += 5;    // same as total = total + 5;  -> 15
total -= 3;    // 12
total++;       // 13  (increment by 1)
total--;       // 12  (decrement by 1)

You will see ++ constantly as the step in for loops below.

Operator precedence: the maths you already know

You learned precedence years ago in arithmetic—“multiply before you add”. Java follows the same idea. In 2 + 3 * 4, the multiplication happens first, giving 14, not 20. The rough order is: ! and ++/-- first, then * / %, then + -, then the relational operators, then &&, then ||, and assignment last. But here is the professional’s secret: do not memorise the full table. When precedence is not blindingly obvious, add parentheses. (price * quantity) > budget is clearer than relying on the rules, and the compiler and your future self both thank you. Parentheses cost nothing and remove all doubt.

The if / else if / else statement

The if statement runs a block of code only when a condition is true. Add else if for more conditions and else for the fallback when none matched. Conditions go in parentheses; the conditional code goes in braces.

int score = 72;

if (score >= 90) {
    System.out.println("Grade: A");
} else if (score >= 75) {
    System.out.println("Grade: B");
} else if (score >= 50) {
    System.out.println("Grade: C");
} else {
    System.out.println("Grade: Fail");
}
// Prints "Grade: C" because 72 is >= 50 but not >= 75

Java checks the conditions top to bottom and runs the first one that is true, then skips the rest. Order matters: if you put score >= 50 first, a score of 95 would wrongly print “C”. In a test, this is exactly how you might branch on an HTTP status code or a page state.

Common mistakes with conditions

  • Using = instead of ==. if (x = 5) is assignment, not comparison. With int this won’t even compile, which is lucky—but the habit of writing == for comparison is essential.
  • Comparing Strings with ==. For text, always use .equals(): write if (status.equals("PASS")), not if (status == "PASS"). Using == compares memory references, not the actual characters, and gives unreliable results.
  • The dangling-brace trap. If you omit braces, only the next single line is governed by the if. Always use braces, even for one line—it prevents a whole class of bugs when you add a second line later.
  • A stray semicolon: if (x > 0); ends the statement immediately, so the block that follows always runs. Watch for that misplaced ;.

The ternary operator

For a simple either/or choice, the ternary operator ? : is a compact shorthand for an if/else that produces a value. Read it as “condition ? value-if-true : value-if-false”.

int score = 72;
String result = (score >= 50) ? "PASS" : "FAIL";
System.out.println(result);   // PASS

// Equivalent to:
// String result;
// if (score >= 50) { result = "PASS"; } else { result = "FAIL"; }

Ternaries are great for short assignments but become unreadable when nested. If you find yourself stacking them, switch back to a normal if/else.

The switch statement

When you are comparing one variable against many fixed values, a chain of else ifs gets noisy. A switch is cleaner. Each case is a value to match; break stops execution falling through to the next case; default handles anything unmatched.

String browser = "firefox";

switch (browser) {
    case "chrome":
        System.out.println("Launching Chrome");
        break;
    case "firefox":
        System.out.println("Launching Firefox");
        break;
    case "edge":
        System.out.println("Launching Edge");
        break;
    default:
        System.out.println("Unknown browser: " + browser);
}
// Prints "Launching Firefox"

Forgetting break causes fall-through: execution continues into the next case, often a bug. Modern Java (14+) offers a tidier switch expression with arrow syntax that needs no break and can return a value:

String message = switch (browser) {
    case "chrome"  -> "Launching Chrome";
    case "firefox" -> "Launching Firefox";
    case "edge"    -> "Launching Edge";
    default        -> "Unknown browser: " + browser;
};
System.out.println(message);

This pattern is common in test frameworks for turning a configuration string into the right browser driver.

Loops: repeating work

A loop repeats a block of code. This is where automation earns its name—you rarely verify just one thing; you verify every row, every link, every field. Java has three loop forms.

The for loop

Use a for loop when you know how many times to repeat (or you are counting through a range). It has three parts in the parentheses: initialise; condition; update.

for (int i = 0; i < 5; i++) {
    System.out.println("Iteration number " + i);
}
// Prints 0, 1, 2, 3, 4

Read it as: start i at 0; keep going while i < 5; after each pass, increase i by 1. Counting from 0 feels odd at first but is the universal convention—and it matches how lists and arrays are numbered (next module), so embrace it early.

The while loop

Use a while loop when you do not know the count in advance—you simply repeat as long as a condition holds. The condition is checked before each pass, so the body might run zero times.

int attempts = 0;
boolean pageLoaded = false;

while (!pageLoaded && attempts < 3) {
    System.out.println("Checking if page loaded, attempt " + attempts);
    // ... imagine a real check here that might set pageLoaded = true
    attempts++;
}

Something inside the loop must eventually make the condition false—here, attempts++. Forget that, and you get an infinite loop that never ends.

The do-while loop

A do-while is a while that checks its condition at the bottom, so the body always runs at least once. Use it when you must do something before you can test whether to continue, like prompting a user until they give valid input.

int number;
Scanner scanner = new Scanner(System.in);
do {
    System.out.print("Enter a number between 1 and 10: ");
    number = scanner.nextInt();
} while (number < 1 || number > 10);
System.out.println("Thanks, you entered " + number);

break and continue

Two statements fine-tune loop behaviour. break exits the loop entirely, right now. continue skips the rest of the current pass and jumps to the next one.

for (int i = 1; i <= 10; i++) {
    if (i == 7) {
        break;        // stop the loop completely when we reach 7
    }
    if (i % 2 == 0) {
        continue;     // skip even numbers, go to next i
    }
    System.out.println(i);
}
// Prints 1, 3, 5 (stops before 7, skips evens)

In testing, break is handy for “stop as soon as you find the matching row”, and continue for “skip the rows that do not apply”.

Nested loops

A loop can contain another loop. The inner loop runs completely for each pass of the outer loop. This is how you work through a grid, a table of rows and columns, or—in Selenium—each cell of a results table.

for (int row = 1; row <= 3; row++) {
    for (int col = 1; col <= 2; col++) {
        System.out.println("Row " + row + ", Column " + col);
    }
}
// Row 1, Column 1 / Row 1, Column 2 / Row 2, Column 1 ... etc.

Tutor tip: loops are the engine of data-driven testing

Almost every powerful testing pattern is a loop in disguise. “Run this login test with ten different username/password combinations” is a loop over a list of data. “Check that every product on the page has a price” is a loop over the elements Selenium found. “Keep polling until the spinner disappears” is a while loop with a timeout. When you later meet data-driven tests and explicit waits, you will recognise the control-flow structures from this very lesson underneath them. Master loops now and that advanced material will feel familiar rather than foreign.

Hands-on exercise

Create a class ControlFlowPractice and solve each part in main:

  1. FizzBuzz (the classic). Loop from 1 to 30. Print “Fizz” if the number is divisible by 3, “Buzz” if divisible by 5, “FizzBuzz” if divisible by both, otherwise the number itself. (Hint: use % and check the “both” case first.)
  2. Grader. Given an int score, use if/else if/else to print a letter grade (A/B/C/Fail), then produce the same PASS/FAIL result in one line using the ternary operator.
  3. Browser switch. Given a String browser, use a switch expression to produce a launch message for chrome, firefox, and edge, with a sensible default.
  4. Retry loop. Simulate retrying an action up to 3 times using a while loop and a counter, stopping early with break if a boolean success becomes true.
  5. Multiplication grid. Use nested for loops to print the multiplication tables from 1 to 5.

Knowledge check

  1. What does the modulus operator % return, and how do you test whether a number is even?
  2. What is the difference between = and ==?
  3. What does “short-circuiting” mean for && and ||, and why is it useful?
  4. In 2 + 3 * 4, what is the result and why? How would you force addition first?
  5. Why should you compare Strings with .equals() rather than ==?
  6. What happens if you forget break in a traditional switch case?
  7. Which loop guarantees its body runs at least once, and why?
  8. What is the difference between break and continue?
Show answers
  1. % returns the remainder of a division; a number is even when number % 2 == 0.
  2. = assigns a value to a variable; == compares two values and returns a boolean.
  3. Evaluation stops as soon as the result is certain (if the left of && is false, or the left of || is true). It is useful for guarding against errors, e.g. element != null && element.isDisplayed().
  4. It is 14, because * has higher precedence than +. Force addition first with parentheses: (2 + 3) * 4 gives 20.
  5. == compares object references, not the text itself, so it can return false for equal strings; .equals() compares the actual characters.
  6. Execution “falls through” into the following case(s), running code you did not intend—usually a bug.
  7. The do-while loop, because it checks its condition at the bottom, after running the body once.
  8. break exits the loop entirely; continue skips the rest of the current iteration and moves to the next one.

Key takeaways

  • Operators come in families: arithmetic (+ - * / %), relational (== != > <…), logical (&& || !), and assignment (= += ++).
  • When precedence is unclear, use parentheses rather than memorising the table.
  • if/else if/else run the first true branch; the ternary ? : is a compact value-producing shorthand; switch handles many fixed values cleanly.
  • Use for when you know the count, while when you do not, and do-while when the body must run at least once.
  • break exits a loop; continue skips to the next pass; nested loops walk grids and tables—the backbone of data-driven testing.

Go deeper

These extras build directly on the operators and control flow you just met, and they turn up constantly in real test code.

Labelled break for nested loops. A plain break leaves only the innermost loop. When you have found your match in a grid and want to exit both loops at once, label the outer loop and break to that label:

search:
for (int row = 0; row < grid.length; row++) {
    for (int col = 0; col < grid[row].length; col++) {
        if (grid[row][col].equals("target")) {
            break search;   // leaves BOTH loops at once
        }
    }
}
  • Bitwise vs logical. A single & or | also works on booleans, but unlike && and || it does not short-circuit—both sides always run. Keep to the double forms in conditions so a guard like element != null && element.isDisplayed() stays safe.
  • Modulus with negatives. In Java the result of % takes the sign of the left operand, so -7 % 3 is -1, not 2. If you lean on % to wrap an index around, guard against negative inputs first.
  • A switch that returns a value. The arrow switch expression can compute a result directly, and yield lets a multi-line case hand one back—useful for mapping a config string to a value with no temporary variable.

Save to list

Scroll to Top