테스트 자동화와 지속적 통합(CI, Continuous Integration)은 현대 소프트웨어 개발에서 매우 중요한 부분을 차지한다. 특히 ChatGPT API와 같은 외부 API를 사용하는 경우, 테스트 자동화는 코드 변경 사항이 애플리케이션의 기능을 유지하면서도 오류 없이 동작하는지 보장하는 중요한 도구이다. 이 섹션에서는 테스트 자동화의 개념부터 시작해, CI 파이프라인을 구축하는 방법까지 순차적으로 다룬다.

테스트 자동화의 개념

테스트 자동화는 소프트웨어의 특정 부분을 테스트하는 작업을 자동으로 수행하는 과정이다. 이 과정에서 유닛 테스트, 통합 테스트, 기능 테스트 등 다양한 유형의 테스트가 자동화된다.

테스트 자동화의 이점은 크게 다음과 같다.

  1. 시간 절약: 반복적으로 수동 테스트를 실행할 필요 없이, 코드를 변경할 때마다 자동으로 테스트가 실행된다.
  2. 신뢰성: 사람이 수행하는 테스트보다 오류 발생 가능성이 낮다.
  3. 빠른 피드백: 코드를 수정하거나 추가할 때 즉시 테스트 결과를 확인할 수 있어 빠르게 문제를 파악할 수 있다.

Pytest를 이용한 테스트 자동화

Python의 테스트 자동화를 위해 자주 사용하는 라이브러리는 Pytest이다. Pytest는 간결한 문법과 유연한 확장성을 제공하여 테스트 케이스를 쉽게 작성할 수 있게 한다.

Pytest 설치 및 기본 사용법

먼저 Pytest를 설치하려면 다음과 같이 명령어를 입력한다.

pip install pytest

Pytest는 파일명이나 함수명이 test_로 시작하는 경우 자동으로 해당 테스트를 인식한다. 예를 들어, 아래는 간단한 유닛 테스트의 예이다.

def add(x, y):
    return x + y

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

이 코드를 테스트하려면 터미널에서 pytest 명령어를 실행하면 된다. Pytest는 모든 테스트 케이스를 실행하고 그 결과를 요약하여 보여준다.

Mocking을 이용한 ChatGPT API 테스트

외부 API를 호출하는 코드를 테스트할 때는 실제 API를 호출하는 대신 Mocking을 사용하여 테스트할 수 있다. Mocking은 특정 객체나 함수를 대체하여 테스트 중에 실제 API 호출 없이 그 동작을 시뮬레이션하는 방법이다.

unittest.mock 라이브러리를 사용하여 ChatGPT API를 호출하는 부분을 Mock할 수 있다. 아래는 간단한 예시이다.

from unittest.mock import patch
import openai

def get_chatgpt_response(prompt):
    response = openai.Completion.create(
        engine="text-davinci-003",
        prompt=prompt,
        max_tokens=100
    )
    return response.choices[0].text.strip()

@patch('openai.Completion.create')
def test_get_chatgpt_response(mock_create):
    mock_create.return_value = {
        'choices': [{'text': 'Mocked response!'}]
    }
    response = get_chatgpt_response("Hello, ChatGPT!")
    assert response == 'Mocked response!'

위 코드에서 @patch 데코레이터를 이용하여 openai.Completion.create 함수가 실제로 호출되지 않고, 대신 Mock 객체가 반환된다. 이를 통해 API 호출에 드는 시간과 비용을 절약할 수 있으며, 네트워크 오류와 같은 문제를 방지할 수 있다.

CI 파이프라인의 개념

