뭔가를 하게 될 때 효율적인 방식을 찾아서 작업을 하시나요?
저는 새로운 게임을 하게 될 때 맨땅에서 자유롭게 하는 것도 좋지만, 먼저 해본 사람들의 공략과 꿀팁을 참고해서 게임을 제 방식대로 즐겨보는 편입니다.

그래서 동물의 숲 게임을 할 때 건물 꾸미기보단, 성장에 도움 되는 작물 대량으로 심기, 무트코인으로 돈 벌기 등등 실용적인 것을 우선적으로 하고 능력이나 자금에 여유가 생긴 뒤 자유로운 컨텐츠를 즐기는 방식으로 진행했습니다
이런 방식처럼 각자의 목표를 달성하기 위한 전략은 다양할 수 있습니다.
이렇게 인트로를 잡아본 이유는 최근 여러 회사에서 자동화 환경을 구축하려는 회사도 많고, 경험이 있는 사람을 많이 뽑는 것 같습니다.
그래서 이번 포스팅에서는 자동화 구성하던 중 제가 경험했던 고민을 하나씩 해결했던 전략을 소개해보려고 합니다 :)
우리 제품은 어떤 전략을 사용해야 할까?
우선 제품이 어떻게 돌아가는지 파악을 해야겠죠?
B2B, B2C, SaaS 등 회사 제품은 고객에게 어떻게 서비스를 제공하고 있고, 어떤 식의 자동화를 구성해야 할지 생각해봐야 합니다.
제가 작업했던 제품은 B2B 제품이었고, 목표는 매뉴얼 테스트 케이스를 자동화하는 것.
기술 조사부터 어떤 환경으로 제공할지 고민하게 되었습니다.

보통 새로운 시스템을 구성할 때 위 이미지처럼 아키텍처 구성하고 로드맵을 구성한 뒤 작업을 진행합니다.
어떤 기술로, 어떻게 제공할지는 이전 포스팅에 다뤘던 것처럼 AI를 활용해서 계획하고 환경을 구성하였습니다.
그럼 이제 어떻게 자동화 시스템을 구축할지 고민이었습니다.
자동화 동작 방식 분석
앞서 설명드린 것처럼, 매뉴얼 테스트 케이스를 자동화해야 하는 목표를 가지고 있었습니다.
하지만 그 방식에서 고민했던 부분... 매뉴얼 TC와 자동화 스크립트를 동일 개수로 만든다면 커버리지는 확실하겠지만, 시간이 너무 오래 걸리지 않을까?라는 생각이 들었습니다.
API 테스트는 Mock 기능을 활용해서 앞선 데이터나 환경을 구성되어 있다 치고 테스트를 수행할 수 있지만, E2E 테스트에서는 어렵다고 판단했습니다.
그럼 하나의 사용자 시나리오 형식의 테스트를 구성하되, 시나리오 과정에서 여러 가지 매뉴얼 테스트 케이스를 포함시키자는 전략을 구성하게 되었습니다.
그렇게 전략을 구성하고, 필요했던 방식은 원할 때 바로 조립해서 쓸 수 있는 유연한 스크립트라고 생각했습니다.
레고를 조립하는 듯 한 자동화 스크립트 구성
자동화 모델링은 POM 구조로 구축했고, 재사용성을 높이기 위해 페이지의 기능을 하나의 레고 블록으로 만드는 작업을 진행하게 되었습니다.

