
🔧 문제 상황
테스팅을 수행하다보면 수 많은 테스트케이스를 기록하고 업로드 하는 것은 필수적이다.
현재 안전에 직결되는 유닛의 테스트를 수행하다보니 테스트케이스가 1,000개 가까이 될 때 도 있는데 이를 리포트로 만들려면
- 테스트 수행 과정 캡처
- PPT에 이미지 삽입
- 체크박스나 텍스트로 결과 표시
이 모든 작업을 수행해야한다.
최근 Test Report 제출을 위해 수 많은 테스트케이스에 대한 작업을 모두 수동으로 하면서 시간이 너무 오래 걸려 이 과정을 자동화 하면 좋을 것 같다는 생각이 들었고, 빠르게 자동화를 수행하기 위해 파이썬 스크립트를 작성하는 것이 적합하다는 생각에 파이썬을 사용한 자동화 스크립트를 작성하게 되었다.
✅ 기대 동작
- 기존에 작업해둔 PPT 템플릿을 열고
- 다음 테스트케이스의 이미지 모두 삽입 ( 현재 TC1번이라면 TC2번 파일 생성)
- 이미지가 맨 뒤로 가도록 처리해서 기존 체크박스/텍스트는 그대로 유지 - > 수행 결과가 더 잘 보이게 함
- 동일한 형식으로 생성된 다음 테스트케이스용 PPT 저장 후, 이를 원하는 테스트케이스 번호까지 반복
⚙️ 동작 과정
1. 테스트케이스 번호 추출
원하는 테스트케이스 까지 ppt를 생성하는 작업을 반복하기 위해 파일 명에서 테스트케이스 번호를 추출한다.
내가 사용하는 테스트케이스는 무조건 파일 명이 xxx_TC0001... xxx_TC000x 까지 증가하는 형식이고, 해당 테스트케이스의 절차에 대해서는 xxx_TC0001_0 ~ xxx_TC0001_x 형식이기 때문에 해당 형식을 따를 수 있도록 작성했다.
xxx_TC0001_0 ~ xxx_TC0001_xbase_name = os.path.basename(ppt_path).replace('.pptx', '')
match = re.search(r'_TC(\d+)', base_name)
if not match:
raise ValueError("파일명에 'TC' 패턴이 없습니다.")
current_tc = int(match.group(1))2. 다음 테스트케이스 파일명 만들기
- > 현재 TC1번이라면 다음은 TC2, 2번이라면 다음은 TC3
# 새로 만들 ppt 이름 지정
def make_new_file_name(filename):
match = re.search(r'(.*_TC)(\d+)', filename)
if match:
prefix = match.group(1)
number = int(match.group(2)) + 1
new_filename = f"{prefix}{number:04d}"
return new_filename
else:
raise ValueError("파일명에 'TC' 패턴이 없습니다.")
re 모듈
regular expression으로 특정 문자열을 검색, 치환, 제거하는 기능을 제공한다.
파이썬에서 정규 표현식을 사용할 때 내장 모듈인 re를 사용하며 match(), fullmatch(), findall(), search() 등 문자열을 처리한다.
import re
match 함수
문자열의 시작부터 비교해서 동일한 패턴이 있는지 확인 후 없다면 None 반환
모든 테스트케이스에 대한 파일이 TC_숫자 형태여야 하므로 이 형태로 match 되는지 확인하여 테스트케이스에 대한 문서가 맞는지 확인했다.
group 함수
문자열에서 매칭되는 데이터를 찾아 반환
xxx_Rx_TC0001문자에서 group 함수를 수행하면 group(0)에는 xxx_RX_TC group(1)에는 테스트케이스의 번호인 1이 남게 된다.
match 함수와 groujp(0), group(1)에 대한 출력 결과는 아래와 같다.

3. 이미지 삽입 및 PPT 저장
3 - 1 기존 이미지 제거
prs = Presentation(ppt_path)
for i, slide in enumerate(prs.slides):
# 기존 이미지 제거
for shape in list(slide.shapes):
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
slide.shapes._spTree.remove(shape._element)
3 - 2 다음 테스트케이스에 첨부되어야 하는 이미지 삽입
이 때 이미지 파일의 확장자가 다를 수 있으므로, 찾고자하는 모든 타입을 넣어줘야한다.
실제로 jpg만 넣어 테스트를 진행해 png 파일에 대한 ppt가 생성되지 않아 원인을 찾고자 계속 디버깅을 했다...
image_base = f"{new_name}_{i+1}"
found_image = None
for ext in ['.png', '.jpg', '.jpeg']:
candidate = os.path.join(image_folder, image_base + ext)
if os.path.exists(candidate):
found_image = candidate
break
3. 이미지 삽입 및 저장
다음 테스트케이스의 이미지가 존재한다면 각 슬라이드에 해당 이미지를 모두 삽입하고, 새로 지정한 파일 이름으로 이미지를 삽입한 ppt 파일을 저장한다.
if found_image:
pic = slide.shapes.add_picture(found_image, Inches(0), Inches(0),
width=prs.slide_width, height=prs.slide_height)
prs.save(new_ppt_path)
🧩 트러블 슈팅 - 이미지 맨 뒤로 보내기
위에서 말했듯 새로운 테스트케이스의 이미지를 삽입한 후 작업을 마치는 것이 아니라 삽입한 이미지 객체를 해당 슬라이드의 맨 뒤로 보내 결과를 쉽게 알 수 있도록 표시해둔 체크박스와 텍스트박스의 설정을 그대로 유지해야 한다. 이 과정에서 이미지를 삽입 후 이미지 객체를 맨 뒤로 보내기 위해 ppt 내부의 Tree를 조작하여 이미지를 맨 뒤로 보내는데 성공했으나 ppt 파일이 손상되는 문제를 겪었다.
if os.path.exists(image_path):
pic = slide.shapes.add_picture(image_path, Inches(0), Inches(0),
width=prs.slide_width, height=prs.slide_height)
# 이미지 맨 뒤로 보내기
slide.shapes._spTree.remove(pic._element)
slide.shapes._spTree.insert(0, pic._element)

