파이썬 API 테스트 프레임워크 구현 가이드

목차

  • 파이썬 API 테스트 입문 - 파이썬 기초
  • 파이썬 API 테스트 입문 - API 테스트 빠른 시작
  • 파이썬 API 테스트 입문 - API 보안 검증, 매개변수화 및 검증
  • 파이썬 API 테스트 입문 - API 테스트 프레임워크 구현

API 테스트 프레임워크

프레임워크란 무엇인가

현재 주류 API 테스트 솔루션

  • 도구 중심 접근법
  • Java 중심 접근법
  • Python 중심 접근법
  • API 플랫폼 접근법

프레임워크 유형

  • 기록 및 재생 방식
  • 데이터 기반 테스트
  • 동작 기반 테스트

프레임워크 계층 및 설계

프레임워크 계층

  • 표현 계층: (사용자 인터페이스)
  • 비즈니스 로직 계층: (데이터 읽기, 구성 로드 및 요청 조립) + 실행 제어 계층(pytest)
  • 데이터 계층: (구성 읽기/데이터 읽기/데이터베이스 연결/기타(log/email)

프레임워크 설계

  • tests: 테스트 케이스 디렉토리
    • users: (사용자 모듈)
      • test_user.py: 테스트 케이스
    • base.py: 케이스 공통 메서드
  • resources: 데이터 파일 디렉토리
    • user_test_data.xlsx: 테스트 케이스 데이터 파일
  • settings: 설정 파일 디렉토리
    • default.conf: 기본 설정 파일
  • results: pytest 생성 보고서 저장 경로
  • logs: log 저장 경로, 일별 log 생성
  • utils: 공통 메서드 디렉토리
    • config_handler.py: 설정 파일 읽기
    • data_reader.py: 데이터 파일 읽기
    • database.py: 데이터베이스 연결
    • logger.py: 로그 설정
    • notification.py: 이메일 발송 설정

프레임워크 구현

settings/default.conf: 표현 계층 - 프로젝트 설정 파일

[execution]
log_level=debug
results_dir=results
logs_dir=logs
timeout=10

[server]
test = http://127.0.0.1:5000
staging = http://127.0.0.1:6000
production = http://127.0.0.1:7000

[database_test]
host = localhost
port = 3307
db = api_test
user = root
password = 

[database_staging]

[database_production]

[notification]
server = smtp.gmail.com
user = test_results@gmail.com
password = ******
subject = API Test Report
recipient = tester@example.com

resources/user_test_data.xlsx: 표현 계층 - 케이스 데이터 파일

등록 테이블 (시트 이름: registration)

테스트케이스 URL 메소드 데이터타입 데이터 상태코드 메시지
test_reg_normal /api/users/register/ POST JSON {"username": "{USERNAME}", "password": "123456"} 200 성공

로그인 테이블 (시트 이름: login)

테스트케이스 URL 메소드 데이터타입 데이터 응답텍스트
test_login_normal /api/users/login/ POST FORM 로그인 성공

SQL 테이블 (시트 이름: queries)

쿼리명 SQL문
checkUser SELECT * FROM users WHERE username=
checkUserPassword SELECT * FROM users WHERE username={USERNAME} AND password=

utils/config_handler.py: 데이터 계층 - 설정 파일 읽기

"""
1. 설정 파일에서 각 섹션 정보 가져오기
2. 프로젝트의 절대 경로 반환
"""
import os
import configparser

# 상대 패키지 임포트 문제 해결

project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


class ConfigHandler:
    def __init__(self, filename="default.conf"):
        self.config = configparser.ConfigParser()
        self.config.read(os.path.join(project_path, "settings", filename))
        
    def get_execution_config(self, option):
        return self.config.get("execution", option)

    def get_server_url(self, environment):
        return self.config.get("server", environment)

    def get_test_db_config(self, option):
        return self.config.get("database_test", option)

    def get_notification_config(self, option):
        return self.config.get("notification", option)

if __name__ == "__main__":
    config = ConfigHandler()
    print(config.get_execution_config("log_level"))
    print(config.get_server_url("test"))

utils/data_reader.py: 데이터 계층 - 데이터 파일 읽기

"""
1. Excel에서 API 테스트 데이터 읽기
2. SQL 명령어 읽기
"""
import xlrd
import sys
import os
sys.path.append("..")
from utils.config_handler import project_path

class TestDataLoader:
    def __init__(self, filename):
        data_file_path = os.path.join(project_path, "resources", filename)   
        self.workbook = xlrd.open_workbook(data_file_path)

    def get_test_case(self, sheet_name, case_name):
        worksheet = self.workbook.sheet_by_name(sheet_name)
        for i in range(1, worksheet.nrows):
            if worksheet.cell(i, 0).value == case_name:
                return worksheet.row_values(i)

        print("테스트 케이스를 찾을 수 없습니다")
        return None

    def get_query(self, query_name):
        worksheet = self.workbook.sheet_by_name("queries")
        for i in range(worksheet.nrows):
            if worksheet.cell(i, 0).value == query_name:
                return worksheet.cell(i, 1).value
        print("쿼리를 찾을 수 없습니다")
        return None

if __name__ == "__main__":
    data = TestDataLoader("user_test_data.xlsx")
    print(data.get_test_case("registration", "test_reg_normal"))
    print(data.get_query("checkUser"))

utils/database.py: 데이터 계층 - 데이터베이스 연결

"""
1. 설정 파일에서 데이터베이스 구성 읽기
2. 데이터베이스 연결
3. SQL 실행 및 결과 반환
"""
import sys
import pymysql
sys.path.append("..")
from utils.config_handler import ConfigHandler

class DatabaseConnector:
    def __init__(self):
        config = ConfigHandler()
        self.connection = pymysql.connect(
            host=config.get_test_db_config("host"),
            port=int(config.get_test_db_config("port")),
            db=config.get_test_db_config("db"),
            user=config.get_test_db_config("user"),
            password=config.get_test_db_config("password"),
            charset="utf8"
        )

        self.cursor = self.connection.cursor()

    def execute_query(self, sql):
        self.cursor.execute(sql)
        return self.cursor.fetchall()

    def __del__(self):
        self.cursor.close()
        self.connection.close()

if __name__ == "__main__":
    db = DatabaseConnector()
    print(db.execute_query("SELECT * FROM users"))

utils/logger.py: 데이터 계층 - 로그 설정

"""
1. log 출력 형식 time - loglevel - file - func - line - msg 설정
2. log 파일 및 화면 출력 지원
3. 다른 모듈에서 호출할 수 있는 logger 반환
"""
import sys
sys.path.append("..")

from utils.config_handler import ConfigHandler, project_path
import time
import logging
import os

class LoggerSetup:
    @classmethod
    def configure_logging(cls):
        config = ConfigHandler()
        log_dir = os.path.join(project_path, config.get_execution_config("logs_dir"))
        today = time.strftime("%Y%m%d", time.localtime(time.time()))
        log_file = os.path.join(log_dir, today + ".log")

        # 표준 logger 가져오기 및 loglevel 설정
        cls.logger = logging.getLogger()
        cls.logger.setLevel(eval("logging." + config.get_execution_config("log_level").upper()))

        # 다양한 핸들러 생성
        file_handler = logging.FileHandler(log_file, mode="a", encoding='utf-8')
        console_handler = logging.StreamHandler()

        # 출력 형식 정의
        formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
        file_handler.setFormatter(formatter)
        console_handler.setFormatter(formatter)

        # 커스텀 핸들러를 logger에 추가
        cls.logger.addHandler(file_handler)
        cls.logger.addHandler(console_handler)

    @classmethod
    def get_logger(cls):
        cls.configure_logging()
        return cls.logger

if __name__ == "__main__":
    logger = LoggerSetup.get_logger()
    logger.info("테스트 메시지")
    logger.debug("디버그 정보")

utils/notification.py: 데이터 계층 - 이메일 서버 연결

"""
1. 설정 파일에서 SMTP 구성 읽기
2. results 폴더에서 report.html 열기 및 이메일 발송
"""
import smtplib
from email.mime.text import MIMEText
import os
import sys
sys.path.append("..")
from utils.config_handler import ConfigHandler, project_path
from utils.logger_setup import LoggerSetup


def send_test_report(report_name):
    config = ConfigHandler()
    logger = LoggerSetup.get_logger()
    report_file = os.path.join(project_path, config.get_execution_config("results_dir"), report_name)

    with open(report_file, "rb") as f:
        body = f.read()

    # 이메일 본문 형식화
    message = MIMEText(body, "html", "utf-8")

    # 이메일 헤더 구성
    message["Subject"] = config.get_notification_config("subject")
    message["From"] = config.get_notification_config("user")
    message["To"] = config.get_notification_config("recipient")

    # SMTP 서버 연결 및 이메일 발송
    smtp_server = smtplib.SMTP()
    smtp_server.connect(config.get_notification_config("server"))
    smtp_server.login(config.get_notification_config("user"), config.get_notification_config("password"))
    smtp_server.sendmail(config.get_notification_config("user"), config.get_notification_config("recipient"), message.as_string())
    print("이메일이 성공적으로 발송되었습니다")

if __name__ == "__main__":
    send_test_report("report.html")

tests/base.py: 비즈니스 로직 계층 - 테스트 케이스 실행을 위한 메서드 캡슐화

"""
1. 데이터 로딩
2. API 요청 전송
3. 테스트 케이스를 위한 메서드 캡슐화

"""
import sys
sys.path.append("..")
from utils.logger_setup import LoggerSetup
from utils.config_handler import ConfigHandler
from utils.database_connector import DatabaseConnector
from utils.data_loader import TestDataLoader
import json
import requests


class TestCaseExecutor:
    def __init__(self):
        self.logger = LoggerSetup.get_logger()
        self.config = ConfigHandler()

    def load_test_data(self, data_file):
        self.data = TestDataLoader(data_file)

    def set_environment(self, env):
        self.environment = env

    def run_test_case(self, sheet_name, case_name, variables={}):
        case_data = self.data.get_test_case(sheet_name, case_name)

        url = self.config.get_server_url(self.environment) + case_data[1]
        data = case_data[4].format(**variables)
        
        if case_data[3].lower() == "form":
            data = json.loads(data)
            headers = {}
        else:
            headers = {"content-type": "application/json"}

        if case_data[2].lower() == "get":
            response = requests.get(url=url)
        else:
            response = requests.post(url=url, headers=headers, data=data)
        return response.text
        
    def verify_response(self):
        pass
    
    def verify_database(self, query_name, variables={}):
        query = self.data.get_query(query_name).format(**variables)
        db = DatabaseConnector()
        return db.execute_query(query)
        

if __name__ == "__main__":
    executor = TestCaseExecutor()
    executor.set_environment("test")
    executor.load_test_data("user_test_data.xlsx")
    result = executor.run_test_case("login", "test_login_normal")
    print(result)

tests/users/test_user.py: 표현 계층: 테스트 케이스 스크립트

import sys
import random
import pytest
sys.path.append("../..")
from tests.base import TestCaseExecutor

test_executor = TestCaseExecutor()

def setup_module(module):
    test_executor.set_environment('dev')
    test_executor.load_test_data('user_test_data.xlsx')

def test_login_normal():
    result = test_executor.run_test_case("login", "test_login_normal")

if __name__ == '__main__':
    pytest.main(["-q", "test_user.py"])

run_tests.py: 표현 계층: 모든 테스트 케이스 실행 진입점

import os
import time
from utils.config_handler import ConfigHandler
from utils.notification import send_test_report
import pytest

def execute_all_tests():
    config = ConfigHandler()
    results_dir = config.get_execution_config("results_dir")
    timestamp = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
    report_name = os.path.join(results_dir, 'test_report_' + timestamp + '.html')
    pytest.main(["-q", "tests", "--html=" + report_name])
    send_test_report(report_name)

if __name__ == '__main__':
    execute_all_tests()

태그: API 테스트 파이썬 테스트 프레임워크 자동화 테스트 Pytest 테스트 자동화

7월 12일 21:49에 게시됨