지속적 통합(CI)은 코드 변경 사항을 자주 병합하고, 병합된 코드에 대해 자동화된 빌드 및 테스트 과정을 실행하는 소프트웨어 개발 방법론이다. CI는 다음과 같은 주요 목표를 갖는다.

  1. 빠른 피드백 제공: 코드 변경이 프로젝트의 다른 부분에 영향을 미치는지를 신속하게 확인할 수 있다.
  2. 코드 품질 향상: 자동화된 테스트를 통해 코드 품질을 유지하고, 결함이 배포 전에 발견되도록 한다.
  3. 팀 협업 강화: 개발자들이 자주 병합하여 충돌을 줄이고, 문제 발생 시 빠르게 해결할 수 있다.

CI 파이프라인은 보통 코드 저장소에 새로운 코드가 푸시(push)될 때 자동으로 테스트를 실행하고, 빌드가 성공적으로 완료되면 통합된 코드를 배포할 준비를 한다.

GitHub Actions를 이용한 CI 파이프라인 설정

GitHub Actions는 GitHub에서 제공하는 CI/CD 도구로, GitHub 저장소에 대한 작업(workflow)을 자동으로 실행할 수 있는 기능을 제공한다. 이를 이용해 코드가 푸시될 때마다 자동으로 테스트를 실행하는 파이프라인을 설정할 수 있다.

  1. .github/workflows 디렉토리 생성 먼저 GitHub 저장소의 루트 디렉토리에 .github/workflows 디렉토리를 생성하고, 그 안에 .yml 파일을 생성한다.

  2. GitHub Actions Workflow 파일 작성

예시로, pytest를 이용한 테스트 자동화를 포함한 간단한 CI 파이프라인 설정은 다음과 같다.

name: Python Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest

    - name: Run tests
      run: pytest

이 스크립트는 코드가 푸시될 때마다 자동으로 실행되며, Python 환경을 설정하고, requirements.txt에 정의된 패키지를 설치한 후 pytest를 실행하여 테스트를 진행한다.

GitHub Actions의 주요 구성 요소:

이렇게 설정된 워크플로우는 코드 변경 시 자동으로 테스트를 실행하며, 성공 여부를 GitHub UI에서 쉽게 확인할 수 있다.

Jenkins를 이용한 CI 파이프라인 구축

Jenkins는 오픈 소스 CI/CD 툴로, GitHub Actions와 달리 독립적인 서버에 설치하여 사용할 수 있다. Jenkins는 다양한 플러그인과 확장 기능을 통해 유연한 CI 파이프라인을 구축할 수 있다.

Jenkins 설치 및 설정

Jenkins를 설치하는 과정은 운영체제에 따라 다를 수 있지만, 일반적으로 다음과 같은 과정을 따른다.

  1. Jenkins 설치: Jenkins는 다양한 플랫폼에서 사용할 수 있다. 공식 사이트에서 플랫폼에 맞는 설치 방법을 따라 Jenkins를 설치할 수 있다.

  2. 플러그인 설치: Jenkins는 기본적으로 다양한 플러그인을 제공하며, 이를 통해 Python 환경이나 GitHub 연동과 같은 기능을 추가할 수 있다. 주로 사용하는 플러그인은 다음과 같다.

  3. Git 플러그인
  4. Pipeline 플러그인
  5. Python 플러그인

  6. Jenkins Job 구성: Jenkins에서 Python 프로젝트의 CI 파이프라인을 설정하려면 새로운 Job을 생성하고, 이 Job에서 파이프라인을 설정할 수 있다.

파이프라인 스크립트 예시

Jenkins에서 Declarative Pipeline을 사용하여 Python 테스트 자동화를 설정할 수 있다. Jenkins의 파이프라인 스크립트는 아래와 같이 구성된다.

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/your-repo-url.git'
            }
        }
        stage('Setup Python') {
            steps {
                sh 'python3 -m venv venv'
                sh './venv/bin/pip install -r requirements.txt'
            }
        }
        stage('Run Tests') {
            steps {
                sh './venv/bin/pytest'
            }
        }
    }
    post {
        always {
            junit '**/test-reports/*.xml'
        }
        success {
            echo 'All tests passed!'
        }
        failure {
            echo 'Some tests failed.'
        }
    }
}

