Packages, Class Diagrams and Unit Tests in Java

Learn how to organize a larger Java program so that it stays easy to understand, change and check. Group classes into packages with imports and the right access levels, sketch the structure in UML class diagrams, and separate the user interface, the program logic and data storage into layers. Then verify the logic with JUnit tests, write code test-first, and let Maven or Gradle compile, test and package the whole project with one command.

IntermediateLesson 10 of 11 in Object-Oriented Programming with Java Course

What you will learn

  1. Group related classes into packages, store each source file where the compiler and build tools expect it, and use classes across packages with imports and the right access level.
    • Declare packages and match them to folders
      Write a package declaration, choose a conventional package name, store the file in the matching folder and refer to classes by their fully qualified names.
    • Use classes from other packages with imports
      Import single classes or whole packages, know which classes need no import, resolve clashes between classes with the same name and use static imports.
    • Control access across packages
      Choose between public, protected, package-private and private so that each package exposes only what other packages need.
  2. Read and draw class diagrams that show classes, their members and the relationships between them, and translate between a diagram and Java code in both directions.
    • Draw and read a single class
      Show a class's name, attributes and operations with types and visibility, and mark static members, abstract classes and interfaces.
    • Show associations and multiplicity
      Connect classes with associations, read and write multiplicities and navigability, and tell composition, aggregation and dependency apart.
    • Model inheritance and move between diagrams and code
      Show inheritance and interface implementation, write Java code from a diagram and draw a diagram from existing code.
  3. Split a program so that the user interface, the application logic and data storage live in separate classes and packages, with dependencies pointing in one direction.
    • Separate the user interface from the program logic
      Keep reading input and printing output in a user interface class, keep the program's rules in logic classes, and connect the two in the main method.
    • Arrange layers, packages and dependencies
      Organize classes into a presentation layer, a logic layer and a data access layer, keep dependencies pointing downward and connect the layers through interfaces.
  4. Write automated unit tests with JUnit, choose test cases that reveal bugs, keep tests independent and readable, and develop new code test-first.
    • Write and run a first JUnit test
      Create a test class, write test methods with assertions, run them and read the result of a passing and a failing test.
    • Choose test cases that find bugs
      Pick typical, boundary and invalid inputs, test expected exceptions and give each test one clear purpose and name.
    • Keep tests independent and maintainable
      Set up fresh test objects before each test, avoid shared state, run one test with many inputs and test the logic separately from input and output.
    • Develop code test-first with TDD
      Follow the red–green–refactor cycle from a test list, take small steps and know what test-driven development does and does not promise.
  5. Use a build tool to manage libraries, follow the standard project layout, and compile, test and package a program with one command.
    • Set up and build a Maven project
      Describe a project in pom.xml with coordinates and dependencies, follow the standard folder layout and run the build lifecycle.
    • Set up a Gradle project and fix failed builds
      Write the equivalent Gradle build script, run JUnit tests on the JUnit Platform, compare the two tools and find the cause of a failed build.

Java Packages study guide

5 chapters

1

Organize a Java program into packages

As a program grows, class names and folders need a structure that both people and Java can follow. Packages group related types and define access boundaries. Imports make those types convenient to use without opening their internals. You will connect a declaration to its source path, resolve names across packages, and decide which classes, constructors, and methods another part of the program should be allowed to call.

1.1 Package names and source folders

A package groups related classes and interfaces under one name. It also separates names: two classes called Parcel can coexist when their package names differ. A package gives Java an access boundary as well as a way to organize files.

Declare the package first

An ordinary source file begins with its package declaration, followed by imports and then the class. Comments can appear before the declaration. For example:

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    public Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

Every top-level type in that file belongs to com.example.parcels.domain. A file has at most one package declaration. The public class Parcel belongs in Parcel.java; changing the folder alone does not change the declared package.

Turn dots into folders

The source root is the folder where the package path begins. In a standard Maven or Gradle project, main Java code starts at src/main/java. Replace each dot in the package name with a slash, then add the class name:

src/main/java/
  com/example/parcels/domain/Parcel.java

The package is com.example.parcels.domain, not src.main.java.com.example.parcels.domain. The first three folders describe the build layout; they are not part of the Java name. When an IDE reports a package mismatch, compare the declaration with the path below the source root. Move the file or correct the declaration so they agree.

Package names use lowercase letters. Organizations commonly reverse their domain: example.com becomes com.example, followed by the project and its area. A project without a domain can use parcels.domain. If a reversed domain component contains a hyphen, replace it with an underscore, as in org.example.hyphenated_name. Avoid the platform's java and javax naming space.

Use the complete class name

A fully qualified name joins the package and simple class name: com.example.parcels.domain.Parcel. It identifies the class even when another package also has a Parcel. Code can name an accessible class in full without an import:

java.util.ArrayList<String> names = new java.util.ArrayList<>();

To run a packaged main class, give its full name and a classpath pointing to the root of the compiled package tree. From the project folder:

javac -d out src/main/java/com/example/parcels/Main.java src/main/java/com/example/parcels/domain/Parcel.java
java -cp out com.example.parcels.Main

Here javac -d out creates the output tree, and java -cp out searches from that tree's root. Starting java Main from a package folder loses the package portion of the class's identity. Build tools and IDEs normally manage these commands.

Distinguish neighboring packages

com.example.parcels and com.example.parcels.domain are separate packages. Sharing a name prefix gives no extra access rights. A file with no declaration belongs to the unnamed package. Named packages cannot import its classes, so use named packages throughout a program with several parts.

Key Takeaways

  • Place the package declaration before imports and the class.
  • Match the package path below the source root; keep src/main/java out of the package name.
  • Use lowercase package names and the fully qualified class name when needed.
  • A shared package-name prefix gives no shared package access.
  • Run packaged classes from the compiled tree’s root using their fully qualified names.

1.2 Imports and class-name clashes

An import lets one source file use an accessible type's simple name. It tells the compiler what the name means. It does not copy the class, download a library, or make a hidden class public.

Read the top of a source file

The UI belongs to one package and uses classes from two others:

package com.example.parcels.ui;

import com.example.parcels.domain.Parcel;
import com.example.parcels.domain.ShippingCalculator;
import java.util.Scanner;

public class TextUI {
    private Scanner scanner;
    private ShippingCalculator calculator;
}

Parcel and ShippingCalculator come from the program's domain package; Scanner comes from the Java library. Each import applies only to this file. Another file that uses Scanner needs its own import or the full name java.util.Scanner.

Choose a single type or a wildcard

import java.util.Scanner; is a single-type import. Its exact class is visible to the reader. import java.util.*; is an import on demand: accessible types in that package can be named when used. The wildcard does not load unused classes into the program or slow it down.

The wildcard stops at that package. It does not include java.util.function.Function or java.util.stream.Collectors, which live in separate packages. The same rule applies to your own code: import com.example.parcels.*; does not reach com.example.parcels.domain.Parcel.

Three cases need no import: types in the same package, automatically available types in java.lang, and types named in full. That is why String, System, Math, Integer, and Object commonly appear without imports.

Resolve a shared simple name

Both java.util and java.awt have a class called List. Two single-type imports for these different classes are a compile error. Two wildcard imports can coexist, but the simple name List becomes ambiguous when used. A single-type import takes precedence over a wildcard import.

If both classes are needed, import one and spell out the other:

import java.util.List;

class NameDisplay {
    private List<String> names;
    private java.awt.List widget;
}

Java imports provide no alias syntax such as import ... as .... The fully qualified name is the way to distinguish the second type.

Import a static member

A static import removes the owning class name from a static member call:

import static java.lang.Math.sqrt;

The file can now call sqrt(16.0). Test code often uses import static org.junit.jupiter.api.Assertions.assertEquals; so assertions read as direct checks. In ordinary program code, use static imports selectively so readers can still see where operations come from.

Follow what the error says

“Cannot find symbol” can mean a missing import or a misspelled type. “Package ... does not exist” points toward a package spelling, source-root, or dependency problem. “Is not public ... cannot be accessed from outside package” means Java found the type but access is blocked. Adding another import cannot fix that access error; inspect the class declaration and the members you need.

Key Takeaways

  • An import resolves names in one file and grants no access rights.
  • A wildcard includes types in one package, without its subpackages.
  • Same-package types and java.lang types need no import.
  • Use one single-type import and one fully qualified name when two needed classes share a name.
  • A static import makes a static member callable without its class name.
  • Distinguish a name-resolution error from an access error.

1.3 A small public surface

A package should expose the classes and methods other parts need while keeping implementation details hidden. Access modifiers make that boundary enforceable. An import can shorten a name, but it cannot cross a boundary that Java access rules close.

Choose access for a class member
AccessWho can use the member?
publicCode that can access the declaring type
protectedThe same package, plus subclass access across packages
No modifierThe same exact package
privateThe declaring class and its nested types

No modifier means package-private for ordinary class members and constructors. It is not a keyword called default. A top-level class has only two choices: public or package-private. A public top-level class is stored in a file with its name, such as ShippingCalculator.java.

Check the type and the operation

Suppose the UI must construct a parcel. A public class alone is insufficient:

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

The constructor has package access. Code in com.example.parcels.ui can name Parcel, but cannot call new Parcel(1200). Make that constructor public if creating parcels is part of the package's public service. Keep weightGrams private and expose an appropriate operation instead of opening the field.

Package-private access compares the whole package name. Neither a parent-looking package nor a child-looking package is the same package. Code in com.example.parcels gets no special access to helpers in com.example.parcels.domain.

Understand protected access

protected allows access from unrelated classes in the declaring package as well as from subclasses. Across packages, an instance member is accessed through the subclass's inheritance context. A subclass can use its own inherited field, but cannot use an arbitrary superclass object as a way around the package boundary.

For example, if Item declares protected int weightGrams, a Letter extends Item in another package can use this.weightGrams. Inside Letter, a parameter typed merely as Item does not permit item.weightGrams; a reference typed as Letter does permit the inherited access. This restriction keeps subclass privileges from becoming general access to every Item.

Hide helpers deliberately

Make ShippingCalculator and feeInCents public if the UI calls them. A supporting WeightBands class can remain package-private. Code outside the domain package then depends on the calculator's behavior, not on how it stores the table. Replacing that helper requires fewer changes elsewhere.

