Python 빌드 및 테스트하기

Python 빌드 및 테스트하기

Python 프로젝트를 빌드하고 테스트하는 지속적 통합(CI) 워크플로를 만드는 방법을 알려드릴게요. 패키지를 빌드·테스트·게시하는 과정을 한 번에 확인할 수 있어요.

출처: 문서

본문

Python 프로젝트를 빌드하고 테스트하는 지속적 통합(CI) 워크플로를 만드는 방법을 알아봅니다.

소개

이 가이드는 Python 패키지를 빌드, 테스트, 게시하는 방법을 보여줍니다.

GitHub 호스팅 러너에는 Python과 PyPy를 포함한 사전 설치된 소프트웨어가 있는 도구 캐시가 있습니다. 아무것도 설치할 필요가 없습니다! 최신 소프트웨어의 전체 목록과 사전 설치된 Python 및 PyPy 버전은 GitHub-hosted runners를 참고하세요.

사전 요구 사항

YAML과 GitHub Actions 구문에 익숙해야 합니다. 자세한 내용은 Writing workflows를 참고하세요.

Python과 pip에 대한 기본적인 이해를 권장합니다. 자세한 내용은 다음을 참고하세요:

Python 워크플로 템플릿 사용하기

빠르게 시작하려면 저장소의 .github/workflows 디렉터리에 워크플로 템플릿을 추가하세요.

GitHub는 저장소에 .py 파일이 하나 이상 이미 포함되어 있다면 작동하는 Python용 워크플로 템플릿을 제공합니다. 이 가이드의 이후 섹션은 이 워크플로 템플릿을 사용자 지정하는 방법의 예시를 제공합니다.

  1. GitHub에서 저장소의 메인 페이지로 이동합니다.

  2. 저장소 이름 아래에서 Actions를 클릭합니다.

    Screenshot of the tabs for the "github/docs" repository. The "Actions" tab is highlighted with an orange outline.

  3. 저장소에 이미 워크플로가 있다면 New workflow를 클릭합니다.

  4. "Choose a workflow" 페이지에 추천 워크플로 템플릿 목록이 표시됩니다. "Python application"을 검색하세요.

  5. "Python application" 워크플로에서 Configure를 클릭합니다.

  6. 필요에 따라 워크플로를 편집합니다. 예를 들어 Python 버전을 변경합니다.

  7. Commit changes를 클릭합니다.

    python-app.yml 워크플로 파일이 저장소의 .github/workflows 디렉터리에 추가됩니다.

Python 버전 지정하기

GitHub 호스팅 러너에서 사전 설치된 Python이나 PyPy 버전을 사용하려면 setup-python 액션을 사용합니다. 이 액션은 각 러너의 도구 캐시에서 특정 Python이나 PyPy 버전을 찾아 필요한 바이너리를 PATH에 추가하며, 이는 잡의 나머지 기간 동안 유지됩니다. 도구 캐시에 특정 Python 버전이 사전 설치되어 있지 않으면 setup-python 액션이 python-versions 저장소에서 적절한 버전을 다운로드해 설정합니다.

setup-python 액션을 사용하는 것은 다양한 러너와 다양한 Python 버전에서 일관된 동작을 보장하므로 GitHub Actions에서 Python을 사용하는 권장 방법입니다. 셀프 호스팅 러너를 사용한다면 Python을 설치하고 PATH에 추가해야 합니다. 자세한 내용은 setup-python 액션을 참고하세요.

아래 표는 각 GitHub 호스팅 러너에서 도구 캐시의 위치를 설명합니다.

