테스트 자동화에서 실패하지 않는 5가지 설계 원칙|QA 엔지니어가 실무에서 사용하는 설계 패턴

테스트 자동화가 실패하는 원인의 대부분은 「도구의 문제」가 아니라 설계·운용의 문제입니다. 재사용성·보수성·독립성·안정성·실행 속도——이 5가지 원칙을 파악함으로써 장기적으로 유지할 수 있는 자동화를 구축할 수 있습니다.

📌 이런 분께 추천합니다

  • 테스트 자동화를 도입했지만 잘 기능하지 않는다고 느끼는 분
  • 자동화 코드의 유지보수가 힘들어진 팀
  • 테스트 자동화를 앞으로 시작하는 데 있어 실패하고 싶지 않은 분
  • 자동화의 설계 방침을 팀에서 통일하고 싶은 QA 엔지니어

✅ 이 글을 읽으면 알 수 있는 것

  • 테스트 자동화가 자주 실패하는 5가지 패턴
  • 테스트 피라미드의 이상적인 비율과 QA 전략에의 응용
  • Page Object Model(POM)에 의한 보수하기 쉬운 설계
  • DRY한 재사용 가능한 테스트 코드 작성법
  • 테스트 데이터 관리와 플레이키 테스트 대책의 실무 노하우

👤

글쓴이 소개:QA 엔지니어로서 Selenium·Playwright·Python을 사용한 테스트 자동화를 실무에서 담당. 「자동화했는데 계속 망가진다」는 실패를 경험한 후에 도달한 설계 원칙을, 실증된 내용으로 전달합니다. GitHub에서 코드 공개 중。

📌 이 글의 결론

테스트 자동화를 오래 지속하는 열쇠는 「동작하는 테스트를 작성하는 것」보다 「망가지기 어려운 테스트를 설계하는 것」입니다. 테스트 피라미드·POM·DRY·테스트 데이터 분리·플레이키 대책——이 5가지를 지키는 것만으로 유지보수 비용은 대폭 줄어듭니다.

「테스트 자동화를 도입했는데 바로 망가져서 결국 수동으로 돌아갔다」——이런 경험을 하신 분이 적지 않습니다. 테스트 자동화는 도구만 도입하면 해결되는 것이 아니라, 설계와 운용의 사고방식이 중요합니다.

이 글에서는 테스트 자동화가 자주 실패하는 패턴과, 그 대책이 되는 5가지 설계 원칙을 실무 경험을 바탕으로 해설합니다.


테스트 자동화가 자주 실패하는 5가지 패턴

먼저 「왜 실패하는가」를 이해하는 것이 올바른 설계로 가는 첫걸음입니다.

❌ 자주 있는 실패 패턴

  • UI가 변경될 때마다 테스트가 망가진다(취약한 셀렉터의 사용)
  • 같은 처리가 테스트 코드 여기저기에 중복되어 있다(DRY 원칙의 무시)
  • 테스트가 다른 테스트의 실행 결과에 의존하고 있다(테스트 간의 의존)
  • 테스트 데이터가 코드에 삽입되어 있어 관리할 수 없다(테스트 데이터의 혼재)
  • 원인 불명으로 실패하거나 성공하거나 한다(플레이키 테스트)
🔴
① 취약한 셀렉터

자동 생성 ID나 복잡한 XPath는 UI 변경으로 즉시 망가진다

📋
② 코드 중복

로그인 처리가 10군데에 중복→한 곳 변경으로 전멸

🔗
③ 테스트 간의 의존

테스트A 성공을 전제로 테스트B가 동작하는 설계는 파탄한다

🗄
④ 테스트 데이터 혼재

테스트 데이터가 코드에 삽입되어 있으면 관리 불능이 된다

🎲
⑤ 플레이키 테스트

같은 코드로 「성공하거나 실패하거나」하는 테스트는 신뢰를 잃는다


설계 원칙① 테스트 피라미드로 밸런스를 설계한다

테스트 자동화 설계의 최중요 개념이 테스트 피라미드입니다. 어떤 종류의 테스트를 어떤 비율로 가질지는 QA 전략의 기본으로 전 세계 개발 현장에서 사용되고 있습니다.

▼ 테스트 피라미드와 이상적인 비율

🖥 E2E / UI
약 10%

🌐 Integration / API
약 20%

🧱 Unit(단위 테스트)
약 70%