Tests can use the same package name as the class they test while living under src/test/java. This gives them package access without placing test code among production files. It does not grant access to private fields. Making every member public just to satisfy a test spreads dependencies on internals; test public behavior or a useful package-level operation instead.

Key Takeaways

  • Package-private class members are accessible only in the exact same package.
  • A caller needs access to both a class and the constructor or method it uses.
  • Protected access includes same-package access and restricted subclass access across packages.
  • Keep fields private and expose only the operations another package needs.
2

Sketch program structure with UML class diagrams

A class diagram gives you a compact view of a program before you read its method bodies. Boxes describe types and operations; lines describe links, counts, and subtype relationships. You will read those marks precisely and translate them into Java declarations. The parcel program provides examples of individual classes, collections of associated objects, and interchangeable repository implementations.

2.1 What a UML class box says

A UML class diagram shows static structure: classes, their members, and relationships. It helps a reader see what kinds of objects exist and what they offer. It does not trace the order of method calls during execution.

Read the three compartments

A class box has the class name at the top, attributes in the middle, and operations at the bottom. An attribute usually represents a Java field; an operation represents a method's visible signature. A sketch can omit compartments that do not serve its purpose.

For a parcel, a textual rendering of the box is:

Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean

The constructor has the class's name and no return type. The method isHeavierThan accepts an integer named limitGrams and returns a boolean.

Translate the order of names and types

UML puts the name before the type: weightGrams: int. Java puts the type first: int weightGrams. An operation follows the same name-first pattern for parameters, then puts the return type after its closing parenthesis.

private int weightGrams;

public boolean isHeavierThan(int limitGrams) {
    return weightGrams > limitGrams;
}

This code corresponds to - weightGrams: int and + isHeavierThan(limitGrams: int): boolean. A method body is usually absent from the diagram; the operation states what can be called, not the algorithm.

Preserve visibility and special markings

The visibility signs are + public, - private, # protected, and ~ package access. Put the sign before the member name. If a supplied diagram omits a sign, do not invent its visibility; add the intended sign when documenting code.

A static member is underlined. It belongs to the class rather than to each object. An abstract class or operation is italicized, or marked {abstract} when italics are inconvenient. The abstract marking means a subclass must supply missing behavior before an object can be created from a concrete class.

An interface box carries «interface» above its name. For example, ParcelRepository can offer + save(parcel: Parcel): void and + findAll(): List<Parcel>. The box states a contract; it does not choose a storage implementation.

Choose the detail the reader needs

A planning sketch might show just Parcel, Shipment, and their main operations. A diagram for implementing a class needs types and visibility. Listing every routine getter can hide the relationships the diagram was meant to show. Select useful members and keep them consistent with the code: a private Java field should not appear with a public + sign, and a static method needs its underline.

Key Takeaways

  • A class box separates the name, attributes, and operations.
  • UML uses name: Type; Java uses Type name.
  • Use +, -, #, and ~ for public, private, protected, and package visibility.
  • Underline static members; mark abstract elements with italics or {abstract}.
  • An interface box shows «interface» and the operations in its contract.

2.2 Read the ends of relationship lines

An association shows a structural link between objects. In Java, one object commonly keeps a reference to another in a field. The line can also show how many objects are allowed and which direction the reference can be followed.

Count from the opposite end

Multiplicity sits beside the class it counts. If 1..* appears beside Parcel and 1 beside Shipment, one shipment has one or more parcels, and each parcel belongs to exactly one shipment. Read each end separately, starting from one object at the opposite end.

MultiplicityMeaning
1Exactly one
0..1Zero or one
* or 0..*Any number, including zero
1..*At least one
2..4Two through four

A single reference can represent 1 or 0..1; a collection represents a many-valued end. A Java List alone does not enforce a lower bound. A Shipment with a 1..* parcel rule must reject an empty initial list and prevent later operations from leaving it empty. Likewise, a required single reference needs a check against null.

Follow the reference

An open arrowhead at Parcel means a reference can be followed from Shipment to Parcel. A class diagram can leave navigability unspecified. In the teaching convention used here, arrows mark one-way references; an unadorned line may mean bidirectional access or unspecified direction. Do not infer a reverse field solely from an arrow toward Parcel.

A role name such as parcels labels what the link means from the shipment. Show that reference either as an association or as - parcels: List<Parcel> in the box. Displaying both without a reason can make one field look like two different pieces of state.

Choose the whole–part meaning

Composition uses a filled diamond at the whole. A part belongs to at most one composite whole at a time and is normally removed with that whole, as an order and its order lines. Aggregation uses a hollow diamond for a looser grouping whose parts can exist independently. A team can group players without owning their lifetimes.

Java has no diamond keyword and does not enforce UML ownership just because a list exists. Model composition only when the design includes that ownership rule. When the design simply says one object knows another, a plain association is clearer than an unsupported diamond.

Show a use without claiming ownership

A dependency is a dashed line with an open arrowhead toward the type being used. A calculator with feeInCents(Parcel parcel) depends on Parcel because its method signature uses that type. A dependency is a general use relationship; it is not limited to code without fields. An association gives more specific structural information when a field is important to the model.

Before drawing a line, ask what the code or requirement establishes: a reference, an allowed count, a use, or ownership. A collection establishes many-valued storage, but it does not by itself establish exclusive ownership or a minimum count.

Key Takeaways

  • Multiplicity next to a class counts its objects for one object at the other end.
  • A collection type does not enforce a minimum or maximum multiplicity.
  • An association arrow points toward the type the source can navigate to.
  • Put a composition or aggregation diamond at the whole’s end.
  • A dependency is a dashed use arrow; an association describes a structural link.
  • Show a reference once unless the diagram deliberately needs both representations.

2.3 From a diagram to Java declarations

Inheritance and interface implementation both connect a specific type to a more general one. UML uses a hollow triangle at the general type, but the line style distinguishes the two relationships. That distinction tells you which Java declaration to write.

Read the triangle and its line

A solid line with a hollow triangle at Item means a subclass such as Letter extends Item. This is generalization. A dashed line with a hollow triangle at ParcelRepository means a class such as InMemoryParcelRepository implements that interface. This is realization.

The triangle always touches the superclass or interface. A dashed open arrow, without the triangle, means dependency instead. Line style and arrowhead together carry the meaning; a dashed line alone is insufficient.

Build the skeleton in a useful order
  1. Create each class or interface in its package.
  2. Translate attributes into fields, preserving names, types, and visibility.
  3. Translate navigable associations into reference or collection fields.
  4. Add extends or implements from the generalization or realization lines.
  5. Translate operations into method headers and then supply their bodies.

Suppose an interface offers save and findAll. An implementation has a many-valued association to Parcel with role parcels:

public class InMemoryParcelRepository implements ParcelRepository {
    private List<Parcel> parcels = new ArrayList<>();

    @Override
    public void save(Parcel parcel) {
        parcels.add(parcel);
    }

    @Override
    public List<Parcel> findAll() {
        return new ArrayList<>(parcels);
    }
}

The dashed triangle supplies implements ParcelRepository. The association supplies the list field. The operation signatures supply the two public methods. The method bodies are an implementation choice: here save appends an object, and findAll returns a new list so callers cannot directly add or remove entries in the stored list. The parcel objects themselves are still shared.

Draw the other direction

Start with one box for each class or interface. A simple-value field becomes an attribute. A reference to a class in the program becomes an association; a collection suggests a many-valued end. Inspect constructor and method checks before claiming a lower bound of one. Translate extends into a solid triangle line and implements into a dashed triangle line.

Inherited fields and operations usually stay in the superclass box. Put new or overridden members in the subclass. Add a dependency for a parameter, return type, or local use when that relationship helps explain the design.

Packages can surround their classes in folder-shaped boxes with a small tab. These groups help reveal which areas depend on which others. Keep a documentation diagram current when code changes; label an early planning sketch so readers understand its purpose.

A Shipment that contains parcels is not a kind of Parcel. Its list gives a “has” relationship, so draw an association. Use inheritance for an “is a kind of” relationship that the Java type system and behavior actually support.

Key Takeaways

  • Generalization uses a solid line and a hollow triangle at the superclass.
  • Realization uses a dashed line and a hollow triangle at the interface.
  • Translate fields, associations, inheritance, and method signatures before writing bodies.
  • Preserve the diagram’s types and visibility when translating.
  • A collection’s declared type alone cannot establish a minimum count.
  • Use associations for contained objects and inheritance for subtype relationships.
  • Keep documentation diagrams consistent with the code they describe.
3

Structure a program in layers

A program becomes easier to test and change when its dialog, rules, and storage have separate responsibilities. You will follow a parcel command through those parts, move calculations out of console code, and pass collaborators through constructors. Interfaces let the logic work with either real storage or an in-memory replacement, while a careful reading of imports reveals which types each layer depends on.

3.1 Keep the dialog out of calculations

The user interface reads commands and displays results. The application logic holds the rules and data those commands act on. Separating them lets a test call a calculation directly, and lets another interface reuse it without copying the rule.

Give each part a clear job

TextUI owns the conversation: ask for a weight, read a line with Scanner, turn the text into a number, call the logic, and print the result. ShippingCalculator accepts a weight in grams and returns a fee in cents. It does not ask the user anything or print an answer.

A method called printFee mixes those jobs if it both calculates and prints. Change the logic operation to feeInCents(int weightGrams). The UI can then print "Fee: " + calculator.feeInCents(weightGrams) + " cents". A test can assert the returned integer without capturing console output.

Write the rule as a return value

The parcel rule charges 500 cents for positive weights through 2,000 g, 900 cents through 10,000 g, then 100 additional cents for every started 1,000 g above 10,000 g.

public int feeInCents(int weightGrams) {
    if (weightGrams <= 0) {
        throw new IllegalArgumentException("weight must be positive");
    }
    if (weightGrams <= 2000) {
        return 500;
    }
    if (weightGrams <= 10000) {
        return 900;
    }
    long excess = (long) weightGrams - 10000;
    long startedBlocks = (excess + 999) / 1000;
    return (int) (900 + 100 * startedBlocks);
}