Ubuntu Mac Windows
Tool Cache Directory /opt/hostedtoolcache/* /Users/runner/hostedtoolcache/* C:\hostedtoolcache\windows\*
Python Tool Cache /opt/hostedtoolcache/Python/* /Users/runner/hostedtoolcache/Python/* C:\hostedtoolcache\windows\Python\*
PyPy Tool Cache /opt/hostedtoolcache/PyPy/* /Users/runner/hostedtoolcache/PyPy/* C:\hostedtoolcache\windows\PyPy\*

셀프 호스팅 러너를 사용한다면 러너가 setup-python 액션을 사용해서 의존성을 관리하도록 구성할 수 있습니다. 자세한 내용은 setup-python README의 using setup-python with a self-hosted runner를 참고하세요.

GitHub는 시맨틱 버저닝 구문을 지원합니다. 자세한 내용은 Using semantic versioningSemantic versioning specification을 참고하세요.

여러 Python 버전 사용하기

다음 예시는 잡에 매트릭스를 사용해서 여러 Python 버전을 설정합니다. 자세한 내용은 Running variations of jobs in a workflow를 참고하세요.

name: Python package

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      # You can test your matrix by printing the current Python version
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"

특정 Python 버전 사용하기

특정 Python 버전을 구성할 수 있습니다. 예를 들어 3.12입니다. 또는 시맨틱 버전 구문을 사용해서 최신 마이너 릴리스를 얻을 수 있습니다. 이 예시는 Python 3의 최신 마이너 릴리스를 사용합니다:

name: Python package

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
      - name: Set up Python
        # This is the version of the action for setting up Python, not the Python version.
        uses: actions/setup-python@v5
        with:
          # Semantic version range syntax or exact version of a Python version
          python-version: '3.x'
          # Optional - x64 or x86 architecture, defaults to x64
          architecture: 'x64'
      # You can test your matrix by printing the current Python version
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"

버전 제외하기

사용할 수 없는 Python 버전을 지정하면 setup-python##[error]Version 3.7 with arch x64 not found 같은 오류로 실패합니다. 오류 메시지에는 사용 가능한 버전이 포함됩니다.

실행하고 싶지 않은 Python 구성이 있다면 워크플로에서 exclude 키워드를 사용할 수도 있습니다. 자세한 내용은 Workflow syntax for GitHub Actions를 참고하세요.

name: Python package

on: [push]

jobs:
  build:

    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.9", "3.11", "3.13", "pypy3.10"]
        exclude:
          - os: macos-latest
            python-version: "3.11"
          - os: windows-latest
            python-version: "3.11"

기본 Python 버전 사용하기

워크플로에서 사용되는 Python 버전을 setup-python으로 구성하는 것을 권장합니다. 의존성을 명시적으로 만들기 때문입니다. setup-python을 사용하지 않으면 python을 호출할 때 PATH에 설정된 기본 Python 버전이 모든 셸에서 사용됩니다. 기본 Python 버전은 GitHub 호스팅 러너마다 다르므로 예상치 못한 변경이 발생하거나 예상보다 오래된 버전이 사용될 수 있습니다.

GitHub-hosted runner Description
Ubuntu Ubuntu 러너에는 /usr/bin/python/usr/bin/python3 아래에 여러 버전의 시스템 Python이 설치되어 있습니다. Ubuntu와 함께 제공되는 Python 버전은 GitHub가 도구 캐시에 설치하는 버전에 추가로 제공됩니다.
Windows 도구 캐시의 Python 버전을 제외하고 Windows에는 동등한 시스템 Python 버전이 함께 제공되지 않습니다. 다른 러너와 일관된 동작을 유지하고 setup-python 액션 없이도 Python을 바로 사용할 수 있도록 GitHub는 도구 캐시의 일부 버전을 PATH에 추가합니다.
macOS macOS 러너에는 도구 캐시의 버전 외에 여러 버전의 시스템 Python이 설치되어 있습니다. 시스템 Python 버전은 /usr/local/Cellar/python/* 디렉터리에 있습니다.

의존성 설치하기

GitHub 호스팅 러너에는 pip 패키지 관리자가 설치되어 있습니다. 코드를 빌드하고 테스트하기 전에 pip를 사용해서 PyPI 패키지 레지스트리에서 의존성을 설치할 수 있습니다. 예를 들어 아래 YAML은 pip 패키지 설치 프로그램과 setuptools, wheel 패키지를 설치하거나 업그레이드합니다.

의존성을 캐시해서 워크플로를 빠르게 할 수도 있습니다. 자세한 내용은 Dependency caching reference를 참고하세요.

steps:
- uses: actions/checkout@v6
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.x'
- name: Install dependencies
  run: python -m pip install --upgrade pip setuptools wheel

Requirements 파일

pip를 업데이트한 후 일반적인 다음 단계는 requirements.txt에서 의존성을 설치하는 것입니다. 자세한 내용은 pip를 참고하세요.

steps:
- uses: actions/checkout@v6
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.x'
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt

의존성 캐싱하기

setup-python 액션을 사용해서 의존성을 캐시하고 복원할 수 있습니다.

다음 예시는 pip용 의존성을 캐시합니다.

steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'
- run: pip install -r requirements.txt
- run: pip test

기본적으로 setup-python 액션은 전체 저장소에서 의존성 파일(pip용 requirements.txt, pipenv용 Pipfile.lock, poetry용 poetry.lock)을 검색합니다. 자세한 내용은 setup-python README의 Caching packages dependencies를 참고하세요.

사용자 지정 요구 사항이 있거나 캐싱을 더 세밀하게 제어해야 한다면 cache 액션을 사용할 수 있습니다. pip는 러너의 운영 체제에 따라 다른 위치에 의존성을 캐시합니다. 캐시해야 하는 경로는 사용하는 운영 체제에 따라 위 Ubuntu 예시와 다를 수 있습니다. 자세한 내용은 cache 액션 저장소의 Python caching examples를 참고하세요.

코드 테스트하기

로컬에서 코드를 빌드하고 테스트하는 데 사용하는 것과 같은 명령을 사용할 수 있습니다.

pytest와 pytest-cov로 테스트하기

이 예시는 pytestpytest-cov를 설치하거나 업그레이드합니다. 그런 다음 테스트가 실행되고 JUnit 형식으로 출력되며 코드 커버리지 결과는 Cobertura로 출력됩니다. 자세한 내용은 JUnitCobertura를 참고하세요.

steps:
- uses: actions/checkout@v6
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.x'
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
- name: Test with pytest
  run: |
    pip install pytest pytest-cov
    pytest tests.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html

[!TIP] 이 예시는 이미 Cobertura XML 커버리지 보고서(--cov-report=xml)를 생성합니다. 커버리지 결과를 풀 리퀘스트에 직접 표시하려면 actions/upload-code-coverage 액션을 사용해서 보고서를 업로드하세요. Setting up code coverage for your repository를 참고하세요.

Ruff로 코드 린트 및/또는 포맷하기

다음 예시는 ruff를 설치하거나 업그레이드하고 모든 파일을 린트하는 데 사용합니다. 자세한 내용은 Ruff를 참고하세요.

steps:
- uses: actions/checkout@v6
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.x'
- name: Install the code linting and formatting tool Ruff
  run: pipx install ruff
- name: Lint code with Ruff
  run: ruff check --output-format=github --target-version=py39
- name: Check code formatting with Ruff
  run: ruff format --diff --target-version=py39
  continue-on-error: true

포맷 스텝에는 continue-on-error: true가 설정되어 있습니다. 이는 포맷 스텝이 성공하지 못해도 워크플로가 실패하지 않도록 합니다. 모든 포맷 오류를 해결한 후에는 이 옵션을 제거해서 워크플로가 새 문제를 잡도록 할 수 있습니다.

tox로 테스트 실행하기

GitHub Actions를 사용하면 tox로 테스트를 실행하고 작업을 여러 잡에 분산할 수 있습니다. 특정 버전을 지정하는 대신 PATH의 Python 버전을 선택하려면 -e py 옵션으로 tox를 호출해야 합니다. 자세한 내용은 tox를 참고하세요.

name: Python package

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python: ["3.9", "3.11", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
      - name: Install tox and any other packages
        run: pip install tox
      - name: Run tox
        # Run tox using the version of Python in `PATH`
        run: tox -e py

워크플로 데이터를 아티팩트로 패키징하기

워크플로가 완료된 후 볼 수 있도록 아티팩트를 업로드할 수 있습니다. 예를 들어 로그 파일, 코어 덤프, 테스트 결과, 스크린샷을 저장해야 할 수 있습니다. 자세한 내용은 Store and share data with workflow artifacts를 참고하세요.

다음 예시는 upload-artifact 액션을 사용해서 pytest 실행에서 나온 테스트 결과를 보관하는 방법을 보여줍니다. 자세한 내용은 upload-artifact 액션을 참고하세요.

name: Python package

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6
      - name: Setup Python # Set Python version
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      # Install pip and pytest
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest
      - name: Test with pytest
        run: pytest tests.py --doctest-modules --junitxml=junit/test-results-${{ matrix.python-version }}.xml
      - name: Upload pytest test results
        uses: actions/upload-artifact@v4
        with:
          name: pytest-results-${{ matrix.python-version }}
          path: junit/test-results-${{ matrix.python-version }}.xml
        # Use always() to always run this step to publish test results when there are test failures
        if: ${{ always() }}

PyPI에 게시하기

CI 테스트가 통과하면 Python 패키지를 PyPI에 게시하도록 워크플로를 구성할 수 있습니다. 이 섹션은 릴리스를 게시할 때마다 GitHub Actions를 사용해서 패키지를 PyPI에 업로드하는 방법을 보여줍니다. 자세한 내용은 Managing releases in a repository를 참고하세요.

아래 예시 워크플로는 PyPI에 인증하기 위해 Trusted Publishing을 사용하므로 수동으로 구성된 API 토큰이 필요 없습니다.

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.

name: Upload Python Package

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  release-build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"

      - name: Build release distributions
        run: |
          # NOTE: put your own distribution build steps here.
          python -m pip install build
          python -m build

      - name: Upload distributions
        uses: actions/upload-artifact@v4
        with:
          name: release-dists
          path: dist/

  pypi-publish:
    runs-on: ubuntu-latest

    needs:
      - release-build

    permissions:
      # IMPORTANT: this permission is mandatory for trusted publishing
      id-token: write

    # Dedicated environments with protections for publishing are strongly recommended.
    environment:
      name: pypi
      # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
      # url: https://pypi.org/p/YOURPROJECT

    steps:
      - name: Retrieve release distributions
        uses: actions/download-artifact@v5
        with:
          name: release-dists
          path: dist/

      - name: Publish release distributions to PyPI
        uses: pypa/gh-action-pypi-publish@6f7e8d9c0b1a2c3d4e5f6a7b8c9d0e1f2a3b4c5d

이 워크플로와 필요한 PyPI 설정에 대한 자세한 내용은 Configuring OpenID Connect in PyPI를 참고하세요.

더 알아보기 (Learn more)