News

E2E 테스트 코드 중복 생겼다면? — Opa5 Page Object 3단계 #shorts #SAP #UI5

▶ YouTube에서 보기

개요 (이 글에서 다룰 것)

SAPUI5 / OpenUI5 애플리케이션의 End-to-End(E2E) 테스트는 OPA5(One Page Acceptance Tests for UI5)로 작성하는 것이 일반적입니다. 그러나 화면 조작 코드를 테스트마다 반복해서 작성하면 유지보수가 어렵고, UI가 변경될 때 모든 테스트를 수정해야 하는 문제가 발생합니다. 이 글에서는 Page Object 패턴을 이용해 화면별 조작 로직을 클래스로 캡슐화하고 재사용하는 방법을 다룹니다.

  • Opa5.extend로 Page Object 클래스 생성 방법 익히기
  • actions와 assertions 메서드를 분리해서 정의하기
  • opaTest 안에서 Given/When/Then 구문으로 호출하기
  • 실무 시나리오에서 에러 핸들링과 안정적인 대기(waitFor) 적용하기
  • 프로덕션 환경에서 테스트 성능과 안정성 확보 방안 살펴보기

이 글을 보기 전에

UI5의 MVC 구조와 컨트롤 ID/바인딩에 대한 기본 이해가 필요합니다. QUnit 단위 테스트 작성 경험과 sap.ui.test 네임스페이스의 기본 객체(Opa5, Press, EnterText 등)에 대한 사전 지식이 있으면 더 빠르게 따라올 수 있습니다. Promise 기반의 비동기 처리 흐름도 알고 있으면 좋습니다.

환경 / 버전 / 준비물

  • SAPUI5 / OpenUI5: 1.96 LTS 이상 권장 (이 글은 1.120 기준으로 작성)
  • 테스트 러너: QUnit 2.x + sap.ui.test.opaQunit
  • 개발 환경: SAP Business Application Studio 또는 VS Code + ui5-cli
  • 패키지: @ui5/cli, karma-ui5 (CI 환경에서 사용 권장)
  • 브라우저: Chromium 기반 (headless 모드 지원)

프로젝트 구조는 webapp/test/integration/pages/ 폴더에 Page Object를 두고, webapp/test/integration/<Feature>Journey.js에 시나리오를 작성하는 구조를 일반적으로 사용합니다. opaTests.qunit.html 또는 opaTests.qunit.js가 테스트 진입점이 됩니다.

핵심 개념

OPA5는 UI5 애플리케이션의 비동기 동작을 안전하게 검증하기 위해 만들어진 통합 테스트 프레임워크입니다. 핵심 아이디어는 "컨트롤이 준비될 때까지 기다렸다가 동작과 검증을 수행한다"는 것이며, 이를 waitFor가 담당합니다.

Page Object 패턴은 일반적인 Selenium/Cypress 세계에서도 널리 쓰이는 디자인 패턴입니다. 비유하자면 리모컨과 같습니다. TV의 내부 회로(=UI 구조)가 바뀌더라도 리모컨 버튼(=Page Object 메서드)의 이름만 그대로 두면, 시청자(=테스트 코드)는 변경된 사실을 모른 채 그대로 사용할 수 있습니다.

화면 = Page Object, 사용자 동작 = actions, 화면 상태 확인 = assertions

OPA5의 BDD 스타일 구문에서는 세 가지 매개변수가 등장합니다.

  • Given: 앱 시작, 초기 데이터 준비 등 사전 조건 설정 — 주로 Arrangements에 정의
  • When: 사용자가 수행하는 동작 — actions에 매핑
  • Then: 결과 검증 — assertions에 매핑

Page Object를 Opa5.createPageObjects({ onTheListPage: { ... } })로 등록하면 Given.onTheListPage, When.onTheListPage, Then.onTheListPage 형태로 자동 노출됩니다. Opa5.extend를 사용하는 방식은 좀 더 클래스 친화적이고, 상속/확장이 자유롭다는 장점이 있습니다.

