Packages, Class Diagrams and Unit Tests in Java
IntermediateLesson 10 of 11 in Object-Oriented Programming with Java Course
What you will learn
- 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 foldersWrite 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 importsImport 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 packagesChoose between public, protected, package-private and private so that each package exposes only what other packages need.
- Declare packages and match them to folders
- 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 classShow a class's name, attributes and operations with types and visibility, and mark static members, abstract classes and interfaces.
- Show associations and multiplicityConnect classes with associations, read and write multiplicities and navigability, and tell composition, aggregation and dependency apart.
- Model inheritance and move between diagrams and codeShow inheritance and interface implementation, write Java code from a diagram and draw a diagram from existing code.
- Draw and read a single class
- 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 logicKeep 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 dependenciesOrganize classes into a presentation layer, a logic layer and a data access layer, keep dependencies pointing downward and connect the layers through interfaces.
- Separate the user interface from the program logic
- 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 testCreate 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 bugsPick typical, boundary and invalid inputs, test expected exceptions and give each test one clear purpose and name.
- Keep tests independent and maintainableSet 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 TDDFollow the red–green–refactor cycle from a test list, take small steps and know what test-driven development does and does not promise.
- Write and run a first JUnit test
- 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 projectDescribe 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 buildsWrite the equivalent Gradle build script, run JUnit tests on the JUnit Platform, compare the two tools and find the cause of a failed build.
- Set up and build a Maven project
Java Packages study guide
1Organize 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/javaout 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.langtypes 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
| Access | Who can use the member? |
|---|---|
public | Code that can access the declaring type |
protected | The same package, plus subclass access across packages |
| No modifier | The same exact package |
private | The 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.
2Sketch 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 usesType 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.
| Multiplicity | Meaning |
|---|---|
1 | Exactly one |
0..1 | Zero or one |
* or 0..* | Any number, including zero |
1..* | At least one |
2..4 | Two 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
- Create each class or interface in its package.
- Translate attributes into fields, preserving names, types, and visibility.
- Translate navigable associations into reference or collection fields.
- Add
extendsorimplementsfrom the generalization or realization lines. - 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.
3Structure 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
mainand 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.
4Verify 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 grams | Expected outcome |
|---|---|
0, -5 | Reject with IllegalArgumentException |
1, 2000 | 500 cents |
2001, 10000 | 900 cents |
10001, 11000 | 1,000 cents |
11001 | 1,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
assertThrowsas 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
@BeforeEachto 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.
5Build 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
targetand 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
1Move a parcel class into a package
13 steps
- 1IntroductionGive 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.
- 2ExampleStart below the source root
The source root is
src/main/java. ForParcelincom.example.parcels.domain, turn the package dots into folders and append the filename:src/main/java/com/example/parcels/domain/Parcel.javaWrite
package com.example.parcels.domain;at the start of the file. The root’ssrc/main/javafolders describe the build layout and stay out of the package declaration. - 3ExplanationKeep the class identity intact
The simple name is
Parcel; the fully qualified name iscom.example.parcels.domain.Parcel. Moving the file does not by itself rewrite that identity.A public class named
Parcelbelongs inParcel.java. Run a packaged main class from the compiled tree’s root using a command such asjava -cp out com.example.parcels.Main, so Java can find the package folders belowout. - 4ChallengeRepair a mismatched location
A public class named
Receiptdeclarespackage com.example.parcels.domain;but is stored belowsrc/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. - 5ExampleMake 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. - 6Key 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 supplyjava.util.function.Function. Use a single-type import, the proper package wildcard, or a fully qualified name for that type. - 7ChallengeUntangle two lists
A file needs both
java.util.Listandjava.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 writejava.awt.Listin full wherever that type is needed. Two different single-type imports cannot give the same simple name two meanings in one file. - 8TipRecognize names that need no import
A file can use types in its own package without importing them. Accessible types from
java.lang, includingString,Math, andSystem, are also available automatically.A static import has a different job: importing
Assertions.assertEqualsstatically allows a directassertEquals(...)call. It shortens a static member reference, not a package name. - 9ExampleCheck 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
Parcelthrough 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. - 10ChallengeOpen the operation, keep the helper hidden
The UI must call a public calculator’s
feeInCentsmethod, currently declared with no access modifier. Only the calculator uses its package-privateWeightBandshelper. What access change is needed?---reveal---
Make
feeInCentspublic and leaveWeightBandspackage-private. The UI needs the calculation operation, not the table helper. Importing either class does not change its access rights. - 11ExplanationA 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.
- 12ChallengeDiagnose the whole access path
TextUIimports a publicParcelcorrectly. Its call tonew 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.
- 13RecapUse 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.
2Read a parcel model and build its Java skeleton
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionRead structure before writing bodies
- 2ExampleTranslate one field and one method
- 3ExplanationSpot a class-level member
- 4ChallengePreserve the signs
- 5ExampleCount parcels from one shipment
- 6ExampleChoose a representation, then enforce it
- 7Key InsightFollow a one-way link
- 8ChallengeCheck a minimum count
- 9ExplanationPut ownership at the correct end
- 10ExampleRead two triangle lines
- 11TipBuild the declarations in order
- 12ChallengeCombine a contract and a collection
- 13RecapKeep each symbol’s job distinct
3Trace one command through separate responsibilities
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow a command through the program
- 2ExampleExtract the rule from console code
- 3ExampleCalculate a started block
- 4Key InsightKeep validation with the rule
- 5ChallengeMake a calculation reusable
- 6ExamplePass storage into the service
- 7ConnectionReplace files with a working fake
- 8TipCheck calls and imports separately
- 9ChallengeRepair a shortcut around the service
- 10ChallengeConnect a controlled service test
- 11RecapPlace each change where its job lives
4Grow a useful Jupiter test suite
19 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTurn requirements into executable checks
- 2ExampleArrange, act, and assert
- 3ExplanationRead expected and actual correctly
- 4TipAllow numerical rounding deliberately
- 5ChallengeRepair a test that only prints
- 6ExampleFind the fee transitions
- 7Key InsightKeep the expectation independent
- 8ExamplePut the rejected call inside the check
- 9ChallengeChoose the case that exposes the change
- 10ExampleGive each test a fresh shipment
- 11ExampleRun a table through one body
- 12ConnectionControl the collaborator, not the test order
- 13ChallengeDiagnose data left by another test
- 14ExampleLet an ordinary year start the cycle
- 15ExampleAdd the general divisible-by-four rule
- 16ExampleLet century cases refine the condition
- 17ChallengeChoose a next step while preserving checks
- 18ChallengeBring case selection and isolation together
- 19RecapTrust specific, repeatable evidence
5Take a project from build file to test report
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake one command repeat the build
- 2ExplanationSeparate sources from generated output
- 3ExampleName the project and its test library
- 4ExampleRequest the phase you need
- 5TipDo not mistake zero tests for checked behavior
- 6ChallengeFind a misplaced test
- 7ExplanationSelect the Gradle tasks
- 8ExampleKeep Jupiter modules aligned
- 9ExampleTell the test task how to run Jupiter
- 10ChallengeRepair test discovery
- 11ExampleFollow a running test’s failure
- 12ChallengeCombine discovery with a fresh build
- 13RecapRead the build as evidence in stages
Java Packages practice questions
Set 1: Names, folders, and Java access10 Practice Questions
Question 1 of 10
Ticket declares package org.example.events.model;. What path should it have below the main Java source root?Show answerHide answer
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
shop.ui needs a helper that currently has no package declaration. How should the helper be organized so the UI can import it?Show answerHide answer
Question 3 of 10
import java.util.*; and uses java.util.function.Predicate by its simple name. What additional single-type import supplies that name?Show answerHide answer
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
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?Show answerHide answer
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
assertTrue(valid) without the Assertions class prefix. What import makes that direct call available?Show answerHide answer
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?Show answerHide answer
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
class RateTable has no access modifier. Where can other top-level classes use it?Show answerHide answer
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
# threshold: int and an underlined + reset(): void. How do those members translate into Java?Show answerHide answer
# 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
public boolean accepts(int grams)?Show answerHide answer
Question 10 of 10
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?Show answerHide answer
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
0..* is written beside Book. How many books may one library be linked to?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
List<Session> field starts empty, and no method checks its size. What is missing from the implementation?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Printable. What Java subtype declaration does that line represent?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?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
CatalogRepository interface. How can a test avoid the real database?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
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?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
actual, and its required result is 42. What Jupiter equality assertion labels the values correctly?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
double calculation differs from 0.6 by about 0.0000000000000001. The required numerical accuracy is 0.000001. How should its expected result be checked?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
reading <= 800 to reading < 800. What valid reading exposes the error?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
IllegalArgumentException. How should a Jupiter test run the call?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
250 + ((grams - 3000) / 500) * 50 above the limit. What new test most directly exposes its rounding defect?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
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
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
TotalsChecks.java imports org.junit.Test. Surefire includes are unchanged. What combined repair lets the class compile and run as a discovered Jupiter test?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
platform(...). Why can related JUnit dependency declarations omit individual version numbers?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
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?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
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
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
1 beside Department and 0..* beside Employee. What does the model allow for one department and require for one employee?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
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?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
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?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
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?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
< 2500 for the lower band. What additional pair of checks exposes both defects?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
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?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
target. What does that file establish about today’s source?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Access modifier
Aggregation
Application logic
Arrange–act–assert
Artifact coordinates
Assembly
Assertion
Association
parcels at the parcel end.Attribute
- weightGrams: int represents a private integer field holding the parcel’s weight.B
Bill of materials
Boundary case
Build lifecycle
Build tool
C
Composition
D
Data access layer
Dependency
Parcel parameter creates a dependency on the parcel type’s definition.Dependency configuration
Dependency injection
Dependency scope
src/test/java without making it a main-code dependency.E
Equivalence class
Exception assertion
assertThrows can verify that a zero weight is rejected and return the exception for a message check.Expected outcome
F
Fake
Flaky test
Fully qualified name
java.util.List distinguishes the collection interface from another type also called List.G
Generalization
Letter toward Item translates into a Java declaration in which Letter extends Item.Gradle task
I
Import declaration
Scanner can use the simple name in its field declaration instead of java.util.Scanner.Interface contract
save through the repository contract whether storage uses memory or a file.J
JUnit Jupiter
@Test keeps the test annotation consistent with the engine selected by the build.JUnit Platform
useJUnitPlatform() when the project’s tests use Jupiter annotations.L
Lambda expression
() -> calculator.feeInCents(0) lambda lets JUnit run the invalid call inside its assertion.Layer
Lifecycle hook
@BeforeEach rebuilds changing objects, while @AfterEach can remove temporary test resources.M
Multiplicity
1..* beside Parcel requires each shipment to retain at least one linked parcel.N
Name clash
List can import one and use the fully qualified name of the other.Navigability
O
Operation
+ totalWeightGrams(): int describes a public method without giving its algorithm.P
Package
Package cycle
Package declaration
package com.example.parcels.domain; makes that package part of the class’s full name.Package grouping
Package-private access
Parameterized test
Plugin
Presentation layer
Protected access
Public surface
R
Realization
Red–green–refactor
Refactoring
Regression
Repository
S
Separation of responsibilities
Single-type import
import java.util.ArrayList; identifies exactly which ArrayList the file uses.Source root
src/main/java for production code in a standard build.parcels.domain.Parcel, the path below the source root is parcels/domain/Parcel.java.Static import
assertEquals so each check reads as a direct assertion call.Static member
Superclass
Surefire
ShippingCalculatorTest matches a default Surefire inclusion pattern.T
Test fixture
Test independence
Test list
Test-driven development
Tolerance
Transitive dependency
U
UML class diagram
Unit test
Unnamed package
User interface
V
Visibility sign
+ public, - private, # protected, or ~ package access.- should retain private visibility when the diagram is translated into Java.W
Wildcard import
java.util.* still leaves java.util.function.Function needing its own import or full name.Wrapper
./mvnw test.About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.