종류 이상 비율 이유
🧱 단위 테스트 70% 고속·저비용·문제의 원인을 특정하기 쉽다
🌐 Integration / API 20% 단위 테스트에서는 찾을 수 없는 모듈 간 버그를 커버
🖥 E2E / UI 10% 중요한 사용자 흐름에 한정. 너무 늘리면 전체가 무거워진다

⚠️ 안티패턴:아이스크림콘형

E2E가 많고·단위 테스트가 적은 「역피라미드」는 최악의 패턴입니다. 실행이 느리고·망가지기 쉽고·원인 특정이 곤란——삼중고의 상태가 됩니다. 「일단 E2E」로 시작하는 것이 최대의 NG입니다.


설계 원칙② Page Object Model(POM)로 보수성을 높인다

UI 테스트 설계에서 필수적인 설계 패턴이 Page Object Model(POM)입니다. 「페이지마다 클래스를 만들고, UI 요소의 조작 로직과 테스트 로직을 분리한다」는 사고방식입니다.

▼ POM의 개념

테스트 코드
UI 요소를 직접 작성
Page 클래스
UI 조작을 집약
테스트 코드
검증 로직만
🔧 유지보수하기 쉽다

UI 변경 시 Page 클래스만 수정하면 된다

♻️ 재사용할 수 있다

로그인 처리를 복수의 테스트에서 공유할 수 있다

🛡 UI 변경에 강하다

테스트 코드가 UI 구조에 의존하지 않게 된다

▼ POM을 사용한 폴더 구성 예시

project/
├── pages/               # UI 조작 클래스(POM)
│   ├── login_page.py    # 로그인 페이지 조작
│   ├── top_page.py      # 톱 페이지 조작
│   └── cart_page.py     # 장바구니 페이지 조작
├── tests/               # 테스트 케이스(검증 로직)
│   ├── test_login.py
│   └── test_checkout.py
└── conftest.py          # pytest의 fixture(공통 설정)
💡 실무 Tip: POM 도입 후에는 「로그인 페이지의 셀렉터가 바뀌었을 때」login_page.py의 한 곳을 고치는 것만으로 됩니다. POM 없이는 로그인 처리를 작성한 모든 테스트 파일을 수정해야 하며, 수정 누락도 발생합니다.

설계 원칙③ DRY한 재사용 가능한 테스트 코드를 작성한다

DRY(Don’t Repeat Yourself)원칙은 테스트 코드에도 해당됩니다. 같은 처리를 복수의 곳에 작성하면 변경할 때마다 전체를 수정해야 하는 필요가 생기며, 유지보수 비용이 급증합니다.

❌ DRY 위반(자주 있는 예)

# test_a.py·test_b.py·test_c.py
# 각각에 같은 로그인 처리가...
driver.find_element(By.ID,"email")
  .send_keys("user@test.com")
driver.find_element(By.ID,"pass")
  .send_keys("password")
driver.find_element(By.ID,"btn")
  .click()

로그인 처리가 10군데에 중복

✅ DRY 적용(권장)

# pages/login_page.py에 공통화
def login(page, email, password):
    page.fill("#email", email)
    page.fill("#pass", password)
    page.click("#btn")

# 각 테스트에서 호출하기만 하면 됨
login(page, "user@test.com", "pass")

한 곳을 고치는 것만으로 전체 테스트에 반영

▼ 재사용 가능한 헬퍼 함수 예시

# helpers/auth.py — 인증 계열의 공통 처리
def login(page, email="default@test.com", password="pass123"):
    page.fill("#email", email)
    page.fill("#password", password)
    page.click("#submit")

def logout(page):
    page.click("#user-menu")
    page.click("#logout")

# helpers/user.py — 사용자 조작의 공통 처리
def create_user(api_client, name, email):
    return api_client.post("/users", {"name": name, "email": email})

def delete_user(api_client, user_id):
    return api_client.delete(f"/users/{user_id}")
💡 실무 Tip: login()·create_user()·delete_user() 등의 공통 처리는 헬퍼 함수나 POM 클래스에 모읍시다. 테스트 코드가 「무엇을 검증하는가」에만 집중할 수 있게 되어 가독성도 크게 향상됩니다.

설계 원칙④ 테스트 데이터를 코드에서 분리한다

테스트 설계에서 「테스트 코드」와 「테스트 데이터」는 분리하는 것이 기본입니다. 테스트 데이터가 코드에 삽입되어 있으면 데이터 변경 때마다 코드를 수정할 필요가 생깁니다.

🔧
fixture(pytest)

테스트의 전후 처리와 데이터를 일원 관리. 셋업·티어다운을 자동화할 수 있다.

@pytest.fixture
def user_data():
 return {“name”:”Taro”}