/**
* 로그인 함수
* @param {string} id - 사용자 ID
* @param {string} pw - 사용자 비밀번호
*/
async function login(id, pw) {
// ID 입력
await this.page.fill('#username', id);
// 비밀번호 입력
await this.page.fill('#password', pw);
// 로그인 버튼 클릭
await this.page.click('button[type="submit"]');
}
간단한 예시를 든다면, 로그인을 진행해야 기능 테스트를 할 수 있기 때문에, 로그인 기능은 반복적으로 수행될 것이고, 하나의 로그인 블록으로 만들어서 로그인이 필요할 때는 이 함수만 호출해서 사용하기.
이렇게 공통으로 사용할 수 있는 기능들을 묶어둔 다면 검증할 때 아래 코드처럼 사용할 수 있겠죠
test('잘못된 비밀번호로 로그인 시 에러 메시지 확인', async ({ page }) => {
await loginPage.login('testuser@example.com', 'wrongpassword');
// 에러 메시지 내용 확인
const errorMessage = await loginPage.getErrorMessage();
expect(errorMessage).toContain('로그인 정보가 다릅니다');
});
test('정상 로그인 성공', async ({ page }) => {
await loginPage.login('testuser@example.com', 'password123');
// 대시보드로 이동했는지 확인
await expect(page).toHaveURL(/dashboard/);
});
코드는 간단한 예시였지만, 이런 방식으로 레고 블록을 하나씩 만들어가는 작업을 진행했습니다.
그래서 다양한 시나리오에서 코드 블럭을 하나씩 가져와서 조립할 수 있는 환경을 구성했고, 시나리오를 동작하는 도중 expect 기능을 사용해서 동작에 이상이 없는지 검증하는 기능을 추가했습니다.

그렇게 만들어 둔 레고 블록들을 모아서 완성된 하나의 시나리오로 구성할 수 있었습니다.
그럼 또 새로운 문제에 직면하게 되었습니다.
사용자 생성 후 로그아웃하는 시나리오가 있는데, 사용자 생성은 했고 로그아웃에 문제가 있어서 실패했다.
로그아웃 기능을 고치고, 다시 시나리오를 실행하면 이미 만들어둔 사용자가 있기에 시나리오는 실패한다.
테스트 중간에 테스트가 실패하면 쌓인 데이터는 어쩌나
이 과정도 고민을 많이 하게 된 부분이었습니다.
자동화를 만든 사람이면 데이터가 쌓인 이슈로 실패했구나 알 수 있지만, 자동화 환경을 사용하는 입장에선 '뭐야 스크립트 다시 돌렸는데 안된다. 자동화 환경 안되는데?'라고 하며 제대로 한거 맞냐고 구박을 줄 수 있었기 때문에...
어떤 회사에서는 API로 데이터를 싹 밀고 다시 시작하는 곳도 있었지만, 저는 아직 API와 하이브리드로 동작되는 자동화를 만들진 못했거든요..
'데이터 생성 -> 테스트 수행 -> 데이터 삭제' 방식의 시나리오라면
'데이터 유무 확인 -> 있다면 삭제 없으면 진행 -> 데이터 생성 -> 테스트 수행 -> 데이터 삭제' 방식처럼 동작 할 때 데이터가 쌓여 있는지 확인해야 하는 기능을 추가하게 되었습니다.
모든 기능을 수행할 때마다 데이터를 확인하는 방식보단, 시나리오 동작 중 데이터 의존도가 있는 로직에 추가했습니다.
예를 들면, 사용자 생성, 설문 생성과 같이 테스트할 데이터가 필요하다면 검증 후 초기화 단계를 추가했고, 시나리오 자체에서 저장된 데이터와 관련 없이 새로 만드는 기능(새로운 대화 생성, 채팅, 첨부파일 전송 등)에는 시나리오가 도중에 멈춰도 다시 수행할 때 새로운 데이터를 생성하기 때문에, 빠른 수행을 위해 데이터 검증을 최소화하는 방식으로 구성했습니다.
그럼 이제 얼마나 빨리 테스트해 줄 수 있는데...? 너 자동이잖아. 빨리 해줘. 의견을 해결하기 위한 고민을 시작했습니다.
속도를 위한 병렬 테스트 환경
병렬로 동시에 돌아가도 서로 영향이 없는 부분은 어디부터 가능할지 생각해 봤습니다.
서버가 많아서 각각 환경에서 돌릴 수 있다면 좋겠지만, 하나의 서버에서 여러 개의 시나리오를 돌려야 하는 것이 일반적이니까요.
우리 제품에는 구분할 수 있는 기준이 있었습니다.
'센터'라는 기능이 있어서, 설문, 자동 응답 기능 등 센터 별 설정을 구분할 수 있는 요소가 있었고 이를 적극 활용했습니다.
그리고, 모든 기능이 센터로 구분되지는 않기에 '센터' 기능으로 구분할 수 있는 시나리오와 전체 공통 시스템 영역을 테스트하는 시나리오로 구분하였습니다.