The first two successful branches include their upper boundaries. For 12,500 g, the excess is 2,500 g. Integer division after adding 999 counts three started blocks, giving 1,200 cents. The intermediate long arithmetic also handles large positive int weights without overflowing when 999 is added.

An invalid weight raises an IllegalArgumentException: the method rejects an argument that violates its rule. The UI can catch that exception and choose a readable message. The rule belongs to the logic; the wording of the message belongs to the interface.

Connect the objects in main

Pass the input reader and calculator into the UI constructor:

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    ShippingCalculator calculator = new ShippingCalculator();
    TextUI ui = new TextUI(scanner, calculator);
    ui.start();
}

Create one scanner for the input stream and share it with the UI. Multiple scanners over System.in can interfere with each other's buffered input. The main method assembles the program; start runs the dialog loop.

When reviewing a class, look for its responsibilities. A Scanner or System.out inside a fee calculator reveals mixed input/output and rules. A fee formula inside the UI reveals the same problem from the other side. Move the calculation into a method with explicit input and a result; keep command parsing and display text together in the UI.

Key Takeaways

  • The UI reads and prints; logic methods accept values and return results.
  • Keep fee rules and validation in the logic so tests can call them directly.
  • Let the UI choose how to display a result or rejected input.
  • Create collaborators in main and pass them into the UI constructor.
  • Count a partially used weight block as a started block when the rule requires it.

3.2 Connect layers through a repository

A layered program groups classes by responsibility. The presentation layer handles the dialog, the logic layer applies rules, and the data access layer saves and retrieves information. A class uses another layer through a clear operation instead of reaching into its internal fields.

Place the parcel classes

The UI package contains TextUI. The domain package contains Parcel, ShippingCalculator, and ParcelService. The data package contains ParcelRepository and its storage implementations. Main assembles the objects above these layers.

A command follows this route: the user enters "add 1200 Alex"; the UI parses it and calls service.register(1200, "Alex"); the service validates the weight, creates a parcel, saves it through the repository, and returns 500 cents; the UI prints the fee. Reading text, deciding what is allowed, and storing the result have separate homes.

Depend on a contract

The service needs storage behavior, not a particular file format. Express that behavior through an interface:

public interface ParcelRepository {
    void save(Parcel parcel);
    List<Parcel> findAll();
}

The service stores a reference typed as ParcelRepository and receives it in the constructor:

public class ParcelService {
    private final ParcelRepository repository;

    public ParcelService(ParcelRepository repository) {
        this.repository = repository;
    }
}

This is constructor injection, a form of dependency injection: the caller provides a collaborator. A service that constructs new FileParcelRepository(...) inside its own methods chooses file storage for itself and makes isolated tests harder.

Change the implementation at assembly

The running program can create new ParcelService(new FileParcelRepository("parcels.csv")). A test can supply new InMemoryParcelRepository(). Both implementations satisfy the same interface. Calls through the interface use polymorphism to reach the actual object's methods.

With the repository contract unchanged, switching storage requires a new implementation and a change at assembly. Fee rules and dialog code do not need to know how the data is stored.

Check dependency direction precisely

The downward service-use path is UI to logic to storage. The domain must not import or call UI classes. If the logic needs to tell the user a result, return it to the UI. If the UI saves directly through the repository, it can bypass the service's validation.

Java type references also matter: a repository signature that names Parcel depends on that domain type. With ParcelRepository in the data package and the service in the domain package, those packages refer to each other. To remove that package cycle, place the repository interface beside the domain types and let the data implementations import it. The service still uses the same contract; only its home changes. Read both the method-call route and the imports when checking the design.

Larger programs may first group packages by feature, such as orders or customers, and then separate responsibilities within each feature. Package names help readers locate responsibilities; constructor parameters and method signatures show the actual dependencies.

Key Takeaways

  • Separate presentation, logic, and data access responsibilities.
  • Pass a repository interface into the service constructor.
  • Choose a file or in-memory implementation where the program is assembled.
  • Route UI actions through the logic so validation is applied.
  • Return results to the UI instead of calling UI code from the logic.
  • Distinguish the service-use direction from Java type dependencies when checking package cycles.
4

Verify code with JUnit tests

Automated tests turn expected behavior into checks you can rerun after every change. Writing a check is only the start: useful inputs expose boundaries, fresh fixtures keep tests independent, and readable failures help you find the cause. You will build Jupiter tests for parcel logic, control their data and collaborators, and use a short test-first cycle to grow a rule one case at a time.

4.1 A test that checks an outcome

A unit test calls a small unit of code with chosen input and checks the result automatically. It makes a requirement executable. Rerunning tests after a change helps find a regression, a bug in behavior that worked before.

Create a Jupiter test

JUnit Jupiter is the programming model used by JUnit 5 and 6. JUnit 6 needs Java 17 or later. Test annotations and assertions use org.junit.jupiter.api.

Place ShippingCalculatorTest.java under src/test/java/com/example/parcels/domain, with the same package declaration as the calculator. The separate source root keeps tests apart from delivered program code while retaining same-package access.

package com.example.parcels.domain;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class ShippingCalculatorTest {
    @Test
    void lightParcelCostsBaseFee() {
        ShippingCalculator calculator = new ShippingCalculator();

        int fee = calculator.feeInCents(1500);

        assertEquals(500, fee);
    }
}

The test class and method can be package-private. A test method returns void and must not be private. Use Jupiter's @Test; JUnit 4's org.junit.Test belongs to a different programming model and is not discovered by the Jupiter engine. JUnit 4 assertions can work when their library is available, but consistent Jupiter imports keep the setup clear.

Read arrange–act–assert

The first part arranges a calculator. The second acts by asking for the fee. The final assertion compares the expected 500 cents with the returned value. Blank lines make these jobs easy to spot. Printing fee alone would leave a person to decide whether it was correct; the assertion makes that decision part of the test.

assertEquals(expected, actual) puts the requirement first and the observed result second. An optional message comes last, as in assertEquals(500, fee, "fee for 1,500 g"). Reversing the first two arguments usually preserves pass/fail for these values but reverses the meaning of the failure report.

Choose the right assertion

Use assertTrue or assertFalse for a boolean, and assertNull or assertNotNull when the presence of an object is the outcome. Object equality checks use equals; without an override, two distinct objects with matching fields may still compare unequal.

Floating-point results need an appropriate tolerance. The calculation 0.1 + 0.2 produces 0.30000000000000004 in a Java double:

assertEquals(0.3, 0.1 + 0.2, 1e-9);

The third argument allows an absolute difference up to 0.000000001. Select a tolerance that fits the calculation; an overly large tolerance can hide a meaningful error. Integer cent calculations need no decimal tolerance.

Read a failure

Run a test from the IDE or the build tool. A report such as "expected: <500> but was: <900>" names the required and observed results; the stack trace points to the assertion. An unexpected exception also fails a test. Green means the checked cases passed; choosing those cases remains a separate part of testing.

Key Takeaways

  • Use Jupiter annotations and assertions from org.junit.jupiter.api.
  • Place tests in src/test/java, normally in the tested class’s package.
  • Arrange the objects, act on them, and assert the outcome.
  • Put the expected value first in assertEquals.
  • Use a justified tolerance for floating-point comparisons.
  • Read the failing test name, expected/actual values, and assertion location.

4.2 Choose inputs at the edges of rules

A useful test set checks how the specification divides inputs into cases. One ordinary input rarely tests a whole rule. Boundary cases at a limit and just beside it are especially good at finding a condition written with the wrong comparison operator.

Partition the input space

An equivalence class groups inputs that should be treated alike. The shipping rule has invalid weights at zero or below, a 500-cent band through 2,000 g, a 900-cent band through 10,000 g, and a started-block calculation above that. Pick representative inputs from the classes, then inspect their boundaries.

Weight in gramsExpected outcome
0, -5Reject with IllegalArgumentException
1, 2000500 cents
2001, 10000900 cents
10001, 110001,000 cents
110011,100 cents

Changing <= 2000 to < 2000 affects exactly 2,000 g. A test at 1,500 g still passes. Above 10,000 g, test both a whole block and the first gram of the next block to check rounding upward.

Expected values come from the requirement and hand calculation. For 12,500 g, the excess is 2,500 g, requiring three started blocks, so the expected result is 1,200 cents. Running the method and copying its answer into the test would also copy any bug.

Test rejected input

An exception test gives JUnit the operation to run:

assertThrows(IllegalArgumentException.class,
    () -> calculator.feeInCents(0));

The () -> ... expression is a lambda: a small piece of code handed to JUnit. JUnit executes it inside the check. Calling the method before handing it to the assertion would let the exception escape before the check could inspect it.

assertThrows accepts the named exception type or a subclass. It fails if the operation completes normally or throws an incompatible type. It returns the exception, so a required message can be checked:

IllegalArgumentException error = assertThrows(
    IllegalArgumentException.class,
    () -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Give one scenario a readable name

Names such as zeroWeightIsRejected and feeIsBaseFeeAtExactly2000Grams identify the intended behavior. A test can have several assertions when they check the same outcome. After adding parcels of 1,200 g and 800 g to a shipment, checking a count of two and a total weight of 2,000 g describes that one scenario. assertAll can group checks so all failures are reported.

Check changed objects through their behavior, such as parcelCount() and totalWeightGrams(), instead of exposing private fields for tests. A refactoring can then change storage details while preserving the checks. Full line coverage means the tests executed the lines; it can still miss a wrong boundary or an untested invalid input.

Key Takeaways

  • Partition inputs by the specification and test representative cases plus boundaries.
  • Check a limit itself and nearby values to catch off-by-one errors.
  • Calculate expected results independently of the implementation.
  • Pass the operation to assertThrows as a lambda.
  • Name one scenario per test; several related assertions can check that scenario.
  • Test changed state through behavior that callers can observe.

4.3 Tests that run alone and in any order

Each test should arrange everything it needs. A test that passes only after another test has added data depends on hidden history. Such tests become hard to trust when a runner changes the order or runs just one method.

Create a fresh fixture

A fixture is the set of objects and data a test uses. Put repeated per-test setup in @BeforeEach:

class ShipmentTest {
    private Shipment shipment;

    @BeforeEach
    void setUp() {
        shipment = new Shipment();
    }

    @Test
    void newShipmentIsEmpty() {
        assertEquals(0, shipment.parcelCount());
    }

    @Test
    void addingParcelIncreasesCount() {
        shipment.add(new Parcel(1200, "Alex"));
        assertEquals(1, shipment.parcelCount());
    }
}

The setup runs before each method. The second test does not alter the shipment used by the first. By default, Jupiter also creates a new test-class instance for each test method. Mutable static fields remain shared, so a static list can still leak state across those instances.

@AfterEach performs per-test cleanup. @BeforeAll and @AfterAll run once for the class and are static under the default lifecycle. Shared read-only reference data can suit once-per-class setup; changing objects usually need fresh per-test construction. Tests must work individually without depending on source-file order.

Use one test body for a table

A parameterized test runs the same behavior check for several rows:

@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
            "10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
    assertEquals(expected,
        new ShippingCalculator().feeInCents(grams));
}

