콘텐츠로 이동

테스트 가이드

BomLens의 테스트 구조와 실행 방법, 테스트 작성 방법, 디버깅 절차를 설명합니다.

테스트 구조

tests/
├── test-scan.sh              # 메인 통합 테스트 — 언어/입력 유형별 15개 블록이
│                              # 한 파일에 이어져 있다(Node.js, Python, Java Maven,
│                              # Ruby, PHP, Rust, Docker 이미지, 바이너리 파일,
│                              # rootfs 디렉터리, ZIP 아카이브 등)
├── test-e2e.sh                # 그 밖의 엔드투엔드 테스트(CLI, 예제, 웹 UI, AI-BOM 등)
├── test-examples-e2e.sh
├── test-web-e2e.sh
├── test-web-ui.sh
├── test-android-scope.sh      # 관심사별 독립 회귀 테스트 스크립트 —
├── test-aibom.sh              # 공용 라이브러리를 쓰지 않고 각자 자기만의
├── test-firmware-unpack.sh    # pass()/fail() 헬퍼를 정의한다
├── test-real-corpus.sh        # 커밋을 고정한 공개 저장소를 스캔한다(아래 참고)
├── ...
├── lib/
│   ├── snapshot-normalize.jq  # 스냅샷 테스트가 공유하는 jq 헬퍼
│   └── corpus-compare.sh      # 코퍼스 실행을 이전 실행과 고정 하한선에 견주어 판정한다
├── corpus/                    # 저장소 목록과 라이선스 포함률 하한선
├── fixtures/                  # 독립 스크립트들이 쓰는 입력 픽스처
└── snapshots/                 # test-snapshot.sh가 비교하는 기대 출력 스냅샷

tests/helpers/나 tests/cases/ 디렉터리는 없습니다. test-scan.sh는 파일 상단에 헬퍼 함수 몇 개(run_scan_with_logs, find_bom_file, assert_bom_sane, assert_spdx_sane, assert_root_has_direct_deps, show_failure_log)를 직접 정의해 두고, 같은 파일 안에서 15개 테스트 블록을 차례로 실행합니다. 각 블록은 몇 줄짜리 코드로, 테스트 작업 공간에 픽스처 프로젝트를 만들고 스캔을 실행한 뒤 jq나 위 헬퍼 함수로 결과를 확인합니다.

tests/ 아래의 독립 스크립트(test-android-scope.sh, test-aibom.sh 등)는 이 장치를 공유하지 않고, 각자 필요한 만큼의 작은 로컬 헬퍼를 따로 정의합니다. 새 스크립트를 쓰기 전에 기존 스크립트 하나를 먼저 읽어 보세요.

테스트 실행

전체 테스트 실행

./tests/test-scan.sh

성공 시 출력 예시(축약 — 실제로는 [TEST]/[✓] 쌍이 15번 출력됩니다):

==========================================
 SBOM Generator - Integration Test
 Version: 1.0.0
==========================================

[INFO] Checking prerequisites...
[✓] Docker check passed
[✓] Scan script check passed

[INFO] Starting tests...

[TEST] Test 1/15: Node.js project (npm)
[✓] Node.js project (2 components)
...
[TEST] Test 15/15: --timestamp run folder
[✓] --timestamp run folder created (./Stamped_1.0.0_.../)

==========================================
 Test Summary
==========================================

Total tests: 15
Passed: 15
Failed: 0
Success rate: 100.0%

전체를 돌리지 않고 케이스 하나만 확인하기

test-scan.sh에는 15개 블록 중 하나만 골라 실행하는 옵션이 없습니다. 특정 언어나 입력 유형 하나만 확인하려면 원하는 픽스처나 예제를 대상으로 스캔을 직접 실행하세요.

./scripts/scan-sbom.sh --project Test --version 1.0.0 --target examples/nodejs --generate-only

반면 독립 스크립트들은 이미 각자 따로 실행할 수 있습니다.

./tests/test-android-scope.sh
./tests/test-aibom.sh

실제 저장소 코퍼스

번들 예제는 규모가 작고 잠금 파일이 없습니다. tests/test-real-corpus.sh는 공개 저장소 17개(tests/corpus/real-corpus.tsv, 생태계마다 2개 이상, 각각 커밋을 고정)를 스캐너로 스캔하고, 저장소마다 컴포넌트 수, purl과 라이선스가 있는 컴포넌트의 비율, 종료 코드, 소요 시간, 스캔이 체크아웃을 바꿨는지를 보고합니다. 저장소 자신이 무시하도록 설정한 빌드 산출물도 변경으로 셉니다.

저장소마다 결과는 다음 상태 가운데 하나입니다.

상태 뜻
ok 소프트웨어를 찾았다
empty 스캔이 소프트웨어를 하나도 식별하지 못했다(--fail-on empty-result)
failed 스캔이 실패했거나 읽을 수 있는 SBOM을 만들지 못했다
timeout SCAN_TIMEOUT 안에 끝나지 않았다(GNU timeout이나 gtimeout이 설치된 곳에서만 적용)
undetermined 품질 결과가 아니다. 저장소를 가져오지 못했거나 스캔이 판정하지 못했다(종료 코드 5)