제품 전체에 영향을 주는 기능 테스트 시나리오는 별도로 구성해서 단일 Worker(일하는 사람 수) 시스템으로 돌아가도록 구성했고, TC 대부분은 센터 내에서 동작하는 기능 검증이었기 때문에 시나리오만 구분해서 동시에 Worker 5개까지 수행해도 영향이 없는 환경을 구성했습니다.
이제 사용자 시나리오를 동시에 만드는 환경은 구성했고... 시나리오 동작 중 실패한 부분을 빠르게 확인할 수 있는 방법은 뭐가 있을까를 생각하게 되었습니다.
긴 시나리오가 실패했을 때 확인은 어떻게 할까
사실 이 부분은 간단하게 해결했습니다.
사용자 시나리오에서 새로운 기능이 동작하는 것을 구분하고 step으로 구분해 두는 것
test('로그인 → 사용자 생성 → 로그아웃 시나리오', async ({ page }) => {
// Step 1: 로그인
await test.step('로그인', async () => {
await page.goto('https://example.com/login');
await page.fill('#username', 'testuser@example.com');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
// 로그인 성공 확인
await expect(page).toHaveURL(/dashboard/);
console.log('✓ 로그인 성공');
});
// Step 2: 사용자 설정 페이지 이동
await test.step('사용자 설정 페이지 이동', async () => {
await page.click('a[href="/settings/users"]'); // 사용자 설정 메뉴 클릭
// 사용자 설정 페이지 로드 확인
await expect(page).toHaveURL(/settings\/users/);
await expect(page.locator('h1')).toContainText('사용자 관리');
console.log('✓ 사용자 설정 페이지 이동 완료');
});
// Step 3: 사용자 생성 후 정상 생성 확인
await test.step('사용자 생성 및 확인', async () => {
// 사용자 추가 버튼 클릭
await page.click('button:has-text("사용자 추가")');
// 사용자 정보 입력
await page.fill('#new-user-name', '테스트유저');
await page.fill('#new-user-email', 'newuser@example.com');
await page.fill('#new-user-role', 'admin');
// 생성 버튼 클릭
await page.click('button:has-text("생성")');
// 생성 완료 확인
await expect(page.locator('.success-message')).toContainText('사용자가 생성되었습니다');
// 사용자 목록에서 생성된 사용자 확인
await expect(page.locator('table tbody tr').filter({ hasText: 'newuser@example.com' })).toBeVisible();
console.log('✓ 사용자 생성 완료');
});
// Step 4: 사용자 정보 버튼 클릭해서 로그아웃
await test.step('로그아웃', async () => {
// 사용자 정보 버튼 클릭
await page.click('.user-info-button'); // 또는 'button[aria-label="사용자 정보"]'
// 드롭다운 메뉴가 나타날 때까지 대기
await expect(page.locator('.user-dropdown-menu')).toBeVisible();
// 로그아웃 버튼 클릭
await page.click('button:has-text("로그아웃")');
// 로그인 페이지로 리다이렉트 확인
await expect(page).toHaveURL(/login/);
console.log('✓ 로그아웃 완료');
});
});
위 코드는 Claude로 생성된 코드이고, 어떤 식으로 구성했는지를 작성해 보기 위해 추가했습니다.
Test Step으로 구분해 두면, 결과 리포트에서 어떤 Step에서 실패했는지 바로 확인할 수 있었고, 디버깅하는 시간을 조금이라도 줄일 수 있는 코딩 방식을 사용했습니다.
아직 부족하다. 그래도 조금 더 빨라지려면...!!!!
테스트 데이터를 사전에 만들어둔다면 더 빨라질까
병렬 시스템으로 돌아가는 스크립트에서, 각자 필요한 데이터를 꼭 만드는 동작이 스크립트 돌아갈 때마다 있어야 할까?
이런 생각을 하게 된 이유는, 동시에 돌아가는 만큼 각 시나리오 별 로그인하는 계정이 다 달랐습니다.
그럼 이 계정과 시나리오 별 센터가 필요했는데, 이 작업이 시나리오를 수행할 때마다 필요한 동작인 것인가를 고민하게 되었고, 사전 데이터를 생성하는 initdata 스크립트를 만들어서 시나리오의 환경 세팅은 한 번만 하고, 시나리오는 테스트를 수행하는 것에 집중하자는 전략을 구성하게 되었습니다.
그래서 병렬 시나리오가 돌아가기 전 initdata 생성 시나리오를 수행한 뒤 시나리오가 돌아가도록 했으며, 한 번이라도 initdata를 생성한 적이 있다면, initdata 시나리오를 수행하지 않고 바로 테스트하는 방식으로 구성했습니다.
initdata 자체도 단순 데이터 생성이라면 API로 생성하는 게 더 빠르지 않나?라는 생각도 있었지만...
자동화로 데이터를 생성하는 중에도 Flaky Test나 스크립트 오류를 확인할 수도 있다고 판단하였고, 아직 자동화의 안정성을 더 검증해야 하며, 3분 안에 완료되는 스크립트였기에, API로 생성하지 않았습니다. (나중에는 변경했을 수도...)
매뉴얼 테스트를 지원하면서 자동화를 만든다면...
이 부분은 전략과는 약간 다른 내용이지만 자동화를 조금이라도 활용했던 부분이 있어서 공유드립니다.
매뉴얼 테스트를 진행하던 도중, 자동화 프로젝트가 생기면서 매뉴얼 테스트를 다른 분이 담당해줘야 했습니다.
업무를 이관한 만큼, 제가 빨리 작업해서 매뉴얼 테스트의 부담을 줄여줬어야 했는데, 자동화 시스템을 완성하고 전달하는 것보단, 활용할 수 있는 기능이 있다면 빨리 제공해서 작고 소중한 도움을 주는 것이 어떨까라고 생각했습니다.

