Hướng dẫn Setup Jenkins cho hệ thống Monolith - Bài 11: Tích hợp Unit Test vào Jenkins Pipeline cho hệ thống Monolith
Sau khi đã đóng gói thành công mã nguồn ở Giai đoạn 1, chúng ta bước sang Giai đoạn 2: Tự động hóa Kiểm thử và Phân tích Mã nguồn. Một bản build chỉ thực sự có giá trị khi nó chạy đúng logic nghiệp vụ. Với hệ thống Monolith, chỉ cần một sửa đổi nhỏ ở hàm dùng chung (Helper/Service) cũng có thể làm sập một module ở tận đầu bên kia hệ thống. Việc tích hợp Unit Test vào Jenkins Pipeline chính là chốt chặn đầu tiên để ngăn chặn thảm họa này.
1. Đặc thù khi chạy Unit Test cho hệ thống Monolith trên CI/CD
Khác với Microservices có bộ test nhỏ gọn và độc lập, Unit Test trong dự án Monolith thường mang hai thách thức lớn khi đưa lên Jenkins:
-
Số lượng test case khổng lồ: Một dự án Monolith lâu năm có thể chứa từ vài trăm đến hàng nghìn bài test. Nếu không tối ưu, bước chạy test sẽ làm nghẽn toàn bộ Pipeline.
-
Sự phụ thuộc ngầm (Implicit Dependencies): Nhiều dự án Monolith viết "Unit Test" nhưng thực chất lại kết nối thẳng vào Database (In-memory SQLite, MySQL test) hoặc Redis. Do đó, môi trường trên Jenkins Agent phải được cô lập hoàn toàn để các luồng build chạy song song không ghi đè dữ liệu test của nhau.
2. Nguyên tắc "Fail-Fast" trong Jenkins Pipeline
Trong Jenkins, khi một câu lệnh chạy trong khối sh trả về mã lỗi khác 0 (Exit code != 0), Jenkins sẽ ngay lập tức đánh dấu Stage đó là FAILED và dừng toàn bộ các bước tiếp theo (như Package hay Deploy).
Quy trình chuẩn cho hệ thống Monolith sẽ được điều chỉnh lại như sau:
- Checkout Code -> 2. Install Dependencies -> 3. Run Unit Tests (Chốt chặn) -> 4. Build & Package Artifact.
Nếu bước 3 có bất kỳ test case nào đỏ, bước 4 sẽ không bao giờ được thực thi.
3. Cấu hình câu lệnh chạy Unit Test cho các nền tảng Monolith phổ biến
Để Jenkins không chỉ chạy test mà còn hiểu được kết quả (bao nhiêu test pass, bao nhiêu fail, fail ở dòng code nào), chúng ta cần xuất kết quả test ra định dạng chuẩn JUnit XML.
A. Đối với PHP / Laravel Monolith (PHPUnit / Pest)
Mặc định Laravel sử dụng PHPUnit. Khi chạy trên Jenkins, bạn nên truyền cờ --log-junit để xuất file báo cáo XML, kết hợp dùng SQLite in-memory để tăng tốc độ tối đa:
Bash
# Chạy test và xuất báo cáo ra thư mục reports/junit.xml
./vendor/bin/phpunit --log-junit reports/junit.xml --colors=never
(Lưu ý: Thêm --colors=never nếu bạn chưa cài plugin AnsiColor để tránh các ký tự mã màu làm rác file log).
B. Đối với Golang Monolith
Golang hỗ trợ chạy test đa luồng cực nhanh với cờ -race (phát hiện race condition trong hệ thống đồng thời cao). Để xuất ra chuẩn JUnit XML cho Jenkins đọc, công cụ phổ biến nhất là kết hợp với gói go-junit-report:
Bash
# Chạy toàn bộ test trong project và chuyển đổi sang định dạng XML
go test -v -race ./... 2>&1 | go-junit-report -set-exit-code > reports/junit.xml
C. Đối với Node.js / NestJS / Express Monolith (Jest)
Sử dụng thư viện jest-junit để xuất báo cáo và chạy với cờ --ci (giúp Jest không tự động cập nhật snapshot mà báo lỗi ngay nếu lệch):
Bash
JEST_JUNIT_OUTPUT_DIR="./reports" JEST_JUNIT_OUTPUT_NAME="junit.xml" npm test -- --ci --reporters=default --reporters=jest-junit
D. Đối với Java Spring Boot Monolith (Maven / Gradle)
Maven (maven-surefire-plugin) và Gradle mặc định đã tự động sinh ra các file báo cáo XML tại thư mục target/surefire-reports/*.xml mà không cần cấu hình thêm:
Bash
mvn test
4. Tích hợp Stage Unit Test hoàn chỉnh vào Jenkinsfile
Hãy nâng cấp tệp Jenkinsfile từ Bài 10 bằng cách chèn thêm Stage Run Unit Tests trước bước đóng gói Artifact:
Groovy
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
timestamps()
}
environment {
APP_NAME = "monolith-core-system"
BUILD_VERSION = "v1.0.${env.BUILD_NUMBER}"
}
stages {
stage('1. Checkout Source Code') {
steps {
checkout scm
}
}
stage('2. Install Dependencies') {
steps {
echo "=== Cài đặt toàn bộ thư viện (bao gồm cả dev-dependencies để phục vụ Test) ==="
script {
// Lưu ý: Ở bước này KHÔNG dùng cờ --no-dev vì cần thư viện PHPUnit/Jest để chạy test
sh 'composer install --no-interaction --prefer-dist'
}
}
}
stage('3. Run Unit Tests') {
environment {
// Ghi đè biến môi trường dành riêng cho việc chạy Test cô lập
APP_ENV = "testing"
DB_CONNECTION = "sqlite"
DB_DATABASE = ":memory:"
CACHE_DRIVER = "array"
QUEUE_CONNECTION = "sync"
}
steps {
echo "=== Bắt đầu chạy Unit Test cho hệ thống Monolith ==="
sh '''
mkdir -p reports
./vendor/bin/phpunit --log-junit reports/junit.xml
'''
}
}
stage('4. Clean & Package Artifact') {
steps {
echo "=== Test đã Pass 100%! Tiến hành dọn dẹp thư viện dev và đóng gói ==="
sh '''
# Loại bỏ các thư viện dev (như PHPUnit, Faker) để gói cài đặt nhẹ và bảo mật hơn
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
# Đóng gói mã nguồn (loại bỏ thư mục tests và reports)
tar --exclude='.git' --exclude='tests' --exclude='reports' -czf ${APP_NAME}-${BUILD_VERSION}.tar.gz .
'''
}
}
}
post {
success {
archiveArtifacts artifacts: "${APP_NAME}-${BUILD_VERSION}.tar.gz", fingerprint: true
}
failure {
echo "=== CẢNH BÁO: Unit Test thất bại hoặc lỗi biên dịch! Đã chặn quá trình đóng gói ==="
}
always {
// Dọn dẹp sau khi chạy
sh 'rm -f .env'
}
}
}
5. Điểm tinh tế trong kỹ thuật tách bạch dev-dependencies cho Monolith
Quan sát kỹ Jenkinsfile ở trên, bạn sẽ thấy một kỹ thuật thực chiến rất quan trọng đối với các ngôn ngữ như PHP hay Node.js:
-
Ở Stage 2 & 3: Chúng ta cài đặt đầy đủ các gói thư viện dành cho môi trường phát triển (
dev-dependenciesnhưphpunit,mockery,jest) để có công cụ thực thi bài test. -
Ở Stage 4 (Sau khi Test đã Pass): Trước khi nén thành gói Artifact đưa lên Production, chúng ta chạy lại lệnh tối ưu kèm cờ
--no-dev(hoặcnpm prune --production) và loại bỏ thư mụctestsra khỏi file nén.tar.gz. Điều này giúp gói Monolith trên Production giảm được hàng chục MB dung lượng thừa và triệt tiêu nguy cơ hacker lợi dụng các thư viện debug/test để tấn công hệ thống.
All Rights Reserved