empty, failed, timeout, 소스 트리 변경이 있거나 라이선스 포함률이 떨어지면 실행이 실패합니다. 측정된 것이 하나도 없을 때도 마찬가지입니다. 포함률은 생태계마다 ok였던 저장소의 평균을 두 가지로 판정합니다. 워크플로에서는 코퍼스 전체를 다룬 main의 가장 최근 성공 실행과 비교하며, 저장소가 2개 이상 비교되었을 때 10퍼센트포인트 이상 떨어지면 실패로 봅니다. 로컬에서는 BASELINE_FILE을 넘길 때만 이 비교가 일어납니다. 또 tests/corpus/license-floor.tsv의 하한선과도 비교하는데, 이 판정은 이전 실행이 없어도 적용됩니다. 하한선을 낮추려면 리뷰를 거쳐야 하므로 서서히 나빠지는 추세가 눈에 보입니다.

Docker를 켠 상태에서 로컬로 실행합니다. 클론과 결과는 ~/.cache/bomlens-real-corpus에 쌓입니다. WORK_DIR로 다른 경로를 지정한다면, Colima에서 Docker를 쓸 때 홈 디렉터리 아래에 두어야 합니다.

ONLY="cobra logrus" ./tests/test-real-corpus.sh   # 일부 저장소만
ECOSYSTEMS="go rust" ./tests/test-real-corpus.sh  # 일부 생태계만

--fail-on보다 오래된 스캐너 이미지를 쓴다면 FAIL_ON_ARGS=""로 설정합니다. 이 경우 빈 결과는 SBOM에서 직접 판별합니다. SCAN_TIMEOUT(저장소당 초 단위, 기본 900이며 워크플로는 1200을 씁니다), WORK_DIR, CORPUS_FILE, SBOM_SCANNER_IMAGE, BASELINE_FILE, DROP_POINTS는 스크립트 머리말에 설명되어 있습니다.

Real-repository corpus 워크플로(real-corpus.yml)는 매일 밤과 수동 요청 때 실행합니다. 이 커밋으로 스캐너 이미지를 빌드하는 러너에서 돌며 20분에서 25분쯤 걸립니다. 풀 리퀘스트에서는 실행하지 않고 필수 검사도 아닙니다. 결과 표는 실행 요약에 나오고, results.tsv는 실행마다 real-corpus-results 아티팩트로 남습니다. 다음 실행은 그중 가장 최근의 성공한 완전한 결과를 기준선으로 삼습니다. 직접 시작하려면 gh workflow run real-corpus.yml을 실행합니다(ecosystems 입력으로 범위를 좁힐 수 있으며, 범위를 좁힌 실행은 기준선이 되지 않습니다).

실행 모드

환경 변수 값 효과
(없음) — [TEST]/[✓]/[✗] 통과·실패 줄만 출력하고, 단계별 출력은 로그 파일에 저장
VERBOSE true 스캔 출력에서 걸러낸 주요 진행 줄도 함께 출력(INFO/WARN/ERROR, Analyzing, Downloading, components, cdxgen, syft)
DEBUG_MODE true Docker·cdxgen·syft 전체 출력을 실시간으로 스트리밍하고, 종료 시 작업 공간을 정리하지 않고 보존
# Verbose 모드
VERBOSE=true ./tests/test-scan.sh

# Debug 모드(문제 분석 시. tests/test-workspace/도 보존됩니다)
DEBUG_MODE=true ./tests/test-scan.sh

test-android-scope.sh 등 일부 독립 스크립트도 VERBOSE를 지원합니다. 스크립트가 읽는 환경 변수는 각 스크립트 상단의 헤더 주석에서 확인하세요.

테스트 작성

언어마다 따로 만들 파일은 없습니다. 새 언어나 생태계에 대한 테스트를 추가하려면 기존 테스트를 확장합니다.

  1. tests/test-scan.sh를 열어 기존 블록의 형식을 따라 새 블록을 추가합니다(Java Maven 블록인 Test 3이 참고하기 좋은 예입니다). 테스트 작업 공간에 픽스처 프로젝트를 만들고, run_scan_with_logs로 스캔을 실행하고, find_bom_file로 생성된 BOM을 찾은 뒤, jq 또는 assert_bom_sane / assert_spdx_sane / assert_root_has_direct_deps로 검증합니다. 주변 블록과 같은 방식으로 성공·실패에 따라 PASSED나 FAILED를 늘리고, 실패 시 show_failure_log를 호출합니다.
  2. 각 블록의 print_test 레이블은 "Test N/15"처럼 번호가 손으로 적혀 있습니다. 블록을 추가한 뒤에는 레이블 번호를 다시 매기고, 스크립트 상단의 --help 문구와 요약 배너의 총 개수도 함께 갱신합니다.
  3. 소스 스캔 패턴에 맞지 않는 경우 — 이미지나 rootfs에 특화된 사례, 재현성 확인, AI-BOM 동작, 느리고 네트워크에 의존하는 회귀 테스트 등 — 라면 대신 tests/test-<name>.sh 스크립트를 새로 만듭니다. test-android-scope.sh 같은 기존 스크립트를 따라 로컬 pass()/fail() 헬퍼를 정의하고, 필요한 도구나 이미지가 없으면 일찍 건너뛰게 하고, 해당 CI 워크플로(퍼 PR 경로는 ci.yml, 느리거나 네트워크에 의존하는 검사는 nightly.yml)에 연결합니다.

