← Back to Projects
BACKEND / SOFTWARE TESTING

Backend Services Optimization

A Java backend engineering project focused on designing modular service components, enforcing application constraints, and using JUnit testing to verify correctness across expected behavior, invalid input, and edge cases.

Java JUnit Object-Oriented Design Unit Testing Input Validation Backend Services
ORIGIN CS 320 Software Testing
ARCHITECTURE Independent Service Modules
QUALITY TARGET 80%+ Test Coverage

Building backend behavior before building an interface.

This project was developed as part of CS 320 and placed me in the role of a software engineer working for the fictional company Grand Strand Systems.

The assignment focused entirely on backend application behavior. Rather than building a user interface, I created service classes responsible for managing three application domains: contacts, tasks, and appointments.

The project treated correctness as something that should be designed, validated, and tested—not assumed because the code compiles.

Each domain had its own object representation, service manager, validation constraints, and JUnit tests. This made the project an exercise in both software construction and quality assurance.

Separating data models from service behavior.

The application was organized around independent domain services. Each domain represented its own data and exposed operations through a corresponding service class.

01

CONTACT SERVICE

Manages contact records and enforces constraints around identifiers, names, phone information, and addresses.

02

TASK SERVICE

Manages tasks while validating required fields such as task names and descriptions against defined limits.

03

APPOINTMENT SERVICE

Manages appointment scheduling and validates appointment information, including date-related constraints.

Keeping these responsibilities independent meant changes to one service could be reasoned about without mixing unrelated contact, task, or appointment behavior.

Managing contact data through controlled service operations.

The Contact service provides backend operations for creating, retrieving, updating, and deleting contact records.

IDENTIFIER

Contacts are distinguished through an ID so the service can reliably locate and manage individual records.

NAME DATA

Contact information is validated before it becomes accepted application state.

PHONE

Phone information is subject to expected format and length constraints.

ADDRESS

Address information is handled as part of the validated contact model.

The service also needed to account for conditions such as duplicate identifiers and invalid or null field values instead of allowing incorrect data to enter the system.

Enforcing constraints around task data.

The Task service follows the same modular approach while managing a different set of application requirements.

Task records include identifying information, a task name, and a description. The name and description are validated against defined character limits so that invalid objects cannot silently become part of the application state.

REQUEST TASK OPERATION Create, update, locate, or delete a task.
CHECK CONSTRAINTS Validate identifiers and required task properties.
APPLY SERVICE LOGIC Perform the requested operation only when state is valid.
VERIFY THROUGH TESTS Confirm both accepted and rejected behavior with JUnit.

Applying validation to scheduling behavior.

The Appointment service manages scheduling information through its own domain model and service operations.

Appointment data introduces constraints that differ from contacts and tasks because dates and scheduling rules must also be considered. This required validation around the state of appointment objects before the service accepted them.

IDENTITY

Appointment records require a stable identifier for reliable management.

DATE / TIME

Scheduling information is validated rather than accepted blindly.

DESCRIPTION

Appointment information is kept within its expected application constraints.

CONFLICT AWARENESS

Scheduling logic considers the validity of appointment state instead of treating every request as automatically acceptable.

Preventing invalid state before it spreads.

Validation was a central part of the project. The objective was not only to test whether valid objects worked, but also to make sure invalid data was rejected consistently.

NULL VALUES Verify required data cannot be omitted where the model does not permit it.
DUPLICATE IDS Ensure records that require unique identifiers cannot overwrite or duplicate existing entries unexpectedly.
LENGTH LIMITS Confirm fields remain within the constraints defined for each service.
DATE RULES Validate scheduling information against the requirements of the appointment model.
VALID INPUT Verify compliant objects are accepted and remain retrievable.
INVALID INPUT Verify violations produce predictable failure instead of silently corrupting application state.

Testing behavior from both directions.

I created JUnit tests for the individual service classes and their object constraints. The suite included both positive and negative test cases.

POSITIVE TESTING

Valid objects can be created.

Records can be added successfully.

Expected values can be retrieved.

Supported updates behave correctly.

NEGATIVE TESTING

Invalid values are rejected.

Duplicate identifiers are detected.

Null constraints are enforced.

Boundary violations produce expected failures.

This distinction matters because a test suite that only confirms successful behavior says very little about what happens when the application receives bad data.

Verifying that service operations produce retrievable state.

One basic unit-test pattern creates a service, adds a valid domain object, retrieves that object again, and verifies that the expected data was preserved.