이 Jenkins 파이프라인 스크립트는 다음 단계로 이루어진다.

  1. Checkout: Git 저장소에서 최신 코드를 가져온다.
  2. Setup Python: Python 가상 환경을 설정하고, 필요한 패키지를 설치한다.
  3. Run Tests: Pytest를 실행하여 테스트를 수행한다.
  4. Post Steps: 테스트가 완료된 후 항상 테스트 결과를 보고하고, 성공 또는 실패 여부에 따라 메시지를 출력한다.

이렇게 설정된 파이프라인은 Jenkins 서버에서 자동으로 실행되며, 테스트 결과를 실시간으로 확인할 수 있다. Jenkins는 대규모 프로젝트에서 매우 유용하며, 사용자 정의가 용이하고 확장성이 뛰어난 도구이다.

CI 파이프라인에서 중요한 고려 사항

CI 파이프라인을 성공적으로 구축하려면 몇 가지 중요한 요소를 고려해야 한다.

병렬 테스트 실행

테스트 자동화가 프로젝트 규모에 따라 시간이 많이 걸릴 수 있다. 이를 해결하기 위해 테스트를 병렬로 실행하는 방법을 고려해야 한다. Jenkins나 GitHub Actions에서는 여러 테스트를 병렬로 실행하여 전체 테스트 시간을 단축할 수 있다.

캐싱

CI 파이프라인에서는 패키지를 자주 설치하게 된다. 이때 동일한 패키지를 반복해서 설치하는 과정을 줄이기 위해 캐싱 기능을 사용하면 성능을 크게 향상시킬 수 있다. GitHub Actions에서는 actions/cache를 사용하여 종속성 파일이나 빌드 결과를 캐싱할 수 있다.

테스트 커버리지 보고

테스트 자동화는 테스트 커버리지를 높이는 것이 목표 중 하나이다. Python에서는 pytest-cov와 같은 라이브러리를 사용하여 코드의 테스트 커버리지를 측정할 수 있으며, 이를 CI 파이프라인에서 자동으로 보고할 수 있다.

pip install pytest-cov

그리고 테스트를 실행할 때는 다음과 같이 실행한다.

pytest --cov=my_module tests/

CI 파이프라인에서 테스트 커버리지를 자동으로 보고하고, 기준 이하일 경우 빌드를 실패하도록 설정할 수 있다.

환경 분리

개발, 테스트, 프로덕션 환경을 분리하여 각 환경에서 독립적으로 테스트 및 배포가 이루어지도록 설정하는 것도 중요하다. Jenkins나 GitHub Actions에서는 환경 변수나 환경 설정 파일을 통해 이러한 분리를 쉽게 관리할 수 있다.

ChatGPT API와 CI 통합 시 주의 사항

ChatGPT API를 사용하는 프로젝트에서 CI 파이프라인을 설정할 때는 몇 가지 주의 사항이 있다.

  1. API Rate Limit: OpenAI API는 일정한 요청 횟수 제한이 있다. CI에서 테스트 중에 너무 많은 API 요청을 보내면, Rate Limit에 도달할 수 있다. 이를 방지하기 위해 Mocking 기법을 적극적으로 사용하는 것이 좋다.
  2. API 키 관리: API 키는 보안에 중요한 요소이므로, CI 환경에서는 이를 안전하게 저장하고 사용하는 것이 중요하다. GitHub Actions에서는 Secrets를 사용하여 API 키를 보호할 수 있고, Jenkins에서는 Credentials을 사용할 수 있다.
  3. 비용 관리: ChatGPT API는 요청에 따른 비용이 발생하므로, CI에서 실제 API를 호출할 때는 비용이 발생할 수 있다. 이로 인해 비용이 과도하게 발생하지 않도록 Mocking을 사용하거나, 최소한의 API 호출로 테스트를 수행해야 한다.