Each row is a separate invocation with its own report. @ValueSource(ints = {0, -5}) supplies one integer per invocation for invalid-input tests. Parameterized annotations come from org.junit.jupiter.params; CsvSource and ValueSource are in its provider package. The junit-jupiter aggregate dependency includes the parameterized-test module.

@DisplayName adds a readable report label. @Disabled records a skipped test with a reason; a skipped check provides no passing evidence for the behavior it would test.

Control the environment

A service test can receive an InMemoryParcelRepository, a working fake that stores parcels in memory. This avoids real file contents and isolates the logic. Pass a Scanner built from a known string to a text UI when testing its input handling. A fixed date or a Random with a fixed seed similarly makes an outside input predictable.

A flaky test changes its result across runs without a relevant code change. Shared mutable data, the current time, uncontrolled random values, and leftover files are common causes. Give the test control of those inputs. Keep helpers small and setup readable; a fixture that constructs many unrelated objects can reveal that the tested class has too many jobs.

Key Takeaways

  • Use @BeforeEach to give tests fresh fixtures.
  • A fresh test-class instance does not reset mutable static state.
  • Parameterized tests run one method with separately reported input rows.
  • Use fakes and supplied inputs to keep logic tests independent of real storage and the clock.
  • Keep every test runnable alone, without a required method order.

4.4 Build behavior through red, green, refactor

Test-driven development (TDD) starts a behavior change with a test. A short cycle makes the missing behavior visible, implements it, and then improves the code's structure. A test list holds cases still to explore without requiring them all to be written at once.

Take one item from the test list

For a Gregorian leap-year checker, list four behaviors: an ordinary non-leap year, a year divisible by four, a century that is not divisible by 400, and a century that is divisible by 400. These cases represent different parts of the rule.

Red: write one test and run it. A missing method may cause a compile error. Once the method exists, verify that the test fails for the behavior you intend to add. A missing assertion, the wrong method call, or an undiscovered test can make an apparent green result misleading. A new case may also already be satisfied by existing code; inspect that result before changing the implementation.

Green: add the simplest code that passes the new test and all previous tests. Refactor: improve structure without changing behavior, rerunning tests after each small change. Then choose the next case.

Let cases develop the rule

The first check is assertFalse(LeapYears.isLeapYear(2023)). Creating the method with return false; passes this case. It is a deliberate first step, not a finished calendar rule.

The next check, assertTrue(LeapYears.isLeapYear(2024)), fails. Change the method to return year % 4 == 0;. Both cases now pass.

Next, assertFalse(LeapYears.isLeapYear(1900)) exposes the century exception. Changing the expression to year % 4 == 0 && year % 100 != 0 makes those three tests pass.

Finally, assertTrue(LeapYears.isLeapYear(2000)) exposes the exception to the century rule. The implementation becomes:

public static boolean isLeapYear(int year) {
    return year % 400 == 0
        || (year % 4 == 0 && year % 100 != 0);
}

Run all four checks after the change. Each new failure pointed to one missing part of the behavior instead of asking you to debug a large unfinished implementation.

Improve structure while green

Refactoring changes names or organization while preserving behavior. Renaming a method, extracting a helper, removing repeated logic, and moving a class to a better package are refactorings when observable behavior stays the same. Changing the fee charged at a boundary is a behavior change and needs a test describing that change.

If a cleanup breaks a test, restore the intended behavior before adding another feature. Small changes help locate the cause of a failure. Skipping cleanup leaves passing code that becomes harder to extend; writing a large implementation for many future cases creates more code to inspect when the next check fails.

TDD depends on the quality of the requirements and selected cases. A misunderstood rule can appear in both the test and implementation. Logic with clear inputs and outputs is a useful starting point because the expected result can be stated independently and checked quickly.

Key Takeaways

  • Keep a test list and implement one behavior at a time.
  • Red makes the missing behavior visible; green makes all current tests pass.
  • Inspect an unexpectedly passing test before assuming it checked the intended change.
  • Refactor structure while preserving behavior and rerun the tests.
  • Use successive cases to expose ordinary rules and exceptions.
5

Build and test projects with Maven or Gradle

A project needs more than source files: it needs libraries, compilation settings, a test runner, and a way to package the result. Maven and Gradle describe those choices so a command can repeat the build. You will use their standard folders, declare Jupiter dependencies, run the included wrapper, and distinguish a code error from a test-discovery or dependency problem by reading the build output.

5.1 Build a Maven project from its description

A build tool turns source files into a checked, packaged result. Maven reads a project description, downloads declared libraries, compiles code, runs tests, and creates an artifact. The same description gives the command line and IDE a shared view of the project.

Use the standard layout

Put production Java code in src/main/java, tests in src/test/java, production resources in src/main/resources, and test data in src/test/resources. Package folders begin below each Java source root. Maven writes generated output under target; edit the sources, not the compiled copies in that folder.

The project-root file pom.xml, the Project Object Model, identifies the project using coordinates: groupId, artifactId, and version. For example, com.example:parcels:1.0-SNAPSHOT identifies a development version of the parcel program. A library dependency has coordinates too.

Declare JUnit for tests

The following is a complete small Maven configuration. It compiles for Java 17 and selects a test runner that supports Jupiter:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>parcels</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.4</version>
      </plugin>
    </plugins>
  </build>
</project>

Maven retrieves dependencies from repositories, normally Maven Central, and caches them in .m2/repository under your home folder. Libraries needed by a dependency are transitive dependencies. Test scope makes JUnit available for compiling and running tests, not for main-code compilation or the program's runtime classpath. An ordinary JAR keeps its dependency JARs separate unless packaging is configured to bundle them.

Ask for a lifecycle phase

The main lifecycle progresses through compile, test, package, and install among other phases. Asking for package runs earlier phases too. With the wrapper, ./mvnw test compiles and tests; ./mvnw package also creates the JAR under target; ./mvnw install additionally places the artifact in the local repository. The separate clean lifecycle, ./mvnw clean, removes generated output.

A test failure stops the normal build before packaging a new artifact. An older JAR may still be present until cleaned, so its presence does not establish that the latest build passed.

Make test discovery visible

Surefire's default patterns include names starting with Test or ending with Test, Tests, or TestCase. ShippingChecks does not match them; use ShippingCalculatorTest and inspect the reported test count. Tests placed under src/main/java cannot use a test-scoped dependency.

The Maven Wrapper, mvnw or Windows mvnw.cmd, runs the project-specified Maven version. Use a project that includes its wrapper files; a POM by itself does not create the scripts. From its root, the wrapper provides a consistent build command without a separate Maven installation.

Key Takeaways

  • Keep main code, tests, and resources in their standard source folders.
  • Identify projects and libraries with group, artifact, and version coordinates.
  • Declare JUnit with test scope and use a compatible Surefire runner.
  • A requested Maven phase runs the earlier phases in its lifecycle.
  • Inspect test counts; a successful build can still have missed a misnamed test.
  • Generated output belongs in target and can be rebuilt.
  • The wrapper runs the Maven version selected by the project.

5.2 Run Gradle tests and follow build errors

Gradle describes a build with a script and executes tasks with their dependencies. A Kotlin script is named build.gradle.kts; a Groovy script is named build.gradle. Plugins supply tasks, and dependency configurations state where libraries are needed.

Configure an application and Jupiter

A project-root settings.gradle.kts can contain rootProject.name = "parcels". A matching Kotlin build script is:

plugins {
    application
}

