CodeBuild Test Report: Build Không Chỉ Chạy Mà Phải Đúng
Build ở các bài trước "thành công", nhưng thành công đó chỉ nói các lệnh chạy không trả lỗi — nó không nói code đúng. Đó là khác biệt giữa "build chạy được" và "phần mềm hoạt động". Chữ "CI" trong CI/CD bao gồm cả việc chạy test mỗi lần tích hợp code. Bài này cho CodeBuild chạy test thật và xuất test report — báo cáo gom kết quả test để bạn thấy ngay bao nhiêu test đạt, bao nhiêu trượt, trượt ở đâu. Bài này cũng khép Part II.
Mục tiêu
Cho CodeBuild chạy bộ test và gom kết quả thành report xem được, hiểu report group là gì, và biết cách để test trượt chặn được build.
Thêm test vào app
App demo là trang tĩnh, nhưng vẫn có những điều đáng kiểm: file tồn tại, nội dung đúng, có dấu phiên bản. Viết bộ test bằng pytest, đặt ở tests/test_app.py:
import os, re
def test_index_exists():
assert os.path.isfile("index.html"), "index.html must exist"
def test_index_has_heading():
html = open("index.html", encoding="utf-8").read()
assert "Hello from the awscicd demo app" in html
def test_version_marker():
html = open("index.html", encoding="utf-8").read()
assert re.search(r"v\d+", html), "expected a version marker like v2"
Khai test và report trong buildspec
Hai thay đổi trong buildspec.yml: chạy test ở phase build (xuất ra file JUnit XML), và thêm khối reports chỉ cho CodeBuild biết gom file đó thành report:
phases:
install:
commands:
- pip install -q pytest
build:
commands:
- echo "Running tests"
- pytest tests/ --junitxml=report.xml -v
reports:
demo-tests:
files:
- "report.xml"
file-format: JUNITXML
pytest --junitxml=report.xml chạy test và ghi kết quả ra report.xml theo định dạng JUnit — định dạng chuẩn mà gần như mọi framework test (JUnit, pytest, Jest, Go test...) xuất được. Khối reports với file-format: JUNITXML bảo CodeBuild đọc file đó và dựng thành report. CodeBuild cũng hiểu các format khác (NUNITXML, TESTNGXML, CUCUMBERJSON, và code coverage).
Cấp quyền report cho role
Dựng report cần thêm quyền cho service role — tạo và cập nhật report group, ghi từng test case:
{
"Effect": "Allow",
"Action": ["codebuild:CreateReportGroup","codebuild:CreateReport",
"codebuild:UpdateReport","codebuild:BatchPutTestCases","codebuild:BatchPutCodeCoverages"],
"Resource": "arn:aws:codebuild:*:*:report-group/awscicd-demo-build-*"
}
Thiếu quyền này, build vẫn chạy nhưng phần tạo report sẽ lỗi. Resource giới hạn đúng vào report group của project (<project>-*).
Chạy và xem kết quả
Sau khi push và chạy build, mỗi build gắn với một report. Lấy ARN report từ build rồi xem tóm tắt:
$ RARN=$(aws codebuild batch-get-builds --ids "$BID" --query 'builds[0].reportArns[0]' --output text)
$ aws codebuild batch-get-reports --report-arns "$RARN" \
--query 'reports[0].[status,testSummary.total,testSummary.statusCounts]'
[
"SUCCEEDED",
3,
{ "ERROR": 0, "FAILED": 0, "SKIPPED": 0, "SUCCEEDED": 3, "UNKNOWN": 0 }
]
Ba test, đạt cả ba. Xem từng case:
$ aws codebuild describe-test-cases --report-arn "$RARN" \
--query 'testCases[].[name,status,durationInNanoSeconds]' --output text
test_index_exists SUCCEEDED 0
test_version_marker SUCCEEDED 1000000
test_index_has_heading SUCCEEDED 1000000
Đây là giá trị của report: không phải lội qua hàng nghìn dòng log để tìm test nào trượt, mà thấy ngay danh sách test với trạng thái và thời gian từng cái. Khi một test trượt, nó hiện FAILED kèm tên, dẫn thẳng tới chỗ cần sửa.
buildspec: pytest --junitxml=report.xml
│
▼ CodeBuild đọc report.xml (JUnit)
┌──────────── Report group: awscicd-demo-build-demo-tests ───────────┐
│ report (lần build này): 3 total, 3 SUCCEEDED │
│ ├─ test_index_exists SUCCEEDED │
│ ├─ test_index_has_heading SUCCEEDED │
│ └─ test_version_marker SUCCEEDED │
│ ... mỗi build thêm một report → xu hướng đạt/trượt theo thời gian │
└─────────────────────────────────────────────────────────────────────┘
Report group: theo dõi theo thời gian
Để ý tên report group: awscicd-demo-build-demo-tests (tên project cộng tên report bạn khai). Report group gom mọi report cùng loại qua các lần build, nên bạn xem được xu hướng — tỉ lệ đạt/trượt thay đổi ra sao theo thời gian, test nào hay trượt. Mỗi lần build tạo một report mới trong group đó.
Để test trượt chặn build
Một điểm dễ sai mà ảnh hưởng lớn. Nếu bạn viết lệnh test kiểu pytest ... || true, build sẽ luôn "thành công" kể cả khi test trượt — report vẫn ghi nhận trượt, nhưng build xanh, và code hỏng đi tiếp tới deploy. Đó là phá vỡ mục đích của CI. Cách đúng là để lệnh test trả mã lỗi tự nhiên: pytest tests/ --junitxml=report.xml (không || true). Khi test trượt, pytest trả mã khác 0, phase build fail, build fail, và pipeline (Part V) sẽ dừng ngay — code hỏng không lọt được tới production. Report cho bạn thấy trượt ở đâu; mã thoát khác 0 chặn nó đi tiếp.
🧹 Dọn dẹp
Report group được tạo tự động; xóa khi dọn series:
$ aws codebuild delete-report-group \
--arn arn:aws:codebuild:ap-southeast-1:111122223333:report-group/awscicd-demo-build-demo-tests \
--delete-reports
Report và report group không tính phí lưu trữ đáng kể, giữ qua các bài không sao.
Tổng kết
Test report biến "build chạy không lỗi" thành "biết chính xác test nào đạt/trượt". Cho CodeBuild chạy test xuất JUnit XML (pytest --junitxml) rồi khai khối reports với file-format: JUNITXML; cấp role quyền tạo report group và ghi test case. Kết quả xem được qua batch-get-reports (tổng/đạt/trượt) và describe-test-cases (từng case), gom trong một report group theo dõi xu hướng. Quan trọng: đừng nuốt lỗi test bằng || true — để test trượt làm build trượt thì CI mới thực sự chặn được code hỏng.
Part II khép lại: ta đã build, truyền secret an toàn, và chạy test có report. Part III là một dịch vụ phụ trợ ngắn nhưng hữu ích — CodeArtifact, kho package nội bộ — để build kéo và publish thư viện riêng thay vì luôn ra Internet.