자동화 테스트는 만들어놓는다고 끝이 아니다. 매일 최신 버전 서버에서 돌아야 하고, 결과를 리포트로 남겨야 하고, 팀이 결과를 바로 알 수 있어야 한다.
이걸 Jenkins 파이프라인 세 단계로 연결했다.
[Jenkins 스케줄]
│
▼
[1단계] 서버 자동 업데이트
NAS 마운트 → 최신 패키지 선택 → 설치 → 서비스 체크
│
▼
[2단계] 테스트 실행 + Allure 전송
npm ci → bddgen → playwright test
→ allure-results 압축 → Allure 서버 전송
│
▼
[3단계] Mattermost 알림
JUnit XML 파싱 + Allure JSON 파싱
→ 성공/실패 요약 → MM 채널 전송
1단계: NAS에서 최신 패키지 자동 설치
매일 빌드 서버에서 새 패키지가 NAS에 올라온다. 이 스크립트가 그 패키지를 자동으로 찾아서 설치한다.
#!/bin/bash
set -x
# 히스토리 초기화
cd /opt/history || exit 1
rm -rf *
sleep 5
# 최신 패키지 선택
LATEST_FILE=$(ls -t /mnt/nas/package/security_allinone/security_allinone_*.BIN 2>/dev/null | head -1)
if [ -z "$LATEST_FILE" ]; then
echo "[ERROR] No package found"
exit 1
fi
DEST_FILE=/opt/tmp/$(basename "$LATEST_FILE")
# 기존 파일 정리 후 복사
rm -f /opt/tmp/security_allinone_* 2>/dev/null
sudo cp "$LATEST_FILE" "$DEST_FILE"
sudo chmod +x "$DEST_FILE"
# 설치 실행 (인수 2 = 자동 설치 모드)
"$DEST_FILE" 2
# 서비스 기동 대기
sleep 60
# 서비스 상태 확인
ps -ef | grep tomcat | grep -v grep && echo "[OK] Tomcat running" || echo "[WARN] Tomcat not detected"
ps -ef | grep httpd | grep -v grep && echo "[OK] HTTPD running" || echo "[WARN] HTTPD not detected"
포인트는 두 가지다.
ls -t ... | head -1: NAS에 쌓인 패키지 중 가장 최신 파일을 자동으로 선택한다. 파일명에 날짜나 버전이 포함되어 있어서 ls -t로 수정 시간 역순 정렬하면 된다.
설치 후 60초 대기: 패키지 설치가 완료돼도 서비스가 완전히 뜨기까지 시간이 걸린다. Tomcat과 HTTPD 기동을 확인하고 넘어간다.
2단계: 테스트 실행 + Allure 서버 전송
#!/bin/bash
TEST_RESULT=0
# 서버 기동 대기 옵션 (필요 시 활성화)
if [ "$STARTING_PING" = "true" ]; then
echo "Waiting for server startup..."
sleep 600
fi
# 의존성 설치
npm ci
npx playwright install --with-deps
# BDD 코드 생성
npx bddgen --verbose
# 테스트 실행 — 실패해도 중단하지 않고 결과값만 저장
npx playwright test --project=product-a --project=product-b --workers=1
TEST_RESULT=$?
여기서 핵심은 TEST_RESULT=$?다. 테스트가 실패해도 스크립트가 중단되지 않는다. Allure 리포트를 만들고 전송하는 과정을 다 마친 뒤, 맨 마지막에 exit $TEST_RESULT로 Jenkins에 빌드 성공/실패를 전달한다. 이걸 빠뜨리면 테스트가 실패했는데 Jenkins 빌드는 성공으로 표시되는 상황이 생긴다.
# Allure 프로젝트 ID 생성 (JOB_NAME 기반)
PROJECT_NAME_RAW=$(basename "$JOB_NAME")
PROJECT_NAME_FINAL=$(echo "$PROJECT_NAME_RAW" | tr '[:upper:]' '[:lower:]' | sed -e 's/_/-/g' -e 's/\./-/g')
export PROJECT_NAME="${PROJECT_NAME_FINAL}-edr"
echo "Final Allure Project ID: $PROJECT_NAME"
# Allure 프로젝트 생성 (없으면 생성, 있으면 패스)
curl -X POST "${ALLURE_SERVER_URL}/projects" \
-H "Content-Type: application/json" \
-d "{\"id\": \"${PROJECT_NAME}\", \"name\": \"${PROJECT_NAME} Report\"}"
# allure-results 압축
rm -f allure-results.zip
(cd allure-results && zip -r "../allure-results.zip" .)
# 개별 프로젝트 + 통합 대시보드 두 군데 전송
curl -X POST "${ALLURE_SERVER_URL}/send-results?project_id=${PROJECT_NAME}" \
-H "Content-Type: multipart/form-data" \
-F "files[]=@allure-results.zip;type=application/zip"
curl -X POST "${ALLURE_SERVER_URL}/send-results?project_id=0-total-dashboard" \
-H "Content-Type: multipart/form-data" \
-F "files[]=@allure-results.zip;type=application/zip"
exit $TEST_RESULT
Allure 결과를 두 군데에 전송하는 게 포인트다. 개별 프로젝트 리포트와 0-total-dashboard라는 통합 대시보드에 동시에 올린다. 팀원들은 각 제품 리포트에서 세부 내용을 보거나, 통합 대시보드에서 전체 현황을 한눈에 볼 수 있다.
프로젝트 ID는 JOB_NAME에서 자동으로 만든다. 대문자를 소문자로, _와 .를 -로 바꾸고 접미사를 붙인다. Jenkins Job 이름이 바뀌어도 규칙대로 따라간다.
3단계: Mattermost 알림 (Python)
테스트가 끝나면 Python 스크립트를 실행해서 결과를 채널에 알린다.
import os, requests, json, urllib3, sys
import xml.etree.ElementTree as ET
import urllib.parse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def get_junit_summary():
xml_path = "./test-results/junit.xml"
if not os.path.exists(xml_path):
return 0, 0
try:
tree = ET.parse(xml_path)
total, failures = 0, 0
for suite in tree.iter('testsuite'):
total += int(suite.attrib.get('tests', 0))
failures += int(suite.attrib.get('failures', 0))
return total, failures
except:
return 0, 0
def send_notification(job_name, build_number, build_url):
allure_results_path = "./allure-results"
allure_report_url = f"{os.environ.get('ALLURE_SERVER_URL')}/projects/{os.environ.get('PROJECT_NAME')}/reports/latest"
total_tc, fail_count = get_junit_summary()
success_count = total_tc - fail_count
# Allure result JSON에서 실패 테스트 이름 추출
failed_tests = {}
if os.path.exists(allure_results_path):
for filename in os.listdir(allure_results_path):
if filename.endswith("-result.json"):
with open(os.path.join(allure_results_path, filename), 'r', encoding='utf-8') as f:
try:
data = json.load(f)
# retry=True인 재시도 케이스는 제외
if data.get('status') == 'failed' and not data.get('retry', False):
failed_tests[data.get('name')] = True
except:
continue
if fail_count > 0:
failures = [
f"{idx}. {name}\n [실패 원인 확인하기]({allure_report_url}?q={urllib.parse.quote(name)})"
for idx, name in enumerate(failed_tests.keys(), 1)
]
message = (
f"**[빌드 결과] {job_name} - #{build_number}**\n\n"
f"테스트 요약: 총 {total_tc}건 중 {success_count}건 성공, {fail_count}건 실패\n\n"
f"**[실패 테스트 목록]**\n" + "\n".join(failures) + "\n\n"
f"--------------------------------------------------\n"
f"[전체 리포트 확인하기]({allure_report_url})\n"
f"[Jenkins 콘솔 보기]({build_url})"
)
else:
message = (
f"**[빌드 결과] {job_name} - #{build_number}**\n\n"
f"테스트 결과: 총 {total_tc}건 모두 성공! ✅\n\n"
f"[전체 리포트 확인하기]({allure_report_url})\n"
f"[Jenkins 콘솔 보기]({build_url})"
)
url = f"{os.environ.get('MM_SERVER_URL')}/api/v4/posts"
payload = {
"channel_id": os.environ.get('MM_CHANNEL_ID'),
"message": message
}
headers = {
"Authorization": f"Bearer {os.environ.get('MM_TOKEN')}",
"Content-Type": "application/json"
}
requests.post(url, headers=headers, data=json.dumps(payload), verify=False)
if __name__ == "__main__":
send_notification(
sys.argv[1] if len(sys.argv) > 1 else "Unknown",
sys.argv[2] if len(sys.argv) > 2 else "0",
sys.argv[3] if len(sys.argv) > 3 else "#"
)
이 스크립트는 두 가지 소스를 조합한다.
JUnit XML: test-results/junit.xml에서 전체 TC 수와 실패 수를 집계한다. Playwright가 junit reporter로 자동 생성해주는 파일이다.
Allure result JSON: allure-results/ 안의 *-result.json 파일을 하나씩 읽어서 status: failed인 케이스의 이름을 꺼낸다. 여기서 retry: true를 체크하는 게 중요하다. Playwright의 retries 설정으로 재시도한 케이스는 JSON에 retry: true로 표시되는데, 이걸 포함하면 같은 테스트가 중복으로 실패 목록에 들어간다.
실패 메시지에는 각 테스트 이름을 URL 인코딩해서 Allure 리포트 링크를 직접 달아준다. 팀원이 채널에서 링크를 누르면 해당 케이스의 실패 원인으로 바로 이동한다.
[빌드 결과] EDR_7.0 - #142
테스트 요약: 총 87건 중 84건 성공, 3건 실패
[실패 테스트 목록]
1. 동적 분석 탐지 결과 검색
[실패 원인 확인하기](https://allure.../index.html?q=...)
2. 정책 생성 후 에이전트 적용 확인
[실패 원인 확인하기](https://allure.../index.html?q=...)
--------------------------------------------------
[전체 리포트 확인하기](https://allure.../index.html)
[Jenkins 콘솔 보기](http://jenkins.../console)
Jenkins에서 실행하는 방법
Jenkinsfile 또는 Job 설정에서 순서대로 실행한다.
// Jenkinsfile 예시
pipeline {
triggers {
cron('0 22 * * *') // 매일 밤 10시
}
stages {
stage('Update Server') {
steps {
sh './scripts/update_server.sh'
}
}
stage('Run Tests') {
steps {
sh './scripts/run_tests.sh'
}
}
}
post {
always {
sh """
python3 ./scripts/notify_mm.py \
"${env.JOB_NAME}" \
"${env.BUILD_NUMBER}" \
"${env.BUILD_URL}"
"""
}
}
}
post { always { ... } }에 MM 알림을 넣는 게 핵심이다. 테스트가 성공하든 실패하든 항상 실행되고, sys.argv로 JOB_NAME, BUILD_NUMBER, BUILD_URL을 넘긴다.
TOKEN, CHANNEL_ID 같은 민감한 값은 코드에 직접 넣지 않고 Jenkins Credentials 또는 환경 변수로 관리한다.
전체 파이프라인이 완성되면
매일 밤 Jenkins가 스케줄로 실행된다. 서버에 최신 패키지가 설치되고, 테스트가 돌아가고, Allure 리포트가 생성되고, 결과 요약이 팀 채널에 올라온다. 아침에 출근하면 채널에 어젯밤 결과가 와 있다.
실패 케이스가 있으면 링크 하나로 바로 들어가서 스크린샷과 Trace를 확인할 수 있다. "어제 뭐가 터졌어요?" 물어볼 필요가 없어진다.