CONTACT SERVICE TEST JUNIT
@Test
public void testAddContact() {
    ContactService service = new ContactService();

    Contact contact = new Contact(
        "123",
        "Takeria",
        "5551234567",
        "Galaxy Blvd"
    );

    service.addContact(contact);

    assertEquals(
        "Takeria",
        service.getContact("123").getFirstName()
    );
}

A test like this verifies more than object construction. It checks the interaction between the domain object and the service responsible for storing and retrieving it.

Using coverage as a signal—not as the entire definition of quality.

The test suite exceeded 80% coverage, giving broad automated verification across the backend components.

Coverage helped reveal whether important code paths had been exercised, but the more meaningful goal was ensuring that tests represented application requirements and edge conditions.

High coverage is useful only when the tests exercise behavior that actually matters.

A line of code being executed during a test does not automatically prove that its behavior is correct. This project reinforced the difference between measuring test activity and designing meaningful test cases.

Applying object-oriented boundaries to backend responsibilities.

The services were structured using object-oriented design principles. Each domain had a focused model and service instead of placing all application behavior into one large class.

ENCAPSULATION

Domain data and the rules surrounding it remain grouped together instead of being manipulated arbitrarily throughout the application.

SEPARATION

Contact, Task, and Appointment behavior remain independent.

REUSABILITY

Related behavior is organized into methods that can be reused rather than duplicated.

EXTENSIBILITY

Modular classes provide clearer places to introduce future behavior.

Designing services that are easier to change safely.

Modular service design made the code easier to test and reason about because each component had a limited responsibility.

TIGHTLY MIXED LOGIC

Multiple domains depend on one large class.

Changes affect unrelated behavior.

Tests require more setup.

Failures are harder to isolate.

MODULAR SERVICES

Each domain owns its behavior.

Changes have a clearer scope.

Tests can target one service at a time.

Defects are easier to locate.

This structure also creates a cleaner path for future additions such as persistence, APIs, or a user interface because the core application behavior already exists independently.

Treating testing as part of development rather than a final check.

Unit testing exposed edge cases that were easy to overlook when thinking only about the expected flow of the application.

Null values, duplicated identifiers, length limits, and other constraints forced the implementation to define exactly what valid application state should look like.

Automated testing then created a repeatable way to verify those decisions whenever the implementation changed.

DEFINE REQUIREMENT Determine what valid behavior should be.
IMPLEMENT CONSTRAINT Express the requirement in application code.
WRITE TEST Verify the behavior automatically.
RUN REGRESSION CHECK Confirm later changes have not broken existing behavior.

Learning to build software that can prove its own behavior.

The most important lesson from this project was that backend quality depends on more than implementing the happy path.

Writing unit tests made requirements more concrete. Instead of saying that an identifier should be unique or that a field should reject an invalid value, the test suite provided an executable demonstration of that rule.

The project also reinforced how modularity supports testing. Smaller, focused service classes are easier to instantiate, isolate, test, and reason about than systems where responsibilities are tightly mixed.

Testing changed from something that verifies finished code into a tool for defining what correct software should do.

Extending beyond isolated unit behavior.

This project focused primarily on unit testing because its backend services were deliberately isolated from a user interface or larger production system.

A natural next stage would be to add higher levels of testing as the architecture grows.

UNIT TESTS

Continue validating individual classes and methods in isolation.

INTEGRATION TESTS

Verify that services interact correctly with persistence, external boundaries, or other application components.

SYSTEM TESTING

Validate complete application behavior once a larger application exists around the services.

REGRESSION TESTING

Preserve existing behavior as new requirements and features are introduced.

A tested foundation for reliable backend development.

The final project demonstrated how backend service design and automated testing support each other. The services provided focused application behavior while the JUnit suite verified that those behaviors remained within their defined constraints.

Built backend services in Java.
Separated Contact, Task, and Appointment domains.
Implemented creation, update, retrieval, and deletion behavior.
Applied object-oriented design principles.
Enforced data validation and application constraints.
Tested valid and invalid execution paths.
Used JUnit for repeatable automated verification.
Exceeded 80% test coverage.
Used testing to uncover edge-case defects.
Strengthened maintainability through modular service design.

The project gave me a stronger foundation in writing backend software that is not only functional, but structured so that its behavior can be verified, maintained, and extended with confidence.

BACKEND SERVICES / DESIGN • VALIDATION • TESTING