실전 코드 3단계

1단계 — Page Object 클래스 생성 (기본)

가장 먼저 빈 골격을 만듭니다. Opa5.extend는 OPA5 베이스 클래스를 상속한 새로운 클래스를 정의하는 메서드입니다. actions와 assertions를 빈 객체로 두어도 클래스 등록은 완료됩니다.

sap.ui.define([
  "sap/ui/test/Opa5"
], function (Opa5) {
  "use strict";

  Opa5.extend("myApp.test.pages.ListPage", {
    actions: {},
    assertions: {}
  });
});

그리고 테스트 진입점(opaTests.qunit.js)에서 이 모듈을 require 해주면 OPA5 컨텍스트에 Page Object가 등록됩니다. 이 단계까지는 아직 동작도 검증도 없지만, 네이밍 컨벤션을 미리 정해두는 것이 중요합니다. 예: 화면 단위(ListPage, DetailPage), 메서드는 iPress..., iSee...처럼 1인칭으로 시작.

2단계 — Action·Assertion 메서드 정의 (실무)

실제 동작을 채워봅니다. 버튼 클릭은 sap/ui/test/actions/Press를, 텍스트 입력은 EnterText를 사용합니다. waitFor는 컨트롤이 렌더링될 때까지 폴링하면서 기다리므로, 비동기 UI에서도 안전하게 동작합니다.

sap.ui.define([
  "sap/ui/test/Opa5",
  "sap/ui/test/actions/Press",
  "sap/ui/test/actions/EnterText"
], function (Opa5, Press, EnterText) {
  "use strict";

  var sViewName = "myApp.view.List";

  Opa5.extend("myApp.test.pages.ListPage", {
    actions: {
      iPressAddButton: function () {
        return this.waitFor({
          id: "addButton",
          viewName: sViewName,
          actions: new Press(),
          errorMessage: "Add 버튼을 찾지 못함"
        });
      },
      iEnterItemName: function (sName) {
        return this.waitFor({
          id: "nameInput",
          viewName: sViewName,
          actions: new EnterText({ text: sName, clearTextFirst: true }),
          errorMessage: "이름 입력 필드를 찾지 못함"
        });
      }
    },
    assertions: {
      iSeeListWithItems: function (iCount) {
        return this.waitFor({
          id: "itemList",
          viewName: sViewName,
          success: function (oList) {
            Opa5.assert.strictEqual(
              oList.getItems().length,
              iCount,
              "리스트에 " + iCount + "개의 아이템이 표시됨"
            );
          },
          errorMessage: "아이템 리스트를 찾지 못함"
        });
      }
    }
  });
});

핵심 포인트는 다음과 같습니다.

  • errorMessage: 실패 시 로그에 남는 메시지. 디버깅 시간을 크게 줄여줍니다.
  • viewName: 동일 ID가 여러 뷰에 존재할 수 있으므로 함께 지정하는 것이 권장됩니다.
  • actions/success: actions는 동작을 수행하고, success는 검증 콜백을 실행합니다.
  • return this.waitFor: 반드시 반환해야 체이닝이 동작합니다.

3단계 — 테스트에서 Page Object 호출 (프로덕션)

이제 실제 시나리오 테스트를 작성합니다. opaTest는 OPA5 전용 테스트 함수로, Given/When/Then 매개변수를 통해 Page Object 메서드에 접근합니다.

sap.ui.define([
  "sap/ui/test/opaQunit",
  "myApp/test/pages/ListPage",
  "myApp/test/arrangements/Startup"
], function (opaTest) {
  "use strict";

  QUnit.module("List Journey");

  opaTest("아이템 추가 플로우", function (Given, When, Then) {
    Given.iStartMyApp();

    When.onTheListPage
      .iEnterItemName("테스트 아이템")
      .and.iPressAddButton();

    Then.onTheListPage.iSeeListWithItems(1);

    Then.iTeardownMyApp();
  });
});

