Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
128: Test Coverage with JaCoCo
Imagine you've hired a security guard to patrol a massive warehouse. At the end of the night, the guard tells you, "Everything is secure; I did my rounds." You might feel great, but there's a problem: you have no actual proof of where they went. Did they actually walk through the freezer section? Did they check the loading dock, or did they just walk in a circle around the breakroom and call it a night?
In Java, your JUnit tests are that security guard. When you run your tests and they all pass (the "green bar"), it's easy to assume your code is safe. But "passing tests" isn't the same as "tested code." JaCoCo is essentially the GPS tracker on your security guard. It tells you exactly which lines of code were executed during your tests and, more importantly, which ones were completely ignored.
How JaCoCo Actually Watches Your Code
JaCoCo doesn't just "watch" your code from the outside. It uses something called bytecode instrumentation. When your tests run, JaCoCo inserts "probes" (tiny bits of code) into your compiled .class files. Every time a line of code is hit, the probe flips a switch. After the tests finish, JaCoCo looks at which switches were flipped and generates a report. I've seen developers get nervous about this, but don't worry—it happens at the bytecode level, so it doesn't mess with your actual source code.
Spotting the Blind Spots in a Payment Processor
Let's look at a real-world scenario. Suppose we have a PaymentProcessor class that handles different payment methods. If you only write a "happy path" test, you're leaving a lot of room for disaster.
public class PaymentProcessor {
public boolean processPayment(double amount, String method) {
if (amount <= 0) {
return false; // Edge case 1
}
if ("CREDIT_CARD".equals(method)) {
return chargeCreditCard(amount);
} else if ("PAYPAL".equals(method)) {
return chargePayPal(amount);
} else {
return false; // Edge case 2: Unsupported method
}
}
private boolean chargeCreditCard(double amt) { /* ... */ return true; }
private boolean chargePayPal(double amt) { /* ... */ return true; }
}
Now, if you write one test like processor.processPayment(100.0, "CREDIT_CARD"), your test will pass. But if you run a JaCoCo report, it's going to scream at you in red. It will show that the amount <= 0 check and the PAYPAL branch were never touched. You've got "green tests," but you have a massive blind spot. One bad input in production, and that untested else block could be hiding a bug that costs the company money.
Line Coverage vs. Branch Coverage
One thing I want you to keep in mind: not all coverage is created equal. JaCoCo tracks two main things: Line Coverage (did this line run?) and Branch Coverage (did I test both the 'true' and 'false' paths of this if statement?).
I've worked with teams who obsessed over "100% Line Coverage," and it's a trap. You can execute a line of code without actually testing the logic of that line. For instance, if you have a complex boolean expression like if (isAdmin && hasPermission || isSuperUser), just hitting that line once doesn't mean you've tested all the ways that condition can be true or false. Always look at the branch coverage in the JaCoCo HTML report; it's much more honest about your risk.
Don't Chase the 100% Dragon
Here is some veteran advice: do not let your manager (or your own ego) force you to hit 100% coverage. I've seen engineers spend three days writing absurd, fragile tests just to cover a single catch (UnexpectedException e) block that is virtually impossible to trigger in reality. That's a waste of your time.
Use JaCoCo as a flashlight to find the obvious gaps—the forgotten else block or the edge case you forgot to handle. Once you hit 80-90%, the effort required to get that last 10% usually outweighs the actual safety it provides. Focus on testing the critical business logic, not just making the report turn green.
📋 Practical Task
Closing the Coverage Gap in a Discount Calculator
You have been handed a DiscountCalculator class and a corresponding test suite. The current tests are passing, but the JaCoCo report indicates that the coverage is only around 60%. Your goal is to identify the uncovered logic and write the necessary tests to reach at least 90% coverage.
The Code:
public class DiscountCalculator {
public double calculateDiscount(double totalAmount, boolean isMember) {
if (totalAmount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
if (isMember) {
if (totalAmount > 1000) {
return totalAmount * 0.20; // 20% for big spenders
} else {
return totalAmount * 0.10; // 10% for standard members
}
} else {
if (totalAmount > 500) {
return totalAmount * 0.05; // 5% for non-members spending a lot
} else {
return 0; // No discount
}
}
}
}
The Current Tests:
@Test
public void testMemberDiscount() {
DiscountCalculator calc = new DiscountCalculator();
assertEquals(100.0, calc.calculateDiscount(1000.0, true));
}
@Test
public void testNonMemberNoDiscount() {
DiscountCalculator calc = new DiscountCalculator();
assertEquals(0, calc.calculateDiscount(100.0, false));
}
Your Task:
- Analyze the
DiscountCalculatorand the provided tests. - Identify which logic branches are currently missing from the test suite (think about negative amounts, high-spending members, and high-spending non-members).
- Write the additional JUnit test methods required to ensure every branch of the
calculateDiscountmethod is executed. - Verify that your new tests cover the
IllegalArgumentExceptioncase.
There are no comments for now.