누군가에게는 작은 도움이지만 큰 영향을 줄 수도 있지 않을까?라는 생각으로 시작하게 되었습니다.
그래서 매뉴얼 테스트 중 설문 데이터 생성이나 상담에 필요한 사전 데이터를 직접 생성한 뒤 기능을 테스트하는 기능들이 많았는데, 이 부분을 우선적으로 자동화해서 매뉴얼 테스트에서는 만들어진 데이터로 테스트에 집중할 수 있게 지원했습니다.
누군가에게는 가벼워 보이는 기능이지만, 테스트 서버에 기껏 쌓아두었던 테스트 데이터가 DB 초기화해야 하는 이슈로 다 삭제되어 다시 데이터를 생성해야 하는 스트레스를 덜어주는 역할이었다고 생각됩니다
테스트 환경 구성에 대한 얘기는 몇 번 다룬 적이 있지만, 구축 과정에서 했던 고민을 적어본 적은 없는 것 같아서 작성하게 되었습니다.
포스팅을 쓰다 보니 내가 이렇게 일했었지, 이때 이렇게 설계했었구나 하며 다시 떠올리는 계기가 된 것 같네요.
제 업무 정리 겸 작성해 봤지만, 자동화를 새로 구축하는 분들에게 조금이라도 도움이 될 수 있을까 봐 포스팅으로 작성했습니다.
저도 혼자 고민하면서 작업한 내용이라 더 좋은 방식은 많을 것이고, 적용할 수 있는 부분이 없을 수 있습니다.
이 사람은 이렇게 작업했구나 라고 참고하는 용으로 읽어주세요! 긴 글 읽어주셔서 감사합니다 :)
'QA > 기능 자동화' 카테고리의 다른 글
| ROI가 좋은 자동화 테스트 환경을 구성하기 (5) | 2025.11.18 |
|---|---|
| 테스트 자동화에서 개발 협업 요청은? (5) | 2025.08.08 |
| IntelliJ(JetBrain) IDE의 AI 무료 환경 제공 (1) | 2025.05.07 |
| 기능 자동화 기술조사부터 환경 구성까지 (6) | 2024.11.04 |
| 부족한 식별자로 신뢰도 있는 자동화 스크립트 구성하기 (1) | 2024.09.08 |