개념
자동화가 죽는 방식은 거의 같다
버려진 자동화 저장소를 열어보면 패턴이 반복된다. 테스트 파일 하나하나가 혼자서 모든
일을 한다 — 로그인하고, 셀렉터로 요소를 찾고, API 를 직접 호출해 데이터를 만들고,
환경별 URL 을 if 로 분기한다. 처음 20개까지는 아무 문제가 없다(모듈 11 레슨 6).
문제는 그다음이다. 로그인 버튼 하나가 바뀌면 47개 파일을 고쳐야 하고, 새로 온 사람은
테스트를 읽어도 무엇을 검증하는 테스트인지 알 수 없다.
gTAA 란 무엇인가
ISTQB CT-TAE(Certified Tester Test Automation Engineering, v2.0 · 2024) 실러버스 3.1.1 은 gTAA 를 이렇게 규정한다 — 테스트 자동화와 그것이 연결되는 시스템들 사이의 소통을 추상적으로 보여주는 고수준 설계 개념. 연결 대상은 SUT(시험 대상 시스템), 프로젝트 관리, 테스트 관리, 형상 관리 네 가지다.
즉 gTAA 는 "Playwright 를 이렇게 쓰라"는 가이드가 아니다. 자동화 아키텍처(TAA)를 설계할 때 빠뜨리면 안 되는 역량의 목록 이라서, 도구를 바꿔도 설계 판단은 남는다.
인터페이스 네 개
| 인터페이스 | 기술하는 것 |
|---|---|
| SUT 인터페이스 | SUT 와 TAF 사이의 연결 |
| 프로젝트 관리 인터페이스 | 자동화 개발의 진척 |
| 테스트 관리 인터페이스 | 테스트 케이스 정의와 자동화된 테스트의 매핑 |
| 형상 관리 인터페이스 | CI/CD 파이프라인, 환경, 테스트웨어 |
셋째 줄이 현장에서 가장 자주 빠진다. 테스트 관리 도구의 TC-1047 과 저장소의
order-cancel.spec.ts 가 아무 연결 없이 따로 살면, "자동화 커버리지가 얼마인가"에
아무도 답할 수 없다.
네 가지 핵심 역량
실러버스는 자동화 도구·라이브러리가 제공하는 핵심 역량(core capabilities) 네 개를 든다 — 흔히 "gTAA 의 네 계층"으로 불리는 그것이다.
| 역량 | 책임 | 비고 |
|---|---|---|
| 테스트 생성(test generation) | 테스트 모델에 기반한 테스트 케이스의 자동 설계 지원 | 실러버스가 선택적(optional) 역량 으로 명시 |
| 테스트 정의(test definition) | 테스트 케이스·테스트 스위트의 정의와 구현. 테스트 정의를 SUT·도구와 분리 한다 | 테스트 데이터·테스트 케이스·테스트 라이브러리 컴포넌트로 다룬다 |
| 테스트 실행(test execution) | 테스트 실행과 테스트 로깅 지원 | 실행 도구 + 로깅·리포팅 컴포넌트 |
| 테스트 적응(test adaptation) | SUT 의 여러 컴포넌트·인터페이스에 자동 테스트를 적응 시키는 기능 | API·프로토콜·서비스로 연결하는 어댑터들 |
세 가지를 짚어둘 만하다. 테스트 생성은 선택이다 — 모델 기반 테스팅 도구(모듈 54)를
쓰지 않는 팀이라면 사람이 손으로 채우고, 그게 결함이 아니다. 정의와 실행은 다른
층이다 — 단언을 쓰는 일과 몇 개 병렬로 돌릴지 정하는 일은 다른 관심사다. 그리고
적응 계층이 가장 자주 없다 — 테스트가 page.click() 을 직접 부르면 적응 계층이
테스트 파일에 녹아 있는 것이다. 모듈 13 레슨 4의 페이지 객체가 이 계층을 제자리로
돌려놓는 가장 기본적인 장치다.
TAS · TAA · TAF — 세 단어를 구분해 써야 한다
실러버스 3.1.2~3.1.3 이 쓰는 세 용어를 섞어 쓰면 설계 논의가 꼬인다.
- TAS(Test Automation Solution) — 실제로 구축해 운영하는 해결책 전체. SUT 의 기능·비기능·기술 요구를 이해한 데서 정의되고, 상용 또는 오픈소스 도구로 구현되며, SUT 전용 어댑터가 추가로 필요할 수 있다.
- TAA(Test Automation Architecture) — TAS 전체의 기술 설계. 실러버스가 TAA 가 다뤄야 한다고 든 항목이 그대로 설계 체크리스트가 된다 — 도구·라이브러리 선정, 플러그인·컴포넌트 개발, 연결성·인터페이스 요구(방화벽·DB·URL·목·스텁·메시지 큐· 프로토콜), 테스트·결함 관리 도구 연결, 버전 관리 시스템과 저장소 활용.
- TAF(Test Automation Framework) — TAS 의 토대. 테스트 하네스(= 테스트 러너), 테스트 라이브러리, 테스트 스크립트, 테스트 스위트를 포함한다.
실무 번역: "우리 자동화"라고 말할 때 사람들은 TAF 를 떠올리지만, 실패하는 자동화는 대개 TAA 에서 빠진 항목 때문에 죽는다 — 방화벽이 안 열려서, 결함 관리 연결이 없어서, 테스트 데이터 경로가 정해지지 않아서.
TAF 계층 — 세 층과 호출 방향
실러버스 3.1.3 은 TAF 계층을 세 층으로 예시하고, 호출 방향에 명확한 금지선 을 둔다.
테스트 스크립트 (Test scripts)
SUT 의 테스트 케이스 저장소 + 스위트 주석
비즈니스 로직 계층의 서비스를 호출한다
✗ 코어 라이브러리를 직접 호출하지 않는다
│ 테스트 단계 · 사용자 흐름 · API 호출
▼
비즈니스 로직 (Business logic)
SUT 에 의존하는 라이브러리가 모두 여기 산다
코어 라이브러리를 상속하거나 그 파사드를 쓴다
│
▼
코어 라이브러리 (Core libraries)
어떤 SUT 에도 의존하지 않는 것만
같은 개발 스택을 쓰는 어떤 프로젝트에서도 재사용테스트 스크립트에서 코어 라이브러리로의 직접 호출을 하지 말라 는 것이 실러버스가 명시한 규칙이다.
그리고 실러버스는 계층을 많이 만들지 말라 고 권고한다 — 목적마다 계층을 하나씩 도입하면 설계가 복잡해질 수 있으니 TAF 계층 수를 낮게 유지하라는 것. 과잉 추상화에 대한 가장 강한 방어 근거다(레슨 2에서 다시 쓴다). 확장 모델도 함께 제시된다 — 여러 TAF 가 하나의 코어 위에 서고 다른 프로젝트가 그 코어를 가져다 쓴다. 즉 코어의 재사용 단위는 프로젝트가 아니라 개발 스택 이다.
계층을 섞으면 생기는 일 — 셀렉터로 보는 비용
추상적인 얘기를 숫자로 바꿔 보자. 셀렉터가 테스트 파일에 흩어진 저장소에서 무엇이 깨지는가.
// ✗ 계층 혼합 — 적응(셀렉터)·정의(검증)·실행(환경)이 한 파일에 섞였다
import { test, expect } from '@playwright/test';
test('주문 취소', async ({ page }) => {
const base = process.env.ENV === 'stage'
? 'https://stage.shop.example'
: 'https://dev.shop.example'; // 실행 관심사
await page.goto(`${base}/login`);
await page.fill('#login-form > div:nth-child(2) > input', 'qa@example.com');
await page.fill('input[name="passwd"]', process.env.QA_PW!); // 적응 관심사
await page.click('.btn.btn-primary.submit');
await page.goto(`${base}/orders`);
await page.click('table tbody tr:first-child .cancel');
await expect(page.locator('.toast-success')).toContainText('취소'); // 정의 관심사
});// ○ 테스트 스크립트는 "무엇을 검증하는가"만 말한다
import { test, expect } from '../fixtures/shop-fixtures';
test('배송 전 주문은 취소할 수 있다', async ({ shopper, orderList }) => {
const order = await shopper.givenPaidOrder({ sku: 'SKU-1001' });
await orderList.open();
await orderList.cancel(order.orderNo);
await expect(orderList.toast).toContainText('취소되었습니다');
});이 구조가 만드는 변경 비용은 여러 갈래로 퍼진다.
| 바뀌는 것 | 흩어진 구조에서 | 계층이 분리된 구조에서 |
|---|---|---|
| 로그인 버튼 클래스명 | 로그인하는 모든 테스트 (보통 전부) | LoginPage 한 곳 |
| 주문 목록 테이블 구조 | 주문을 만지는 모든 테스트 | OrderListPage 한 곳 |
환경 추가(qa2) | if 분기가 있는 모든 파일 | 설정 파일 한 곳 |
더 나쁜 건 고쳐야 할 파일을 찾는 비용 이다. .btn-primary 는 전수 검색이
안 된다 — 로그인 버튼인지 저장 버튼인지 구분이 안 되니까. 결국 전부 열어 본다.
그리고 읽히지 않는다 — 첫 예제 15줄 중 검증은 마지막 한 줄이다. 두 번째 예제에는
셀렉터가 한 줄도, URL 도, 환경 분기도 없다. 그게 핵심이다 — 테스트 스크립트는
비즈니스 로직 계층의 서비스를 호출 할 뿐이다.
공통 컴포넌트의 경계 — 무엇이 어느 층에 사는가
판단 기준은 하나다 — 이 코드가 다른 제품의 자동화에서도 글자 하나 안 바꾸고 쓰일 수 있는가? 그렇다면 코어 라이브러리, 아니라면 비즈니스 로직이다.
| 코드 | 어느 층 | 이유 |
|---|---|---|
| 재시도 가능한 HTTP 클라이언트 래퍼 | 코어 | 어떤 SUT 든 동일 |
| 상관 ID 생성·주입, 구조화 로거, 아티팩트 경로 규칙 | 코어 | SUT 무관 |
LoginPage, OrderListPage | 비즈니스 로직 | 이 제품의 화면 |
givenPaidOrder() 시드 함수 | 비즈니스 로직 | 이 제품의 API |
expectOrderCancelled() 복합 단언 | 비즈니스 로직 | 이 제품의 도메인 |
order-cancel.spec.ts | 테스트 스크립트 | 케이스 그 자체 |
경계를 잘못 그었을 때의 증상은 역방향 의존 으로 나타난다. 코어가 OrderStatus
같은 도메인 타입을 import 하기 시작하면 그 코드는 이미 코어가 아니다. tsconfig.json
의 paths 로 @core/*·@domain/* 별칭을 잡고 린트로 방향을 고정하면 기계적으로
막을 수 있다.
// eslint.config.js — 의존 방향을 린트로 고정한다
export default [
{
files: ['src/core/**/*.ts'],
rules: {
'no-restricted-imports': ['error', {
patterns: [
{ group: ['@domain/*', '../domain/*', '**/domain/**'], message: '코어는 도메인에 의존하면 안 된다' },
],
}],
},
},
{
files: ['tests/**/*.spec.ts'],
rules: {
'no-restricted-imports': ['error', {
patterns: [{ group: ['@core/*'], message: '테스트는 비즈니스 로직 계층을 통해 코어를 쓴다' }],
}],
},
},
];문서로만 적어둔 계층 규칙은 무너진다. CI 가 거절하는 규칙만 살아남는다.
gTAA 를 Playwright 저장소에 겹쳐 보기
| gTAA 역량 | 이 저장소에서 해당하는 것 |
|---|---|
| 테스트 생성 | (없음 — 선택적 역량. 케이스는 사람이 설계) |
| 테스트 정의 | tests/**.spec.ts, src/domain/assertions/, src/domain/data/ |
| 테스트 실행 | playwright.config.ts(프로젝트·병렬·재시도·리포터), CI 워크플로 |
| 테스트 적응 | src/domain/pages/, src/domain/api/, src/core/http/ |
여기서 테스트 실행 역량의 상당 부분은 모듈 14(CI/CD)와 모듈 59(TestOps)가 맡는다. 이 모듈은 저장소 안의 구조 — 정의와 적응 — 에 집중한다.
실무에서 왜 필요한가
구조 설계는 "있으면 좋은 것"이 아니라 자동화의 수명을 결정하는 변수 다.
첫째, 변경 비용이 선형이 아니다. 셀렉터가 흩어진 저장소는 테스트 수에 비례해 변경 비용이 커진다 — 테스트 200개면 UI 리뉴얼 한 번에 200개를 본다. 계층이 분리돼 있으면 화면 수에 비례한다. 이 차이가 "유지할 사람 0.5명"과 "2명"을 가른다.
둘째, 인수인계가 가능해진다. 만든 사람이 떠난 뒤에도 돌아가는 저장소는 테스트
파일을 열었을 때 도메인 언어만 보인다. givenPaidOrder() 는 QA 신입도 읽지만,
깊은 CSS 경로가 박힌 page.fill(...) 은 만든 사람도 3개월 뒤엔 못 읽는다.
셋째, 공통 어휘가 설계 리뷰 시간을 줄인다. "이건 적응 계층에 있어야 하는 로직이 정의 계층에 올라와 있다"는 한 문장이 20분짜리 애매한 대화를 대체한다. 같은 이유로 아키텍처는 협상 도구가 된다 — 레슨 4에서 개발팀에 "셀렉터가 자꾸 깨져서요"라고 말하는 것과 "적응 계층이 DOM 구조에 결합돼 스타일 변경이 테스트 실패로 번집니다"라고 말하는 것은 다른 결과를 낳는다.
채용시장에서의 위치
자동화 아키텍처 설계 경험은 자동화 엔지니어와 SDET·자동화 리드 를 가르는 선이다. 공고의 "테스트 프레임워크 설계 및 개선 경험" 문구가 가리키는 게 이 능력이고, 면접에서 "기존 자동화가 매일 깨지는 상황에 들어갔다면 첫 2주에 무엇을 보겠습니까"라는 질문의 모범 답안은 도구가 아니라 계층 진단이다 — 셀렉터가 어디 사는지, 환경 설정이 몇 곳에 흩어져 있는지, 테스트 파일에서 도메인 언어가 보이는지. ISTQB CT-TAE v2.0 실러버스 3장이 이 레슨의 원전이며, 자격증을 따지 않더라도 네 용어를 정확히 구분해 쓰는 것만으로 설계 논의에서 신뢰를 얻는다(모듈 42 레슨 6의 자격증 지도 참고).