파워포인트 슬라이드 내부의 도형, 텍스트상자, 이미지 등의 렌더링 순서를 결정하는 spTree를 맨 뒤로 지정하여이미지를 삽입 하였으나, 파워포인트 내부의 XML 구조를 직접 조작하는 비공식 API를 사용한 것이 원인으로 예상된다.결과적으로 복구를 수행하면 원하는 동작이 모두 수행된 파일이 복구되기는 했으나, 많은 파일을 모두 일일히 열어 복구를 수행해야한다면 자동화를 만든 의미가 없을 것 같아 이 문제를 해결해야했다.
📌 관련 Issue
이와 같은 내용을 참고할 수 있는 github의 이슈를 확인하게 되었고, 언급되어 있는 방법들로 문제가 해결되지 않아 지정한 경로에 있는 모든 ppt 파일을 열어 이미지를 맨 뒤로 보내는 또 다른 매크로를 만들어 문제를 해결했다.
shape: set z-order of shape in slide · Issue #49 · scanny/python-pptx
github.com
🖥️ 전체 코드
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx import Presentation
from pptx.util import Inches
import glob
import os
import re
# 새로 만들 ppt 이름 지정
def make_new_file_name(filename):
match = re.search(r'(.*_TC)(\d+)', filename)
if match:
prefix = match.group(1)
number = int(match.group(2)) + 1
new_filename = f"{prefix}{number:04d}"
return new_filename
else:
raise ValueError("파일명에 'TC' 패턴이 없습니다.")
# 이미지 추가한 새로운 ppt 생성
def update_ppt_images(ppt_path, image_folder):
base_name = os.path.basename(ppt_path).replace('.pptx', '')
new_name = make_new_file_name(base_name)
new_ppt_path = os.path.join(os.path.dirname(ppt_path), f"{new_name}.pptx")
prs = Presentation(ppt_path)
for i, slide in enumerate(prs.slides):
# 기존 이미지 제거
for shape in list(slide.shapes):
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
slide.shapes._spTree.remove(shape._element)
# 새 이미지 삽입
# 이미지 확장자 설정 (.png, .jpg, .jpeg)
image_base = f"{new_name}_{i+1}"
found_image = None
for ext in ['.png', '.jpg', '.jpeg']:
candidate = os.path.join(image_folder, image_base + ext)
if os.path.exists(candidate):
found_image = candidate
break
if found_image:
pic = slide.shapes.add_picture(found_image, Inches(0), Inches(0),
width=prs.slide_width, height=prs.slide_height)
prs.save(new_ppt_path)
# end_tc까지의 ppt 생성하기 위한 함수
def batch_generate_ppt(ppt_path, image_folder, end_tc):
base_name = os.path.basename(ppt_path).replace('.pptx', '')
match = re.search(r'_TC(\d+)', base_name)
if not match:
raise ValueError("파일명에 'TC' 패턴이 없습니다.")
current_tc = int(match.group(1))
for _ in range(current_tc + 1, end_tc + 1):
update_ppt_images(ppt_path, image_folder)
base_name = os.path.basename(ppt_path).replace('.pptx', '')
new_name = make_new_file_name(base_name)
ppt_path = os.path.join(os.path.dirname(ppt_path), f"{new_name}.pptx")
batch_generate_ppt(base로 둘 ppt 이름(.pptx까지 포함), 삽입할 이미지의 경로, 만들고 싶은 마지막 tc의 번호)
📌 마무리
이 자동화 툴과 이미지를 맨 뒤로 보내는 매크로를 함께 활용한 결과, 전체 작업 시간은 약 93.91% 단축되었다.
- 수동 작업 시간: 3분 50초
- 툴 사용 시 소요 시간: 14초
다음 글에는 이미지를 맨 뒤로 보내는 매크로에 대해 정리해보겠다.
'Automotive > Automation' 카테고리의 다른 글
| Python으로 PPT 작업 자동화 하기 2 (0) | 2025.06.25 |
|---|