프로덕션 환경에서는 다음 사항을 추가로 고려합니다.

  • CI 통합: karma-ui5 + headless Chrome으로 파이프라인에서 자동 실행. 실패 시 스크린샷 저장 옵션 활성화.
  • 타임아웃 튜닝: Opa5.extendConfig({ timeout: 30, pollingInterval: 200 })로 환경별 조정.
  • Mock 서버: 실제 백엔드 대신 sap.ui.core.util.MockServer 또는 OData V4 Mock으로 데이터 격리.
  • Journey 분리: 기능 단위(ListJourney, DetailJourney)로 파일을 나누고, AllJourneys.js에서 통합.
  • 매처(Matcher) 활용: PropertyStrictEquals, BindingPath, I18NText로 ID에 의존하지 않는 견고한 선택자 작성.

흔한 실수 / 트러블슈팅

Page Object 패턴을 처음 도입할 때 자주 마주치는 문제들을 정리합니다.

Q1. waitFor가 타임아웃 되며 컨트롤을 찾지 못합니다.

대부분 ID 불일치 또는 viewName 누락이 원인입니다. 브라우저 콘솔에서 sap.ui.test.RecordReplay.findControlSelectorByDOMElement를 활용해 정확한 셀렉터를 추출해보세요. 또한 동적으로 생성되는 컨트롤(예: 다이얼로그)은 searchOpenDialogs: true 옵션이 필요합니다.

Q2. 테스트가 로컬에서는 통과하지만 CI에서는 실패합니다.

일반적으로 폴링 간격과 타임아웃이 부족한 경우입니다. CI 환경의 리소스가 제한적이면 컨트롤 렌더링이 느려질 수 있으므로, Opa5.extendConfig({ timeout: 60 })로 충분히 늘려보는 것이 권장됩니다. 또한 애니메이션이 활성화되어 있다면 sap.ui.getCore().getConfiguration().setAnimationMode("none")을 적용해 안정성을 높일 수 있습니다.

Q3. Page Object 메서드가 Given/When/Then 어디에 노출되는지 헷갈립니다.

Opa5.extend로 만든 Page Object는 actions/assertions 구분만 있을 뿐, 실제로는 Given/When/Then 모두에 동일한 메서드가 노출됩니다. 컨벤션상 When에서는 actions를, Then에서는 assertions만 호출하는 것이 가독성에 좋습니다. 강제 분리가 필요하면 Opa5.createPageObjects를 대신 사용하는 것을 고려할 수 있습니다.

Q4. 동일 화면에서 메서드가 너무 많아져 클래스가 비대해집니다.

화면 단위가 아닌 영역(Section) 단위로 Page Object를 분리하는 것을 권장합니다. 예: ListHeaderPage, ListFilterBarPage, ListTablePage. 또는 공통 동작을 BasePage로 추출하고 Opa5.extend로 상속받아 재사용할 수 있습니다.

다음 단계 / 관련 주제

Page Object 패턴에 익숙해졌다면 다음 주제를 이어서 학습하면 좋습니다.

  • OPA5 Matchers 심화: AggregationFilled, BindingPath, Interactable 매처로 견고한 선택자 만들기
  • SAP Fiori elements 테스트: sap.fe.test.JourneyRunner를 활용한 Fiori elements 앱의 표준화된 테스트
  • Visual Regression: wdi5(WebdriverIO + UI5)로 실제 브라우저 기반 E2E 및 스크린샷 비교
  • Code Coverage: karma-coverage로 OPA5 테스트의 커버리지 측정
  • CAP + UI5 통합 테스트: 백엔드 CAP 서비스와 UI5 앱을 함께 띄워 검증하는 풀스택 시나리오

핵심 한 줄

Page Object 패턴은 OPA5 테스트의 "리모컨"이다 — 화면이 바뀌어도 테스트 코드는 흔들리지 않게, actions와 assertions로 동작과 검증을 캡슐화하라.

참고 자료

댓글 0

아직 댓글이 없습니다.