repositories {
    mavenCentral()
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

application {
    mainClass = "com.example.parcels.Main"
}

tasks.test {
    useJUnitPlatform()
}

The application plugin supplies Java build tasks and adds run. mavenCentral() selects a dependency repository. The BOM, or bill of materials, aligns JUnit module versions, so the version appears once. testImplementation supplies libraries needed to compile and run tests. testRuntimeOnly supplies the launcher only when running them. Main-code libraries use implementation, which also makes them available to test code.

useJUnitPlatform() selects the platform that runs Jupiter tests. Adding Jupiter libraries without configuring the runner can leave those tests undiscovered. Current Gradle fails when test sources exist but no tests are discovered under its default setting; inspect the test task output and configuration instead of removing the check.

Run tasks with the wrapper

Use the wrapper included with the project: ./gradlew test runs tests; ./gradlew build compiles, tests, and packages; ./gradlew run starts the configured main class; and ./gradlew clean removes build. Windows uses gradlew.bat. Gradle uses the same main/test source layout as Maven and writes generated output to build.

A task marked UP-TO-DATE has unchanged tracked inputs and outputs, so Gradle reuses its previous result. That message differs from a newly executed test run. The HTML test report is build/reports/tests/test/index.html.

Find the stage that failed

Start with the earliest useful error. “Cannot find symbol” during main compilation points to the named code, import, or dependency. Missing org.junit.jupiter.api during test compilation points to test dependency setup; the same error during main compilation can mean a test was put in the main source folder. A dependency resolution error points toward coordinates, repository access, or network availability.

A running test failure is different: the test was compiled and discovered, but its check failed. If "expected: <500> but was: <900>" appears at the 2,000 g fee boundary, inspect the comparison that chooses the band. Changing dependencies will not repair that calculation.

Maven offers a declarative XML description with fixed lifecycle phases. Gradle offers configurable scripts and tasks, including skipping work that is up to date. Both automate repeatable Java builds; use the tool chosen for the project and read its first actionable failure before changing code.

Key Takeaways

  • Plugins add Gradle tasks; configurations control where dependencies are available.
  • Align JUnit versions with a BOM and run Jupiter on the JUnit Platform.
  • Use the included Gradle Wrapper for test, build, run, and clean tasks.
  • Distinguish cached task results, compilation errors, dependency errors, and assertion failures.
  • Read the named failed test and expected/actual values before changing the implementation.

Java Packages examples

5 Walkthroughs

1

Move a parcel class into a package

13 steps
Connect a source path, imports, and access checks while separating UI code from domain code.
  1. 1
    IntroductionGive the class a clear home

    A parcel program has a calculator and a text UI. As these classes move into separate packages, Java needs names, folders, and access levels that agree.

    Follow the move in three passes: place the class, let another file name it, then check whether that caller can use its constructor and methods.

  2. 2
    ExampleStart below the source root

    The source root is src/main/java. For Parcel in com.example.parcels.domain, turn the package dots into folders and append the filename:

    src/main/java/com/example/parcels/domain/Parcel.java
    

    Write package com.example.parcels.domain; at the start of the file. The root’s src/main/java folders describe the build layout and stay out of the package declaration.

  3. 3
    ExplanationKeep the class identity intact

    The simple name is Parcel; the fully qualified name is com.example.parcels.domain.Parcel. Moving the file does not by itself rewrite that identity.

    A public class named Parcel belongs in Parcel.java. Run a packaged main class from the compiled tree’s root using a command such as java -cp out com.example.parcels.Main, so Java can find the package folders below out.

  4. 4
    ChallengeRepair a mismatched location

    A public class named Receipt declares package com.example.parcels.domain; but is stored below src/main/java/com/example/parcels/ui/. Its package is intended to stay unchanged. Where should the file go?

    ---reveal---

    Move it to src/main/java/com/example/parcels/domain/Receipt.java. The package path begins below the source root, and the filename matches the public class. Changing an import elsewhere would not repair the source layout.

  5. 5
    ExampleMake the type name convenient

    The UI has its own declaration, then imports the domain type:

    package com.example.parcels.ui;
    
    import com.example.parcels.domain.Parcel;
    

    Its class body can now use the simple name Parcel. The import applies to this file only. It resolves a name; it does not copy a class or make a dependency available to the build.

  6. 6
    Key InsightA wildcard stops at one package

    import com.example.parcels.*; does not include the domain package. The shared name prefix looks like a family tree, but each full package name denotes a separate package.

    The same rule explains why java.util.* does not supply java.util.function.Function. Use a single-type import, the proper package wildcard, or a fully qualified name for that type.

  7. 7
    ChallengeUntangle two lists

    A file needs both java.util.List and java.awt.List. It currently has single-type imports for both, and compilation fails. How can the file name both types without ambiguity?

    ---reveal---

    Keep one single-type import, such as import java.util.List;, and write java.awt.List in full wherever that type is needed. Two different single-type imports cannot give the same simple name two meanings in one file.

  8. 8
    TipRecognize names that need no import

    A file can use types in its own package without importing them. Accessible types from java.lang, including String, Math, and System, are also available automatically.

    A static import has a different job: importing Assertions.assertEquals statically allows a direct assertEquals(...) call. It shortens a static member reference, not a package name.

  9. 9
    ExampleCheck the constructor too

    The type is public, but its constructor has no modifier:

    public class Parcel {
        Parcel(int grams, String recipient) { }
    }
    

    That constructor is package-private. The UI can resolve Parcel through the import, but cannot construct it from the UI package. Mark the constructor public if creating parcels belongs to the domain’s intended public operations; keep its fields private.

  10. 10
    ChallengeOpen the operation, keep the helper hidden

    The UI must call a public calculator’s feeInCents method, currently declared with no access modifier. Only the calculator uses its package-private WeightBands helper. What access change is needed?

    ---reveal---

    Make feeInCents public and leave WeightBands package-private. The UI needs the calculation operation, not the table helper. Importing either class does not change its access rights.

  11. 11
    ExplanationA subclass has specific privileges

    Protected members are accessible to the entire declaring package and to subclasses under cross-package inheritance rules. In another package, a subclass can use its own inherited instance member.

    It cannot use a reference typed as the superclass to access that member on an arbitrary superclass object. Protected access is therefore broader than subclass-only access, yet narrower than public access.

  12. 12
    ChallengeDiagnose the whole access path

    TextUI imports a public Parcel correctly. Its call to new Parcel(800, "Jo") reports that the constructor cannot be accessed from outside the package. The constructor has no modifier. What fixes this intended cross-package creation?

    ---reveal---

    Make the constructor public. The package and import already resolve the type, so moving the file or adding a wildcard import does not address the reported access failure. Private parcel fields can stay private.

  13. 13
    RecapUse three separate checks

    Check location by matching the declaration to folders below the source root. Check naming through imports or fully qualified names. Check access on the type and the operation the caller actually uses.

    This order helps turn a vague “Java cannot use this class” problem into a specific source-layout, name-resolution, or visibility repair.

2

Read a parcel model and build its Java skeleton

13 steps
Translate UML boxes, relationship ends, and subtype lines into consistent Java declarations.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionRead structure before writing bodies
  2. 2
    ExampleTranslate one field and one method
  3. 3
    ExplanationSpot a class-level member
  4. 4
    ChallengePreserve the signs
  5. 5
    ExampleCount parcels from one shipment
  6. 6
    ExampleChoose a representation, then enforce it
  7. 7
    Key InsightFollow a one-way link
  8. 8
    ChallengeCheck a minimum count
  9. 9
    ExplanationPut ownership at the correct end
  10. 10
    ExampleRead two triangle lines
  11. 11
    TipBuild the declarations in order
  12. 12
    ChallengeCombine a contract and a collection
  13. 13
    RecapKeep each symbol’s job distinct
3

Trace one command through separate responsibilities

11 steps
Move a fee calculation out of the UI and connect a service to replaceable storage.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionFollow a command through the program
  2. 2
    ExampleExtract the rule from console code
  3. 3
    ExampleCalculate a started block
  4. 4
    Key InsightKeep validation with the rule
  5. 5
    ChallengeMake a calculation reusable
  6. 6
    ExamplePass storage into the service
  7. 7
    ConnectionReplace files with a working fake
  8. 8
    TipCheck calls and imports separately
  9. 9
    ChallengeRepair a shortcut around the service
  10. 10
    ChallengeConnect a controlled service test
  11. 11
    RecapPlace each change where its job lives
4

Grow a useful Jupiter test suite

19 steps
Write a first assertion, choose boundaries, isolate state, and develop a rule through small test-first changes.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionTurn requirements into executable checks
  2. 2
    ExampleArrange, act, and assert
  3. 3
    ExplanationRead expected and actual correctly
  4. 4
    TipAllow numerical rounding deliberately
  5. 5
    ChallengeRepair a test that only prints
  6. 6
    ExampleFind the fee transitions
  7. 7
    Key InsightKeep the expectation independent
  8. 8
    ExamplePut the rejected call inside the check
  9. 9
    ChallengeChoose the case that exposes the change
  10. 10
    ExampleGive each test a fresh shipment
  11. 11
    ExampleRun a table through one body
  12. 12
    ConnectionControl the collaborator, not the test order
  13. 13
    ChallengeDiagnose data left by another test
  14. 14
    ExampleLet an ordinary year start the cycle
  15. 15
    ExampleAdd the general divisible-by-four rule
  16. 16
    ExampleLet century cases refine the condition
  17. 17
    ChallengeChoose a next step while preserving checks
  18. 18
    ChallengeBring case selection and isolation together
  19. 19
    RecapTrust specific, repeatable evidence
5

Take a project from build file to test report

13 steps
Configure dependencies and test discovery, run wrappers, and diagnose failures at the stage where they occur.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake one command repeat the build
  2. 2
    ExplanationSeparate sources from generated output
  3. 3
    ExampleName the project and its test library
  4. 4
    ExampleRequest the phase you need
  5. 5
    TipDo not mistake zero tests for checked behavior
  6. 6
    ChallengeFind a misplaced test
  7. 7
    ExplanationSelect the Gradle tasks
  8. 8
    ExampleKeep Jupiter modules aligned
  9. 9
    ExampleTell the test task how to run Jupiter
  10. 10
    ChallengeRepair test discovery
  11. 11
    ExampleFollow a running test’s failure
  12. 12
    ChallengeCombine discovery with a fresh build
  13. 13
    RecapRead the build as evidence in stages

Java Packages practice questions

50 Practice Questions in 5 sets

Set 1: Names, folders, and Java access10 Practice Questions

Question 1 of 10

A public class Ticket declares package org.example.events.model;. What path should it have below the main Java source root?
  1. org/example/events/Ticket/model.java
  2. org/example/events/model/Ticket.java
  3. src/main/java/org/example/events/model/Ticket.java
  4. org.example.events.model/Ticket.java
Show answerHide answer
Answer: B
Each dot becomes a folder below the source root, and the public class name supplies Ticket.java. The root is already the starting location, so adding src/main/java again would duplicate the build-layout portion of the path.

Question 2 of 10

A class in package shop.ui needs a helper that currently has no package declaration. How should the helper be organized so the UI can import it?
  1. Leave it unnamed and add import Helper; to the UI source file.
  2. Place it in a folder called shop without adding a package declaration.
  3. Keep it unnamed and qualify its name with the main source-root path.
  4. Place it in a named package with accessible declarations.
Show answerHide answer
Answer: D
A class in a named package cannot import a type from the unnamed package. Give the helper a package declaration and matching location, then provide the access its caller needs; moving the file alone does not give it a named-package identity.

Question 3 of 10

A file has import java.util.*; and uses java.util.function.Predicate by its simple name. What additional single-type import supplies that name?
  1. import java.util.function.Predicate;
  2. import java.util.Predicate;
  3. import java.util.function;
  4. import static java.util.function.Predicate;
Show answerHide answer
Answer: A
Predicate lives in the separate java.util.function package, so its full type name is needed in the single-type import. The existing java.util.* wildcard does not reach subpackages, and a static import is for static members.

Question 4 of 10

A file imports java.util.* and java.awt.*, then declares List<String> names. How can it make List refer to the collection interface while retaining both wildcard imports?
  1. Add import static java.util.List;.
  2. Replace the declaration’s type with Object.
  3. Add import java.util.List;.
  4. Move both wildcards before the package declaration.
Show answerHide answer
Answer: C
An explicit single-type import takes precedence over imports on demand, resolving List to java.util.List. The two wildcard imports can remain; a static import would not identify this top-level type as a static member.

Question 5 of 10

A Jupiter test calls assertTrue(valid) without the Assertions class prefix. What import makes that direct call available?
  1. import org.junit.jupiter.api.Assertions.assertTrue;
  2. import static org.junit.jupiter.api.Test.assertTrue;
  3. import org.junit.jupiter.api.Assertions;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
Show answerHide answer
Answer: D
assertTrue is a static member of Assertions, so a static import makes the member callable directly. Importing the class alone permits Assertions.assertTrue(...), but does not remove the owning class name from the call.

Question 6 of 10

book.data.Record is public and has a constructor declared Record(int id). An unrelated class in book.ui imports it but cannot construct it. What access change supports that call?
  1. Make the constructor public.
  2. Make the constructor protected.
  3. Import book.data.* as well.
  4. Rename the UI package book.data.ui.
Show answerHide answer
Answer: A
The omitted modifier gives the constructor package access, and book.ui is a different package. Public access supports this unrelated caller; protected access would not, because the caller is neither in the declaring package nor using subclass privileges.

Question 7 of 10

An ordinary helper class declared class RateTable has no access modifier. Where can other top-level classes use it?
  1. In packages sharing its top-level name.
  2. Only in its exact declaring package.
  3. In any package after an explicit import.
  4. Only in subclasses, in any package.
Show answerHide answer
Answer: B
A top-level class without public is package-private, restricting its use to the exact same package. Imports resolve names without granting permission, so an explicit import from another package cannot open access to this helper.

Question 8 of 10

A class box contains # threshold: int and an underlined + reset(): void. How do those members translate into Java?
  1. A private integer field and a public abstract reset method.
  2. A package-private integer field and a public static reset method.
  3. A protected integer field and a public static reset method.
  4. A protected integer field and a public instance reset method.
Show answerHide answer
Answer: C
The # sign marks protected visibility, while underlining marks a static member. The plus sign makes reset public and its return type is void; underlining does not mean abstract, which uses italics or an abstract marking.

Question 9 of 10

What UML operation describes public boolean accepts(int grams)?
  1. + boolean accepts(int: grams)
  2. - accepts(grams: int): boolean
  3. + accepts(grams: boolean): int
  4. + accepts(grams: int): boolean
Show answerHide answer
Answer: D
UML keeps the operation name first, writes each parameter as name then type, and puts the return type after the parentheses. The plus sign preserves public visibility; reversing the parameter and result types would describe a different method.

Question 10 of 10

Package base has package-private class Engine. Its explicitly declared constructor and its method run() both have no access modifier. An unrelated Screen in base.ui must call new Engine().run(). What minimum visibility changes support that call?
  1. Make Engine public; keep the rest package-private.
  2. Make Engine and run public, not the constructor.
  3. Make Engine, its constructor, and run public.
  4. Make only the constructor and run public.
Show answerHide answer
Answer: C
The UI must access the type, call its constructor, and invoke its method. Each currently has package access, and base.ui is a separate package, so all three need public access for this unrelated caller. Opening only the type and method still leaves construction blocked.
Set 2: Class relationships and layered responsibilities10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

On a library–book association, 0..* is written beside Book. How many books may one library be linked to?
  1. At least one book.
  2. Exactly one book.
  3. Zero or more books.
  4. Zero or one book.
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A course must have one or more sessions. Its List<Session> field starts empty, and no method checks its size. What is missing from the implementation?
  1. Checks that establish and preserve the minimum session count.
  2. A different list import so the collection enforces a minimum.
  3. A static session field shared by every course instance.
  4. A protected list field allowing sessions to fill it directly.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A folder model groups documents that may be shared and continue to exist after the folder is removed. Does the requirement support composition of documents by that folder?
  1. Yes; every collection of references establishes composite ownership.
  2. Yes; a folder is the whole, so document lifetime does not matter.
  3. No; composition is reserved for classes without collection fields.
  4. No; shared independent documents lack composite ownership.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A class has a dashed UML line ending in a hollow triangle at Printable. What Java subtype declaration does that line represent?
  1. extends Printable
  2. implements Printable
  3. imports Printable
  4. new Printable()
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

Basket contains private List<Product> products; and does not extend Product. How should the main relationship be shown?
  1. An association toward Product, with a many-valued Product end.
  2. A solid generalization line from Basket with its triangle at Product.
  3. A dashed realization line from Basket with its triangle at Product.
  4. A navigable association from Product to Basket with a many-valued Basket end.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A grade calculator both computes an average and prints it. A web interface needs the calculation. What change makes that logic directly reusable?
  1. Copy the calculation into the web interface.
  2. Add a web print format inside the calculator.
  3. Return the average; let interfaces format it.
  4. Have the calculator read the interface name first.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A text UI rejects nonnumeric input before calling a calculator. The calculator must also reject negative numeric values for every caller. Where should that rule live?
  1. Only in the text UI, alongside numeric parsing.
  2. In the calculator, with messages handled by the UI.
  3. In the main method, after creating the calculator.
  4. In the repository, after storing the calculated result.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A service constructor accepts a CatalogRepository interface. How can a test avoid the real database?
  1. Pass a database filename to the existing real implementation.
  2. Make the service constructor create the database repository.
  3. Move the service into the presentation package for the test.
  4. Inject an in-memory CatalogRepository implementation.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A booking screen saves directly through a repository. The service contains a seat-limit check and creates its own file repository. Tests need to verify that limit without disk access. What paired change supports both the screen and the tests?
  1. Route screen requests through the service and inject the repository into it.
  2. Keep the direct screen save and inject the repository only into the screen.
  3. Route screen requests through the service and share one static file repository.
  4. Move the seat-limit check into the screen and let the service keep choosing files.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

rules.OrderService imports storage.OrderRepository. That interface names rules.Order in its signatures, and storage implementations implement it. How can the contract be relocated to remove the rules/storage package cycle while retaining interchangeable storage?
  1. Move Order to storage; OrderService imports a concrete store.
  2. Move the interface to rules; storage implements that contract.
  3. Move OrderRepository to the UI; both lower layers import it.
  4. Keep the layout; replace the imports with fully qualified names.
Show answerHide answer

Save this lesson to see this answer.

Set 3: Assertions, case selection, and independent tests10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A method returns actual, and its required result is 42. What Jupiter equality assertion labels the values correctly?
  1. assertEquals(actual, 42);
  2. assertTrue(actual);
  3. assertNotNull(42);
  4. assertEquals(42, actual);
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A correct double calculation differs from 0.6 by about 0.0000000000000001. The required numerical accuracy is 0.000001. How should its expected result be checked?
  1. assertEquals(0.6, actual);
  2. assertEquals(0.6, actual, 1.0);
  3. assertEquals(0.6, actual, 1e-6);
  4. assertNotNull(actual);
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A size rule accepts integers from 1 through 500, inclusive. Is 0 a valid input, a valid boundary input, or an invalid input?
  1. An invalid input.
  2. A valid boundary input.
  3. A valid input.
  4. An unspecified input.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A sensor accepts integer readings through 800 inclusive. A developer changes its upper check from reading <= 800 to reading < 800. What valid reading exposes the error?
  1. 799
  2. 800
  3. 801
  4. 400
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A method must reject an empty string with IllegalArgumentException. How should a Jupiter test run the call?
  1. assertThrows(IllegalArgumentException.class, () -> parser.parse(""));
  2. assertThrows(IllegalArgumentException.class, parser.parse(""));
  3. assertEquals(IllegalArgumentException.class, parser.parse(""));
  4. assertNotNull(() -> parser.parse(""));
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A tariff charges 250 cents through 3,000 g, then 50 cents per started 500 g above that. Its implementation uses 250 + ((grams - 3000) / 500) * 50 above the limit. What new test most directly exposes its rounding defect?
  1. 3,500 g must cost 300 cents.
  2. 4,000 g must cost 350 cents.
  3. 3,001 g must cost 300 cents.
  4. 3,000 g must cost 250 cents.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A new test-class instance is created for each test, but a static mutable list retains entries from an earlier test. What setup gives each scenario its own list?
  1. Rename the test methods to force an alphabetical order.
  2. Use a non-static list initialized for each test.
  3. Create one list in a static BeforeAll method.
  4. Keep the static list and add DisplayName labels.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A discount test reads the current date and uses a static repository containing prior customers. It passes alone but changes results across suite runs. How should its setup be redesigned?
  1. Keep today’s date and run the test first in every suite.
  2. Use a fixed date and reuse the shared populated repository.
  3. Recreate the service only, leaving both outside inputs unchanged.
  4. Supply a fixed date and a fresh repository for each scenario.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A fee rule has eight input–expected-result rows using the same assertion. How can Jupiter keep separate reported cases without eight copied method bodies?
  1. Use a parameterized test with the rows as its argument source.
  2. Use one test that prints every result but asserts only the first.
  3. Use BeforeAll to run the fee calculation once for the whole class.
  4. Use Disabled on the repeated methods and retain one active method.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

All current tests pass. You want to rename a helper and extract repeated logic without changing any output. What TDD activity is this?
  1. Adding a new red case for a different feature.
  2. Changing expected results to match the helper’s output.
  3. Refactoring while rerunning the passing tests.
  4. Skipping earlier tests until another behavior is implemented.
Show answerHide answer

Save this lesson to see this answer.

Set 4: Build configuration and failure reports10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A Gradle project has Jupiter tests and declares their libraries. What call inside the test task selects their execution platform?
  1. mavenCentral()
  2. testImplementation()
  3. mainClass.set()
  4. useJUnitPlatform()
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What is the usual filename for a Gradle build script written in Kotlin?
  1. settings.gradle.kts
  2. build.gradle.kts
  3. build.gradle
  4. pom.xml
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Where does a standard Maven project put unit-test Java source files?
  1. src/test/java
  2. src/main/java
  3. target/test-classes
  4. src/test/resources
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A project uses JUnit only in tests. What Maven dependency scope keeps it available for test compilation and execution without adding it to main classpaths?
  1. compile
  2. test
  3. runtime
  4. provided
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

With a normal Maven lifecycle and no skipping configured, what happens before the package phase creates a JAR?
  1. Only main code compiles; tests run separately.
  2. An older installed JAR is copied into the sources.
  3. Main and test code compile, and the tests run.
  4. The clean lifecycle always deletes output first.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A Java 17 Maven project declares only JUnit Jupiter 6.1.3 but configures Surefire 2.12.4. Its test file TotalsChecks.java imports org.junit.Test. Surefire includes are unchanged. What combined repair lets the class compile and run as a discovered Jupiter test?
  1. Rename it TotalsTest and use Jupiter @Test, but keep Surefire 2.12.4.
  2. Keep TotalsChecks, use Jupiter @Test, and select Surefire 3.5.4.
  3. Rename it TotalsTest and select Surefire 3.5.4, but keep org.junit.Test.
  4. Rename it TotalsTest, use Jupiter @Test, and select Surefire 3.5.4.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A JUnit BOM is declared with Gradle’s platform(...). Why can related JUnit dependency declarations omit individual version numbers?
  1. The Java compiler infers the newest installed version of each module.
  2. The wrapper replaces every dependency version with its Gradle version.
  3. The BOM supplies aligned version constraints for the JUnit modules.
  4. The test task downloads any module whose class name appears in a test.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A Gradle project cannot compile a test because org.junit.jupiter.api is missing. Its only Jupiter declaration is testRuntimeOnly("org.junit.jupiter:junit-jupiter"), and the test task already uses the JUnit Platform. What configuration repair addresses this stage?
  1. Declare Jupiter with testImplementation so test compilation can see its API.
  2. Add another useJUnitPlatform call without changing the dependency.
  3. Move the test into main sources and leave runtime-only Jupiter unchanged.
  4. Keep runtime-only Jupiter and add a DisplayName annotation to the test.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A service test reuses a static file repository. It passes in the IDE after a reset but fails during Gradle’s full suite because earlier tests stored extra records. The service already accepts a repository interface. What repair isolates its behavior?
  1. Inject a fresh in-memory repository arranged with only this test’s records.
  2. Use BeforeAll to share the same file repository across the entire suite.
  3. Run Gradle clean and keep the shared repository’s data assumptions.
  4. Make the test public and rely on the full suite’s current execution order.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A new TDD check passes immediately, although you intended it to expose missing behavior. What is the appropriate next investigation?
  1. Add production code until the passing test begins to fail.
  2. Confirm that it runs, asserts the intended result, and may already be satisfied.
  3. Delete the assertion because a passing result proves the new behavior is absent.
  4. Refactor unrelated classes before checking which method the test calls.
Show answerHide answer

Save this lesson to see this answer.

Set 5: Diagnose a complete Java change10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A running test reports "expected: <18> but was: <21>" from a correctly ordered equality assertion. What value did the method return?
  1. 18
  2. 3
  3. 21
  4. 39
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

An association shows 1 beside Department and 0..* beside Employee. What does the model allow for one department and require for one employee?
  1. A department requires one employee; each employee may have no departments.
  2. A department may have no employees; each employee has exactly one department.
  3. A department requires many employees; each employee has exactly one department.
  4. A department may have no employees; each employee may have many departments.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A method returns a list containing three specified values, but the internal collection type may change. How should the test check the result?
  1. Read the private backing array and assert its exact capacity.
  2. Assert that its concrete collection class matches the current one.
  3. Print the elements and let the runner compare them automatically.
  4. Compare the returned contents with the three required values.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Class base.Item has a protected instance field weight. In another package, Special extends Item has an instance method with parameters Item a and Special b. Inside that method, which expressions can access the inherited field directly?
  1. this.weight and b.weight
  2. this.weight and a.weight
  3. a.weight and b.weight
  4. this.weight, a.weight, and b.weight
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A diagram shows MemoryArchive realizing interface Archive and holding one or more Entry objects through a navigable association. A generated class currently says extends Archive and creates an empty unchecked list. What combined correction preserves the model?
  1. Use implements Archive but keep the unchecked empty list because List enforces one or more.
  2. Use implements Archive and reject empty constructor inputs, but permit removing the last entry.
  3. Use implements Archive and enforce a nonempty entry collection through construction and updates.
  4. Use implements Archive and replace the collection with one optional Entry reference.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A reporting service imports UI formatters and creates its own file store. Its calculation is needed by a second interface and by tests without disk access. What design repair addresses both dependencies?
  1. Return formatted console text and let the service keep constructing the file store.
  2. Move the file store into the UI package and keep UI formatter calls in the service.
  3. Make the service’s formatter and file store static so both interfaces can share them.
  4. Return report data, keep formatting in the interfaces, and inject a storage contract.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A TDD checker currently returns year % 4 == 0 and passes tests for 2031 and 2032. A new test for 2100 must reject that year, and a test for 2400 must later accept it. What sequence of behavior changes fits the pending cases?
  1. Exclude divisible-by-400 years for 2100, then restore divisible-by-100 years for 2400.
  2. Exclude divisible-by-100 years for 2100, then restore divisible-by-400 years for 2400.
  3. Exclude divisible-by-100 years first, then require divisibility by 400 for every leap year.
  4. Exclude divisible-by-100 years first, then allow multiples of 400 only when not divisible by 100.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A packing rule rejects nonpositive weights, charges 400 cents through 2,500 g, and 700 cents above. Its tests cover only 1,000 g. A change accidentally accepts zero and uses < 2500 for the lower band. What additional pair of checks exposes both defects?
  1. Zero must throw; 2,500 g must return 400 cents.
  2. Zero must throw; 2,501 g must return 700 cents.
  3. 1 g must return 400 cents; 2,501 g must return 700 cents.
  4. Negative 1 g must throw; 2,499 g must return 400 cents.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A Gradle script contains testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") and tasks.test { }. A Jupiter test class is stored under src/main/java, and its annotation fails to compile. What changes let the class compile and run under Jupiter?
  1. Keep it in main sources, use testImplementation for Jupiter, and select the JUnit Platform.
  2. Move it to test sources, keep runtime-only Jupiter, and select the JUnit Platform.
  3. Move it to test sources, use testImplementation for Jupiter, and retain the default runner.
  4. Move it to test sources, use testImplementation for Jupiter, and select the JUnit Platform.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A normal Maven package run stops at a failing test, but a JAR from yesterday remains in target. What does that file establish about today’s source?
  1. It establishes that today’s source passed its tests.
  2. It establishes that packaging ignored the failure.
  3. It establishes only that an older artifact exists.
  4. It establishes that Maven rebuilt yesterday’s source.
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

73 Key Terms, each with its definition

A

Access modifier
An access modifier is a keyword that controls which code may use a declaration, such as a class, constructor, field, or method.
A public class can still have a constructor that a UI class in another package is unable to call.
Aggregation
Aggregation is a loose whole–part grouping, shown by a hollow diamond, whose detailed meaning depends on the modeled design.
A team can group players who also exist independently, without claiming ownership of their lifetimes.
Application logic
Application logic is the code that implements a program’s rules and operations on data, independently of its dialog with the user.
A fee calculator chooses the correct fee band without asking for input or writing to the console.
Arrange–act–assert
Arrange–act–assert is a test-writing pattern that separates preparing a scenario, performing its action, and checking its outcome.
Creating a shipment, adding one parcel, and checking its count give the test three clear parts.
Artifact coordinates
Artifact coordinates identify a Maven project or library using its group, artifact name, and version so the build can resolve the intended artifact.
The coordinates distinguish one JUnit library version from another without copying JAR files by hand.
Assembly
Assembly is the creation and connection of collaborating program objects, commonly performed near the application’s entry point.
A main method creates one scanner and a calculator, passes them to a UI object, and starts its dialog.
Assertion
An assertion in a test is an executable check that fails the test when the observed result does not meet the stated expectation.
An equality assertion makes a wrong fee fail automatically instead of leaving it in printed output.
Association
An association is a structural relationship linking objects of different types, commonly implemented by stored reference fields.
A shipment’s parcel list can be drawn as an association with the role name parcels at the parcel end.
Attribute
An attribute is a named property shown in a UML class box, commonly corresponding to a Java field with a type and visibility.
The entry - weightGrams: int represents a private integer field holding the parcel’s weight.

B

Bill of materials
A bill of materials is dependency metadata that aligns related module versions so a build can keep a family of libraries consistent.
The JUnit BOM supplies matching module versions after one common version is selected.
Boundary case
A boundary case uses an input at or beside the edge of a specified rule, where a small change can alter the expected behavior.
When a fee band ends at 2,000 g, testing exactly 2,000 g exposes an incorrectly strict comparison that a 1,500 g test would miss.
Build lifecycle
A build lifecycle is Maven’s ordered sequence of phases, where requesting a later phase runs the earlier phases in that lifecycle.
Requesting package normally runs the test phase before producing the project’s packaged artifact.
Build tool
A build tool automates project tasks such as resolving dependencies, compiling source code, running tests, and packaging artifacts.
One Maven command can compile a parcel project, run its tests, and produce the resulting JAR.

C

Composition
Composition is a whole–part association with exclusive composite ownership of a part at a time, shown by a filled diamond at the whole.
An order and its order lines can use composition when removing the order also removes those parts.

D

Data access layer
The data access layer implements saving and retrieving persistent data, keeping storage details separate from application rules.
A file repository handles the file format while a parcel service handles valid parcel weights.
Dependency
A dependency is a use relationship in which one element relies on another, drawn as a dashed line with an open arrowhead.
A fee operation taking a Parcel parameter creates a dependency on the parcel type’s definition.
Dependency configuration
A dependency configuration defines where a Gradle dependency is available, such as main implementation, test compilation, or test runtime.
A platform launcher can use testRuntimeOnly because test sources do not need it during compilation.
Dependency injection
Dependency injection supplies an object’s collaborators from outside it, allowing their implementations or inputs to be selected by the caller.
Passing a repository into the service constructor lets the test choose controlled storage.
Dependency scope
A dependency scope controls which Maven classpaths include a library, separating test-only requirements from main program requirements.
Test scope makes JUnit available under src/test/java without making it a main-code dependency.

E

Equivalence class
An equivalence class in testing is a group of inputs that the specification treats alike, represented by useful sample cases and boundaries.
Several positive weights in the same weight band share a fee, while zero belongs to an invalid-input class.
Exception assertion
An exception assertion runs a supplied operation and checks whether it throws the required type of exception or an accepted subtype.
assertThrows can verify that a zero weight is rejected and return the exception for a message check.
Expected outcome
An expected outcome is the result or behavior required by the specification, determined independently of the code being tested.
When heavy parcels are charged per started weight block, the expected fee comes from counting blocks, not from copying the method’s output.

F

Fake
A fake is a simple working implementation of a dependency used in tests, providing usable behavior with controlled resources.
An in-memory repository stores parcels for service tests without opening or changing a real file.
Flaky test
A flaky test gives inconsistent results without a relevant code change, often because time, shared state, or outside data is uncontrolled.
A test reading the current date can change its outcome when the calendar changes overnight.
Fully qualified name
A fully qualified name identifies a Java type by combining its package name with its simple name, removing package-name ambiguity.
Using java.util.List distinguishes the collection interface from another type also called List.

G

Generalization
Generalization is a UML subtype relationship drawn with a solid line and a hollow triangle pointing toward the more general type.
A line from Letter toward Item translates into a Java declaration in which Letter extends Item.
Gradle task
A Gradle task is a unit of build work, such as compilation or testing, that can depend on other tasks and reuse unchanged results.
Running the build task also brings in the compilation and testing work required for that build.

I

Import declaration
An import declaration lets one Java source file use a type or static member by a shorter name without changing its accessibility.
A UI class that imports Scanner can use the simple name in its field declaration instead of java.util.Scanner.
Interface contract
An interface contract specifies operations an implementation offers, allowing callers to use the interface type without choosing its implementation.
A service can call save through the repository contract whether storage uses memory or a file.

J

JUnit Jupiter
JUnit Jupiter is the JUnit programming model and test engine that provides modern Java test annotations, assertions, and extensions.
Using Jupiter’s @Test keeps the test annotation consistent with the engine selected by the build.
JUnit Platform
The JUnit Platform is the foundation that discovers and launches supported test engines, including the engine for Jupiter tests.
Gradle selects it with useJUnitPlatform() when the project’s tests use Jupiter annotations.

L

Lambda expression
A lambda expression supplies a small operation as a value so other code can decide when and how to execute that operation.
The () -> calculator.feeInCents(0) lambda lets JUnit run the invalid call inside its assertion.
Layer
A layer groups classes with a shared kind of responsibility and offers operations to other parts through a defined boundary.
A parcel service applies validation before it asks a storage layer to save a new parcel.
Lifecycle hook
A lifecycle hook is a method the test framework runs at a defined point, such as before each test or once before the whole class.
@BeforeEach rebuilds changing objects, while @AfterEach can remove temporary test resources.

M

Multiplicity
Multiplicity states the allowed number of linked objects at a relationship end for one object at the opposite end.
Writing 1..* beside Parcel requires each shipment to retain at least one linked parcel.

N

Name clash
A name clash occurs when a simple name can refer to conflicting declarations, requiring the code to distinguish the intended type.
A file needing two types called List can import one and use the fully qualified name of the other.
Navigability
Navigability describes whether an object can follow a relationship toward another object, often shown with an open arrowhead.
An arrow from shipment to parcel shows that a shipment can reach its parcels through references.

O

Operation
An operation is a behavior declared in a UML type, described by its name, parameter types, visibility, and return type.
The operation + totalWeightGrams(): int describes a public method without giving its algorithm.

P

Package
A package is a named group of Java types that organizes related code, separates type names, and provides an access boundary.
Separate UI and domain packages keep command handling apart from the classes that calculate parcel fees.
Package cycle
A package cycle exists when a chain of type dependencies leads from a package back to itself, linking their definitions in both directions.
Placing the repository interface beside domain types can remove a domain/data import cycle.
Package declaration
A package declaration assigns the types in a source file to a named package and appears before its imports and class declarations.
Writing package com.example.parcels.domain; makes that package part of the class’s full name.
Package grouping
A package grouping is a folder-shaped area in a UML diagram that organizes type boxes according to their package membership.
Grouping UI and domain classes separately makes references across those areas easier to inspect.
Package-private access
Package-private access allows an ordinary class member or top-level type to be used only from its exact declaring package.
A helper without a public modifier stays unavailable to a UI class in a neighboring package, even with an import.
Parameterized test
A parameterized test executes one test method for several supplied argument sets, reporting each invocation as a separate case.
A weight-and-fee table can check several boundaries without repeating the assertion method body.
Plugin
A plugin adds build capabilities and configuration, often supplying tasks and conventions for a particular kind of project.
Gradle’s application plugin adds a run task for the configured parcel main class.
Presentation layer
The presentation layer translates user interaction into requests to the program and presents the returned results to the user.
A text UI can format a fee in a sentence without placing that sentence inside the code that calculates the fee.
Protected access
Protected access permits use within the declaring package and restricted inherited access by subclasses in other packages.
A subclass in another package can read its own inherited protected field without making that field public.
Public surface
A package’s public surface is the set of accessible types and operations it deliberately offers to code outside the package.
A domain package can expose a shipping calculator while hiding the helper that stores its weight bands.

R

Realization
Realization is a relationship in which a type fulfills an interface contract, shown by a dashed line and a hollow triangle.
An in-memory repository realizes the same repository interface that a file-based repository realizes.
Red–green–refactor
Red–green–refactor is the TDD cycle of exposing missing behavior, making all current tests pass, and improving structure while preserving behavior.
After the new fee case passes, extracting repeated calculation code belongs in the refactor step.
Refactoring
Refactoring changes the internal organization of code while preserving its observable behavior, such as by renaming or extracting a method.
Moving a fee helper into a better package is a refactoring when the calculator still gives the same results.
Regression
A regression is a defect introduced into behavior that previously worked, often after a change in another part of the code.
An existing fee-boundary test catches a regression when a comparison changes from inclusive to strict.
Repository
A repository offers operations for storing and retrieving domain objects behind a contract that can have different implementations.
A test supplies an in-memory repository to avoid depending on a real parcel data file.

S

Separation of responsibilities
Separation of responsibilities assigns different jobs to different parts of a program so each can be understood, changed, and tested more easily.
Changing the prompt wording should require no edit to the method that computes a parcel’s fee.
Single-type import
A single-type import makes one accessible type available by its simple name and explicitly states which package supplies it.
The declaration import java.util.ArrayList; identifies exactly which ArrayList the file uses.
Source root
A source root is the folder below which Java package paths begin, such as src/main/java for production code in a standard build.
For parcels.domain.Parcel, the path below the source root is parcels/domain/Parcel.java.
Static import
A static import lets a source file name an accessible static member directly, omitting the name of the class that declares it.
JUnit tests often statically import assertEquals so each check reads as a direct assertion call.
Static member
A static member belongs to a class instead of an individual object and is shown by underlining its UML attribute or operation entry.
Underlining a maximum-weight constant distinguishes one shared class value from each parcel’s weight.
Superclass
A superclass is the class from which a subclass inherits members and behavior; UML places the generalization triangle at this class.
Both letters and parcels can inherit shared item behavior without repeating it in each subclass box.
Surefire
Surefire is the Maven plugin that runs unit tests during the test phase using configured test frameworks and discovery rules.
Naming a test class ShippingCalculatorTest matches a default Surefire inclusion pattern.

T

Test fixture
A test fixture is the collection of objects and data arranged for a test, providing the controlled starting state its scenario needs.
A fresh empty shipment before each test prevents parcel additions from leaking into another scenario.
Test independence
Test independence means each test provides its own required state and can pass or fail correctly when run alone or in any order.
A count test creates its own parcels instead of relying on a previous method to add them.
Test list
A test list records behaviors and cases still to cover, allowing the developer to select one small next step and add discoveries as needed.
A leap-year list includes ordinary years, divisible-by-four years, and the two century cases.
Test-driven development
Test-driven development is a development method that uses a failing test to guide a small behavior change before improving the code’s structure.
A failing century-year case guides the next change to a leap-year checker instead of a large rewrite.
Tolerance
A tolerance is an allowed numerical difference used by an assertion when exact equality is unsuitable for a floating-point calculation.
A small justified tolerance accepts a rounding difference without accepting a substantially wrong result.
Transitive dependency
A transitive dependency is a library required by another dependency, resolved by the build tool along with the directly requested artifact.
Declaring the Jupiter aggregate also brings in the modules needed for its testing features.

U

UML class diagram
A UML class diagram models static software structure through type boxes, attributes, operations, and relationships between types.
A parcel diagram shows what a shipment stores without tracing the order of commands a user enters.
Unit test
A unit test is an automated check of a small unit of code that compares observed behavior with an independently specified outcome.
A calculator test calls the fee method and asserts the expected number of cents without starting the UI.
Unnamed package
The unnamed package holds types from source files without package declarations; named packages cannot import those types.
Moving shared helpers into a named package lets the other named parts of a project refer to them.
User interface
A user interface is the part of a program that accepts user actions and presents results, such as a text-command dialog.
A text UI for a fee calculator asks for grams, reads the reply, calls the calculator, and prints the returned fee.

V

Visibility sign
A visibility sign is a UML prefix that marks access: + public, - private, # protected, or ~ package access.
A field shown with - should retain private visibility when the diagram is translated into Java.

W

Wildcard import
A wildcard import makes accessible types in one package available on demand, without making types in its subpackages available.
Importing java.util.* still leaves java.util.function.Function needing its own import or full name.
Wrapper
A wrapper is a project-supplied launcher that retrieves and runs the build-tool version chosen by the project for repeatable commands.
A checked-in Maven wrapper lets contributors run the specified Maven version with ./mvnw test.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started