--- title: "Jam 05 - Exercise 2" tags: - 3 ๐Ÿงช in testing - 4 ๐Ÿฅณ done - classes - uml - arraylist - documentation --- <!-- markdownlint-disable line-length single-h1 no-inline-html --> <!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } --> {%hackmd dJZ5TulxSDKme-3fSY4Lbw %} # Exercise 2 - Product Class Implementation ## Overview - Exercise 2 Now that we have our UML design, let's implement the `Product` class. This exercise will teach you how to: 1. Create a basic class structure 2. Add fields and methods incrementally 3. Follow test-driven development practices 4. Implement proper encapsulation ## The Problem - Exercise 2 The Product class represents a retail store product with properties like name, SKU, price and description. It follows the UML design from Exercise 1 and includes proper encapsulation and validation. The implementation will demonstrate object-oriented principles and include complete documentation. We'll use IntelliJ's code generation features to help us create many of the required methods efficiently. ## Required Steps - Exercise 2 ### Step 1 - Write some code 1. Create `Product.java` in your `jam05` package 2. Add your class JavaDoc 3. Using your UML diagram for guidance, add the instance fields to the class ### Step 2 - Generate the rest IntelliJ IDEA provides powerful code generation features that can save you significant time and reduce errors. One of the most useful features is the code generator, which can create constructors, getters, setters, and other common code patterns automatically. Let's use IntelliJ's code generator to create our constructor: 1. Below the instance fields, right click and select "Generate" 2. Select "Constructor" 3. Select the fields you want to include in the constructor (control click/Command click on the fields to select multiple). Use your UML diagram as a guide. 4. Click "OK" IntelliJ will create the constructor with the selected fields. You can then modify the constructor to include proper validation. Proper validation could include something like allowing any price information, but ensuring you store it only as 2 decimal places. One way to do that would be with: ```java this.price = BigDecimal.valueOf(price) .setScale(2, RoundingMode.HALF_UP) .doubleValue(); ``` :::info Let's break down the decimal handling code: 1. `BigDecimal.valueOf(price)` - Creates a BigDecimal from the double value - BigDecimal provides precise decimal arithmetic - Better than double for money calculations 2. `.setScale(2, RoundingMode.HALF_UP)` - Sets decimal places to 2 digits - HALF_UP rounds 5 and above up, below 5 down - Example: 10.125 becomes 10.13 3. `.doubleValue()` - Converts BigDecimal back to double - Preserves the rounded 2 decimal places - Returns final formatted price ::: Now that we have our constructor working, let's explore another powerful IntelliJ feature! While it might seem like a small thing, code generation can save us tons of time and reduce errors. Let's use IntelliJ to generate our getters: 1. Add a couple of empty lines below the constructor 2. Press Alt+Insert (โŒ˜N on Mac) to open the Generate menu (or right click and select "Generate" like we did last time) 3. Select "Getter" 4. Select all fields in the dialog 5. Click OK Wow! We have getters! Now let's implement the toString method. You might have seen there is generator option for this too! 1. Add a couple of empty lines below the last getter 2. Press Alt+Insert (โŒ˜N on Mac) to open the Generate menu (or right click and select "Generate" like we did last time) 3. Select "toString" 4. Select all fields in the dialog 5. Click OK Hot Dang! We have a toString method! Only downside is that generator doesn't follow Google's style guide (or our Classroom Modified Google Style) so you'll need to adjust the code to fix that. <!-- TODO - Add a video about multicursor fixing --> Step back and look at all that code you "wrote!" You've got a constructor, getters, and a toString method! That's a lot of code for something that didn't exist a few minutes ago! ## Testing with JUnit So far, we've written programs that have a main method to drive and test our code. But what happens when we don't have a main method yet? How can we test our code? Enter JUnit! JUnit is the most widely-used testing framework for Java, providing a structured way to verify our code works correctly without needing a main method. For this jam, we'll provide you with the JUnit tests to help guide your implementation. This will let you focus on learning how to work with the testing framework and understand what good tests look like. In future jams, you'll start writing your own tests, which is a critical skill for professional software development. Writing tests helps you think through edge cases, document expected behavior, and catch bugs early. First, let's get the test files. We'll actually get both test files at once. :::warning ๐Ÿšจ **Important**: - Make sure you're in your project root directory when running these commands - The files must be placed in the correct package directory (`src/test/java/jam05/`) - Create the test directory structure if it doesn't exist: `mkdir -p src/test/java/jam05` - Verify the files were copied correctly before proceeding ::: ```bash # If working on your laptop (replace userid with your Bucknell username): scp "userid@linuxremote.bucknell.edu:/home/csci205/2025-spring/student/jam05/ProductTest.java" src/test/java/jam05/ scp "userid@linuxremote.bucknell.edu:/home/csci205/2025-spring/student/jam05/ProductCatalogTest.java" src/test/java/jam05/ # If working on linuxremote: cp "/home/csci205/2025-spring/student/jam05/ProductTest.java" src/test/java/jam05/ cp "/home/csci205/2025-spring/student/jam05/ProductCatalogTest.java" src/test/java/jam05/ ``` You can add additional tests to these files but do not change any of the existing test code. Part of your grade will be passing these actual tests, not just the tests in your file. ๐Ÿ”‘ **Understanding JUnit Tests** A JUnit test is a method that verifies a specific piece of functionality. Each test method: - Is marked with the `@Test` annotation - Has a descriptive name explaining what it tests - Uses assertion methods to verify expected behavior - Tests one specific aspect of the code For example, here's a test from `ProductTest.java`: ```java @Test void testConstructorAndGetters() { Product p = new Product("Test Product", "SKU001", 9.99); assertEquals("Test Product", p.getName()); assertEquals("SKU001", p.getSku()); assertEquals(9.99, p.getPrice(), 0.001); } ``` This test verifies that: 1. We can create a Product with the given parameters 2. The getter methods return the correct values 3. Price values are handled with proper precision :::info ๐Ÿ’ก **Common JUnit Assertions** JUnit provides many assertion methods to verify code behavior: - `assertEquals(expected, actual)`: Verifies two values are equal - `assertTrue(condition)`: Verifies a condition is true - `assertFalse(condition)`: Verifies a condition is false - `assertNull(value)`: Verifies a value is null - `assertNotNull(value)`: Verifies a value is not null - `assertThrows(ExceptionClass.class, () -> code)`: Verifies code throws an exception For floating-point comparisons, use a delta value: ```java assertEquals(9.99, product.getPrice(), 0.001); // Allows small rounding differences ``` ::: ๐Ÿ’ก **Running Tests in IntelliJ** Before we can run the test files, because we also grabbed the test file for `ProductCatalog`, we need to make sure it exists or the tests won't compile. Using your usual steps, create the `ProductCatalog` class in the `jam05` package but don't add anything to it yet. You will notice most of the test code in `ProductCatalogTest` is actually commented out for now. We will deal with this later. To run tests in IntelliJ: 1. Right-click on the test file in the Project view 2. Select "Run 'ProductTest'" 3. View results in the test runner window - Green โœ…: Test passed - Red โŒ: Test failed - Click on failed tests to see detailed error messages You can also: - Run a single test method by clicking the โ–ถ๏ธ icon next to it - Run all tests in a package by right-clicking the package - Use the keyboard shortcut Ctrl+Shift+F10 (โŒ˜+Shift+F10 on Mac) :::warning ๐Ÿšจ **Common Testing Mistakes** 1. **Incomplete Testing** - Not testing edge cases (zero, negative values) - Missing null input validation - Not verifying error conditions 2. **Poor Test Design** - Testing multiple things in one test - Using hardcoded "magic numbers" - Tests that depend on other tests 3. **Test Maintenance** - Not updating tests when requirements change - Duplicate test code - Unclear test names or purpose ::: Let's run the provided tests to verify our Product implementation works correctly. Later, when we implement ProductCatalog, we'll learn about Test-Driven Development (TDD) where we write tests (or at least uncomment tests) before writing the code! Oh man, you probably passed two tests but failed the rest. Let's fix that! Lets look at the failed `testToString`. ![failedTest](https://www.dropbox.com/scl/fi/n0m19zec2e2o7so02q7td/failedTest.png?rlkey=bogu5suza33lev3d74cmq7hea&raw=1) We can see that the test is expecting string `Test Product (SKU: SKU002) - $9.99`, but we are getting `Product{name='Test Product', sku='SKU002', price=9.99}`. I guess that generator wasn't perfect all along. Assume the test is correct as it was written by someone on the requirements team so we need to change our `toString` method. Go to your `Product.java` file and change the `toString` method to match the test. I recommend returning something with `String.format` but you can do it in whatever way you like that passes the tests. If you've correctly fixed passing testToString, you've probably also fixed the other test. If not, go back and fix that. > ๐Ÿ” **Checkpoint** > > Before proceeding, verify: > > - `Product.java` created in correct package > - All required fields implemented with proper visibility > - Constructor validates and stores input correctly > - All getter methods implemented > - toString method matches test requirements > - All 5 Product tests pass > - Code follows Google style guide ## Save Your Work - Exercise 2 Verify what files are uncommitted: ```bash git status ``` Stage your changes: ```bash git add src/main/java/jam05/Product.java src/main/java/jam05/ProductCatalog.java src/test/java/jam05/ProductTest.java src/test/java/jam05/ProductCatalogTest.java ``` Commit your work: ```bash git commit -m "jam05: Implement Product class with passing tests" ``` Your working directory should now be clean. :::success ๐Ÿ”‘ **Key Takeaways** - IntelliJ's code generation features can speed up development - JUnit tests help verify code correctness - Test-driven development guides implementation - Proper toString formatting improves code readability - Version control helps track implementation progress :::