Back in the "pre-CI/CD" days developers ran tests by hand, copied files between servers, and wondered why the app that "worked on my local machine" threw errors in production. Those were rough times. Luckily, programmers hate boring repetitive work. So after thinking and applying some tech, they came up with build and test automation. Now we and other devs can spend our time on more important stuff instead of hunting down a missing ; or a stray space.
Build and test automation are foundational steps toward CI/CD. But why do we need all that? Let's break it down.
Build automation
Build is the process of turning your code into a runnable program. For Java apps that usually means turning .java source files into .class files and then into a final .jar or .war artifact.
Problems with manual builds
If your project is tiny, manual builds aren't a huge deal. Well, most of the time. But imagine a project with hundreds of classes, dozens of modules, and a bunch of dependencies. Are you going to run those steps every single time? That's a waste of time. And bugs... they'll be waiting around every corner.
Why automate builds?
Build automation solves a lot of problems:
- Saves time. Instead of running commands manually, you set the process up once and the system handles it for you.
- Repeatability. Every build happens the same way, minimizing human error.
- Dependency handling. Build tools automatically find, download, and wire up the libraries you need.
- Integration with CI/CD pipelines. Systems like Jenkins, GitLab CI, or CircleCI can trigger builds automatically after each code change.
Tools for build automation
Java developers have a solid toolkit. You've probably heard of these:
- Maven — the corporate favorite that asks you to follow a strict structure, but automates everything from compilation to docs generation.
- Gradle — a modern alternative to Maven that gives you more flexibility and plays nicely with Kotlin.
- Ant — an old-school tool that's still around, but usually requires more manual configuration.
Example pom.xml (Maven) that shows build automation:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>awesome-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
This file automatically builds the project, downloads dependencies, and compiles the code.
Test automation
Testing is like a final check for software—you verify everything works as intended before users see it.
In Java we usually talk about three main types of tests:
- Unit tests — test individual classes or methods.
- Integration tests — check how different modules work together.
- End-to-end (E2E) tests — test the whole app, including interaction with external systems (e.g., the database).
Why automate testing?
Because manual testing is a pain and it's slow. An automated test can do in a minute what a manual tester might take hours to check. Plus each automated test runs the same way every time, removing the human factor. And tests run often (for example, on every commit), so bugs get caught and fixed quickly.
Tools for test automation
In the Java ecosystem there are plenty of tools:
- JUnit — the go-to framework for writing and running tests.
- Mockito — a library for creating mocks and stubs.
- Spring Boot Test — a set of tools integrated into Spring Boot for testing services and controllers.
Example of a simple unit test using JUnit:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 should equal 5");
}
}
Integrating automated steps into a CI/CD pipeline
Build and test automation fit perfectly into CI/CD:
- Every commit triggers the pipeline. For example, a developer makes changes, pushes them to the repo, and the pipeline starts automatically.
- Build runs automatically. Tools like Jenkins run the build process and produce artifacts.
- Tests run after the build. If something fails, the pipeline stops and the developer gets notified.
Example .gitlab-ci.yml file:
stages:
- build
- test
build:
stage: build
script:
- mvn clean package
test:
stage: test
script:
- mvn test
Now, on every commit GitLab CI will automatically run the Maven build and the tests.
Final example: from code to production
What this looks like in practice:
- The developer writes new code and pushes it to the repo.
- CI/CD runs a pipeline that:
- Builds the project with Maven.
- Runs tests with JUnit.
- Creates an artifact (for example, a
.jarfile). - Deploys the artifact to a staging environment.
If everything goes well, the changes make it to production.
Let's move on to the next lecture, where we'll start digging into configuring CI/CD tools so this all runs smoothly.
GO TO FULL VERSION