diff --git a/.github/workflows/gradle-test.yaml b/.github/workflows/gradle-test.yaml new file mode 100644 index 000000000..1097f45d5 --- /dev/null +++ b/.github/workflows/gradle-test.yaml @@ -0,0 +1,71 @@ +name: Gradle CI + +on: + pull_request: + branches: + - master + - develop + +jobs: + test: + runs-on: ubuntu-latest + + steps: + # 1. 코드 체크아웃 + - name: Checkout code + uses: actions/checkout@v4 + + # 2. Java 환경 설정 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + # 3. Gradle 캐시 활성화 + - name: Cache Gradle dependencies + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle + + # 4. Gradle 테스트 실행 + - name: Run tests + run: ./gradlew test + continue-on-error: true + + # 5. 테스트 결과 파싱 및 요약 생성 + - name: Generate test summary + if: always() + run: | + echo "Parsing test results..." + mkdir -p test-results-summary + python3 scripts/parse_test_results.py build/test-results/test/ > test-results-summary/summary.md + + # 6. 테스트 리포트 아티팩트 업로드 + - name: Upload Test Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-report + path: build/reports/tests/test # 테스트 리포트 디렉터리 경로 + + # 7. PR에 코멘트 작성 + - name: Comment on PR + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + script: | + const fs = require('fs'); + const summary = fs.readFileSync('test-results-summary/summary.md', 'utf8'); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: summary + }); diff --git a/scripts/parse_test_results.py b/scripts/parse_test_results.py new file mode 100644 index 000000000..4c23208bd --- /dev/null +++ b/scripts/parse_test_results.py @@ -0,0 +1,95 @@ +# scripts/parse_test_results.py + +import sys +import os +import xml.etree.ElementTree as ET + +# 최대 표시할 실패한 테스트 수 +MAX_FAILED_TESTS = 10 +# 에러 메시지 최대 길이 +MAX_ERROR_MESSAGE_LENGTH = 100 + +def parse_test_results(test_results_dir): + total_tests = 0 + total_failures = 0 + total_errors = 0 + total_skipped = 0 + failed_tests = [] + + for root_dir, dirs, files in os.walk(test_results_dir): + for file in files: + if file.endswith('.xml'): + tree = ET.parse(os.path.join(root_dir, file)) + root_element = tree.getroot() + + total_tests += int(root_element.attrib.get('tests', 0)) + total_failures += int(root_element.attrib.get('failures', 0)) + total_errors += int(root_element.attrib.get('errors', 0)) + total_skipped += int(root_element.attrib.get('skipped', 0)) + + for testcase in root_element.findall('testcase'): + # 실패한 테스트 케이스 파싱 + failures = testcase.findall('failure') + testcase.findall('error') + for failure in failures: + message = failure.attrib.get('message', '').strip() + if len(message) > MAX_ERROR_MESSAGE_LENGTH: + message = message[:MAX_ERROR_MESSAGE_LENGTH] + '...' + failed_tests.append({ + 'classname': testcase.attrib.get('classname', ''), + 'name': testcase.attrib.get('name', ''), + 'message': message + }) + + passed_tests = total_tests - total_failures - total_errors - total_skipped + + return { + 'total': total_tests, + 'passed': passed_tests, + 'failures': total_failures, + 'errors': total_errors, + 'skipped': total_skipped, + 'failed_tests': failed_tests + } + +def generate_summary(result): + status = '✅ **테스트 성공**' if result['failures'] == 0 and result['errors'] == 0 else '❌ **테스트 실패**' + summary = f"{status}\n\n" + summary += f"- 총 테스트: {result['total']}개\n" + summary += f"- 성공: {result['passed']}개\n" + summary += f"- 실패: {result['failures']}개\n" + summary += f"- 에러: {result['errors']}개\n" + summary += f"- 스킵됨: {result['skipped']}개\n\n" + + if result['failed_tests']: + summary += "**실패한 테스트 상세 내역:**\n\n" + summary += "| 번호 | 테스트 클래스 | 테스트 메서드 | 에러 메시지 |\n" + summary += "|---|---|---|---|\n" + + for idx, test in enumerate(result['failed_tests'][:MAX_FAILED_TESTS], 1): + summary += f"| {idx} | `{test['classname']}` | `{test['name']}` | `{test['message']}` |\n" + + remaining_failures = len(result['failed_tests']) - MAX_FAILED_TESTS + if remaining_failures > 0: + summary += f"\n...외 {remaining_failures}개의 실패한 테스트가 있습니다.\n" + + summary += "\n자세한 내용은 테스트 리포트를 확인해주세요." + + else: + summary += "모든 테스트를 성공적으로 통과했습니다! 🎉" + + return summary + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: parse_test_results.py ") + sys.exit(1) + + test_results_dir = sys.argv[1] + + if not os.path.exists(test_results_dir): + print("❌ 테스트 결과 디렉터리가 존재하지 않습니다.") + sys.exit(0) + + result = parse_test_results(test_results_dir) + summary = generate_summary(result) + print(summary) diff --git a/test/example/HelloWorldTest.java b/test/example/HelloWorldTest.java index 8cb0ddcf1..ae70ba8ae 100644 --- a/test/example/HelloWorldTest.java +++ b/test/example/HelloWorldTest.java @@ -11,4 +11,5 @@ public void testHelloWorldMessage() { String message = "Hello, World!"; assertEquals("Hello, World!", message, "The message should be 'Hello, World!'"); } + }