🎭
mock(모크)

외부 API·DB로의 실제 요청을 가짜로 대체. 고속·안정·비용 불필요.

@mock.patch(“requests.get”)
def test_api(mock_get):
 mock_get.return_value = …

🗄
test DB(테스트용 DB)

운영 DB와는 별도의 테스트용 DB를 준비. 테스트 후 데이터를 리셋할 수 있다.

DATABASE_URL =
 ”sqlite:///test.db”
# 테스트 후 자동 삭제

⚠️ 주의: 테스트 데이터에 실제 개인 정보나 기밀 데이터를 사용하는 것은 절대 NGです. 반드시 더미 데이터를 준비합시다. Python이라면 faker 라이브러리로 간단하게 생성할 수 있습니다.

설계 원칙⑤ 플레이키 테스트를 근절한다

플레이키 테스트(Flaky Tests)란, 같은 코드로 「성공하거나 실패하거나」하는 테스트를 말합니다. 테스트 자동화에서 가장 많고·가장 성가신 문제 중 하나입니다. 플레이키 테스트가 늘어나면 「테스트 결과를 아무도 신뢰하지 않게 된다」는 최악의 상황에 빠집니다.

🎲 플레이키 테스트의 주요 원인

  • wait 부족:요소가 아직 표시되지 않았는데 클릭하려고 한다
  • 비동기 처리:API 응답이 느릴 때 다음 조작으로 진행해 버린다
  • 테스트 데이터 경합:병렬 실행 중에 같은 데이터를 복수의 테스트가 덮어쓴다

▼ 원인과 대책 일람

원인 증상 대책
⏱ wait 부족 요소가 아직 표시되지 않았는데 클릭 explicit wait 사용
🔄 비동기 처리 API 지연 시 다음 조작으로 진행 상태를 확인하고 나서 다음으로
🗄 데이터 경합 병렬 실행 중에 같은 데이터를 복수 테스트가 덮어씀 테스트마다 독립된 데이터 사용

❌ 플레이키가 되는 작성법

import time
time.sleep(3)  # 고정 대기는 NG
driver.find_element(
    By.ID, "submit").click()

✅ 안정되는 작성법(explicit wait)

# explicit wait(권장)
wait = WebDriverWait(driver, 10)
btn = wait.until(
    EC.element_to_be_clickable(
        (By.ID, "submit")))
btn.click()

💡 실무 Tip: Playwright는 기본으로 Auto-wait가 유효하므로 Selenium보다 압도적으로 플레이키 테스트가 줄어듭니다. 신규 프로젝트에서 플레이키 문제로 고생하고 싶지 않은 경우는 Playwright의 채택을 강력히 권장합니다.

5가지 설계 원칙:일람 정리

# 원칙 포인트 효과
테스트 피라미드 단위 70% / API 20% / E2E 10% 실행 속도 최적화·비용 절감
POM 조작 로직과 테스트 로직을 분리 보수성·재사용성의 향상
DRY·재사용 공통 처리를 헬퍼 함수에 모은다 변경 비용 절감·가독성 향상
데이터 분리 fixture·mock·test DB를 사용 구분 테스트의 독립성·안정성 확보
플레이키 대책 explicit wait·독립·데이터 분리 테스트 결과에 대한 신뢰성 회복

정리

이 글에서는 테스트 자동화에서 실패하지 않기 위한 5가지 설계 원칙을 해설했습니다.

📋 이 글의 정리

  • 실패의 대부분은 도구가 아니라 설계·운용의 문제
  • ① 테스트 피라미드:단위 70% / API 20% / E2E 10%의 밸런스를 지킨다
  • ② POM:조작 로직을 분리하여 보수성·재사용성을 높인다
  • ③ DRY:공통 처리를 헬퍼 함수에 모아 코드 중복을 없앤다
  • ④ 테스트 데이터 분리:fixture·mock·test DB로 데이터를 코드에서 분리한다
  • ⑤ 플레이키 대책:explicit wait·테스트 독립·데이터 분리로 테스트 결과를 안정시킨다

「완벽한 자동화」를 목표로 할 필요는 없습니다. 우선 1가지 원칙부터 도입해 보세요. 그것만으로도 테스트의 수명은 확실히 늘어납니다.

다음 글에서는 실제로 Selenium과 Python을 사용하여 테스트 자동화를 구현하는 방법을 해설합니다. 먼저 Selenium으로 링크 끊김을 자동 감지하는 방법 부터 읽어보세요👇

タイトルとURLをコピーしました