새 언어를 처음부터 끝까지 추가하는 전체 절차(감지, build-prep, 예제 프로젝트, 갱신할 문서)는 패키지 매니저 추가 가이드를 참고하세요.

헬퍼 함수 레퍼런스

아래는 tests/test-scan.sh 상단에 정의되어 그 파일 안의 테스트 블록이 쓰는 함수입니다. 다른 스크립트가 함께 쓰는 공용 라이브러리는 아닙니다.

함수 검증 내용
assert_bom_sane <파일> <프로젝트> 파일이 CycloneDX 형식이고, metadata.component.name이 입력한 프로젝트 이름과 같고, components가 배열인지
assert_spdx_sane <파일> 유효한 SPDX 2.x 문서인지(spdxVersion이 SPDX-로 시작, SPDXID가 SPDXRef-DOCUMENT, packages가 배열)
assert_root_has_direct_deps <파일> 루트 컴포넌트의 dependencies 항목에 비어 있지 않은 dependsOn이 있는지
find_bom_file <프로젝트> <버전> 현재의 실행별 하위 폴더 구조와 예전의 평면 구조 양쪽에서 생성된 BOM을 찾는다

이 밖의 검증은 대부분 생성된 BOM에 대한 평범한 jq -e '...' 식입니다. 별도의 단언 라이브러리는 없습니다. 독립 스크립트는 필요할 때마다 자기만의 작은 헬퍼를 정의합니다(test-android-scope.sh의 count(), has_direct() 등).

테스트 작성 원칙

독립성: 각 테스트는 다른 테스트에 의존하지 않아야 합니다. 테스트 순서가 달라져도 결과가 동일해야 합니다.

정리: test-scan.sh는 이미 trap cleanup EXIT 하나로 종료 시 작업 공간(로그 제외)을 정리합니다. 이 파일 안에 블록을 추가할 때는 따로 정리할 필요 없이 작업이 끝나면 $TEST_DIR로 돌아가기만 하면 됩니다. 독립 스크립트는 자기 작업 공간을 스스로 정리해야 합니다.

명확한 이름: print_test/print_success/print_error에 넘기는 문자열은 어떤 언어인지가 아니라 그 블록이 무엇을 검증하는지를 말해야 합니다.

최소 단언: 그 변경이 지키려는 것만 검증하고, 그 이상은 넣지 않습니다.

로깅 및 디버깅

생성된 SBOM 직접 검사

# 컴포넌트 수 확인
jq '.components | length' NodeExample_1.0.0_bom.json

# 모든 PURL 목록
jq '[.components[].purl]' NodeExample_1.0.0_bom.json

# 라이선스 목록
jq '[.components[].licenses[]?.license.id] | unique' NodeExample_1.0.0_bom.json

실패 하나 디버깅하기

# tests/test-workspace/를 보존하므로 실행 후 픽스처와 BOM을 확인할 수 있다
DEBUG_MODE=true ./tests/test-scan.sh

# 또는 독립 스크립트를 직접 디버깅
VERBOSE=true ./tests/test-android-scope.sh

CI 통합

GitHub Actions

ci.yml은 메인 테스트를 실행하고, 실패하면 보존된 로그를 업로드합니다.

- name: Run integration tests
  run: |
    VERBOSE=true ./tests/test-scan.sh

- name: Upload test logs on failure
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: test-logs
    path: tests/test-workspace/failed-tests-logs/

독립 스크립트들은 test-scan.sh에서 호출되는 대신 각자 별도의 단계로 연결되어 있습니다. 예를 들어 bash tests/test-aibom.sh는 ci.yml에서 실행되고, 느리고 네트워크에 의존하는 test-android-scope.sh는 nightly.yml에서 실행됩니다. 실제 저장소 코퍼스는 별도의 예약 워크플로 real-corpus.yml이 맡습니다.

테스트 실패 시 대응 절차

  1. DEBUG_MODE=true 로 재실행하여 상세 로그를 확인합니다.
  2. 실패한 언어의 예제 디렉터리에서 scan-sbom.sh를 직접 실행합니다.
  3. Docker 이미지를 최신 버전으로 업데이트합니다: docker pull ghcr.io/sktelecom/bomlens:latest
  4. 해결되지 않으면 GitHub Issues에 환경 정보와 로그를 첨부해 제보해 주세요.

관련 문서: 기여 가이드 | 아키텍처 | 패키지 매니저 추가