Windows 데스크톱 앱의 UI 자동 테스트 ── UI Automation 구조와 FlaUI로 만드는 잘 깨지지 않는 테스트

· 업데이트: · · 테스트, UI Automation, FlaUI, WinForms, WPF, C#, .NET, Windows, CI/CD

수정 이력(7건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
Codex 리뷰에 따라 UI assertion·샘플의 일본어 문자열을 한국어로 맞췄습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
관련 기사 링크를 한국어 permalink에 맞추는 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 서두에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1,283건)에 대응하여 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
UIA 트리의 3가지 뷰의 포함 관계 그림과 읽는 방법을 추가했습니다. `inspect.exe`의 어느 창·어느 메뉴에 무엇이 나오는지 표와 조작 절차, 테스트 프로젝트를 만드는 절차(병렬 실행 중지 포함), GitHub Actions 셀프 호스트 러너의 최소 CI 설정을 추가했습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635356)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「Windows 데스크톱 앱의 UI 자동 테스트 ── UI Automation 구조와 FlaUI로 만드는 잘 깨지지 않는 테스트」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-desktop-ui-automation-testing/

DOI(등록된 아카이브)
10.5281/zenodo.21635356
DOI(마지막 등록 버전)
10.5281/zenodo.21635357

「릴리스할 때마다 모든 화면을 손으로 클릭해 도는 확인 작업에 꼬박 2일이 걸린다」「지난달에 고친 곳 옆에서 다른 화면이 깨져 있었고, 고객 현장에서 발견됐다」「Web 팀은 Selenium이나 Playwright로 회귀 테스트를 자동화하고 있는데, 데스크톱 앱은 손도 대지 못한 채다」. WinForms / WPF 업무 앱을 오래 유지보수하는 현장에서 이런 상담을 자주 받습니다.

한편으로, 의욕적으로 모든 화면의 테스트를 쓰기 시작했다가 유지보수에 버티지 못하고 1년 만에 포기했다는 실패담도 같은 정도로 듣습니다. UI 자동 테스트는 도구 선택과 「어디까지 할지」의 선을 잘못 그으면 수동 테스트보다 비용이 커집니다. 반대로 구조를 이해하고 잘 깨지지 않게 설계한 뒤, 범위를 스모크 테스트로 한정하면 「릴리스 전 2일」을 「매일 밤 무인으로 20분」으로 바꿀 수 있는 투자가 됩니다. 이 기사에서는 기반이 되는 UI Automation(UIA) 구조, 도구 현황(FlaUI / WinAppDriver / Appium), FlaUI 구현, 잘 깨지지 않게 하는 설계, CI에서의 무인 실행까지, 당사가 실제 프로젝트에서 쓰는 패턴을 한 차례 정리합니다.

1. 먼저 결론

한 줄로 말하면, 「AutomationId로 찾고, 컨트롤 패턴으로 조작하고, 범위는 스모크 테스트로 한정한다」입니다. 아래는 그 내용입니다.

  • UI 자동 테스트는 테스트 피라미드의 최상단입니다. 느리고, 잘 깨지며, 실패 원인 파악에 시간이 걸리므로 단위·통합 테스트의 대체물이 되지 않습니다. 로직은 아래 층에서 지키고, UI 테스트는 「기동해서 주요 경로가 통과한다」는 것을 확인하는 스모크 테스트로 한정합니다(6장).
  • 구조의 기반은 Windows UI Automation(UIA)입니다. 데스크톱을 뿌리로 하는 자동화 트리에서 요소를 찾고, AutomationId / Name 등의 속성으로 특정한 뒤, 컨트롤 패턴(Invoke / Value / SelectionItem 등)으로 조작합니다.12
  • 대상 앱의 요소가 어떻게 보이는지는 코드를 작성하기 전에 inspect.exe 또는 Accessibility Insights for Windows로 확인합니다. inspect는 Windows SDK에 포함된 레거시 도구이며, 현재는 Accessibility Insights가 공식 권장입니다.3
  • 도구의 당사 권장은 FlaUI + xUnit / NUnit입니다. FlaUI는 UIA를 얇게 감싼 MIT 라이선스 OSS로, UIA2 / UIA3 모두에 대응하며 지금도 활발히 유지보수되고 있습니다.4
  • 한때 Microsoft 공식 선택지였던 WinAppDriver는 최종 안정판이 2020년 11월 v1.2.1에서 멈춰 있고, 서버 본체는 비공개 소스라 커뮤니티 측에서 고칠 수도 없습니다. 신규 채택은 권하지 않습니다(3장).5
  • 테스트가 잘 깨지지 않는지는 80%가 개발 측에서 AutomationId를 부여하는 규약으로 정해집니다. WPF는 x:Name 또는 AutomationProperties.AutomationId, WinForms는 Name / AccessibleName입니다. 표시 문자열(Name)에 의존하는 검색, 좌표 클릭, Thread.Sleep은 금지하고, 조건 대기(Retry)와 Page Object 패턴으로 작성합니다(4~5장).67
  • CI에서의 무인 실행에는 대화형 데스크톱 세션이 필수입니다. 서비스로 구성한 에이전트에서는 동작하지 않으며, 화면 잠금이나 RDP 연결을 끊어도 실패합니다. 셀프 호스트 러너를 자동 로그온(autologon)으로 구성하는 것이 기본형입니다(7장).8

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 24건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 구조 ── UI Automation의 트리·속성·패턴

2.1 자동화 트리

UI 자동 테스트 도구는 화면을 이미지로 인식하는 것이 아닙니다. Windows에는 스크린 리더 등 보조 기술이 앱 UI를 프로그램에서 읽고 조작하기 위한 공식 기반 UI Automation(UIA)이 있으며, 테스트 자동화는 이 같은 기반을 그대로 이용합니다.1

UIA에서는 데스크톱을 뿌리(루트)로 하여, 열려 있는 모든 창과 그 안의 컨트롤이 트리 구조로 공개됩니다. 버튼도 텍스트 박스도 그리드의 행도, 모두 트리 위의 자동화 요소입니다. 트리에는 모든 요소를 포함하는 raw 뷰 외에, 조작 대상이 되는 컨트롤만으로 좁힌 컨트롤 뷰, 내용만의 콘텐츠 뷰라는 필터된 뷰가 있습니다.1 테스트 코드는 기본적으로 컨트롤 뷰를 순회하며 요소를 찾게 됩니다.

3가지 뷰는 병렬 선택지가 아니라, 같은 트리 하나에 거는 필터 강도의 차이입니다. 포함 관계는 중첩됩니다.

raw 뷰 ── 트리 위의 모든 요소control 뷰 ── 조작 대상이 될 수 있는 요소content 뷰 ── 이용자에게 정보를 전하는 요소버튼 SaveButton텍스트 박스 CustomerNameBox리스트 항목 테스트상사타이틀 바스크롤 바그룹 테두리장식용 구분선레이아웃만을 위한 중간 요소

읽는 방법은 「content 뷰에 있는 것은 control 뷰에도 있고, control 뷰에 있는 것은 raw 뷰에도 있다」입니다. 테스트에서 찾고 싶은 요소는 대개 안쪽 둘에 들어 있습니다. 반대로 raw 뷰에만 나오는 요소(레이아웃 전용 컨테이너 등)를 검색의 발판으로 삼으면, UI 구성을 조금만 바꿔도 깨집니다. inspect.exe에서는 이 뷰를 Options 메뉴의 「Raw View」「Control View」「Content View」로 전환할 수 있으므로, 2.3절에서 점검할 때 「내가 찾고 싶은 요소가 어느 뷰까지 남는지」를 봐 두면 검색 조건 설계가 수월해집니다.3

여기서 잡아 두고 싶은 것은 「눈에 보이는 것」이 아니라 「트리에 공개된 것」만 조작할 수 있다는 점입니다. 표준 컨트롤로 짠 화면은 트리에 깔끔히 나오지만, owner-draw로 직접 그린 리스트, 그래픽 라이브러리로 그린 도면 영역, 서드파티 그리드의 일부 등은 트리 위에서는 「그냥 요소 하나」로만 보일 수 있습니다. 이 보이는 방식이 그대로 UI 자동 테스트 범위의 상한이 됩니다. 그래서 코드를 작성하기 전의 트리 확인(2.3절)이 첫 작업입니다.

2.2 속성과 컨트롤 패턴

트리에서 목적 요소를 특정하는 실마리가 속성입니다. 실무에서 쓰는 것은 다음 4가지입니다.

속성 내용 테스트에서의 위치
AutomationId 개발자가 붙이는 식별자. 언어(로케일)에 의존하지 않음 검색 키의 핵심. 형제 요소 안에서 유일하게 함 6
Name 표시 문자열에서 온 이름(버튼 라벨 등) 사람에게는 알기 쉽지만, 문구 변경·다국어화에서 깨짐
ControlType Button / Edit / ComboBox 등의 종류 필터링 보조
ClassName 구현 클래스명(WinForms 클래스명 등) 최후의 수단. 구현 변경에 잘 깨짐

AutomationId는 「로케일이 바뀌어도 같은 값이어야 한다」「형제 요소 안에서 유일해야 한다」고 공식적으로 정의되어 있으며, UI 자동 테스트가 언어나 버전을 넘어서 안정적으로 요소를 찾기 위한 전용 속성입니다.6 반대로 말하면, AutomationId가 부여되지 않은 앱의 UI 테스트는 표시 문자열이나 트리상의 위치라는 잘 깨지는 실마리에 의존하게 됩니다. 여기가 5장의 주제입니다.

찾은 요소를 조작하는 수단이 컨트롤 패턴입니다. UIA는 「클릭할 수 있다」「값을 가진다」「선택할 수 있다」와 같은 컨트롤의 기능면을 컨트롤 종류와는 독립된 패턴의 집합으로 공개합니다. 문서 자체가 「컨트롤 패턴과 UI의 관계는 COM 객체와 인터페이스의 관계와 같다」고 설명하듯이, 요소에 「어느 패턴을 구현하고 있는지」를 질의한 뒤 그 패턴을 통해 조작하는 설계입니다.2 COM에 익숙한 분에게는 QueryInterface의 UI 판이라고 하면 전해질 것입니다. 주요 패턴은 다음과 같습니다.

패턴 할 수 있는 일 전형적인 컨트롤
Invoke 기본 액션 실행(클릭에 해당) 버튼, 메뉴 항목
Value 값 가져오기·설정 텍스트 박스
SelectionItem / Selection 항목 선택·선택 상태 가져오기 리스트, 콤보 박스, 탭
Toggle 켜기·끄기 전환 체크 박스
ExpandCollapse 펼치기·접기 콤보 박스, 트리 항목
Text 텍스트 내용 읽기 문서, 리치 텍스트
Window 최대화·최소화·닫기 최상위 창
Scroll / ScrollItem 스크롤, 항목을 보이는 위치로 리스트, 그리드

「버튼을 클릭한다」는 테스트 코드는 내부적으로 「그 요소의 Invoke 패턴을 가져와 Invoke()를 호출한다」는 처리입니다. 좌표를 계산해 마우스 이벤트를 보내는 것이 아니라, 컨트롤 자신이 공개하는 조작을 호출한다──이 차이가 창 위치나 DPI에 의존하지 않는 안정된 테스트의 기반이 됩니다.

2.3 inspect.exe와 Accessibility Insights로 「보이는 것」을 확인한다

대상 앱의 요소가 어떤 AutomationId / Name / ControlType / 패턴으로 공개되어 있는지는, 도구로 실물을 보는 것이 가장 좋습니다.

  • inspect.exe: Windows SDK에 포함되는 대표적인 도구입니다(SDK 설치 위치의 bin\<version>\<platform>에 있습니다). 마우스나 키보드 포커스로 요소를 고르면 UIA 속성과 패턴이 목록으로 표시되고, 트리 탐색도 검증할 수 있습니다. 다만 공식으로는 「레거시 도구」로 분류되며, Accessibility Insights로의 이전이 권장됩니다.3
  • Accessibility Insights for Windows: Microsoft의 현행 권장 도구입니다. 마우스 호버나 포커스 이동만으로 요소의 UIA 속성을 확인할 수 있는 Live Inspect가 편리하고, 접근성 관점의 자동 검사(FastPass)도 붙어 있습니다.3
  • FlaUInspect: FlaUI 프로젝트 부속 인스펙터입니다. FlaUI가 실제로 쓰는 UIA2 / UIA3 각각의 시점에서 트리를 확인할 수 있으므로, FlaUI로 테스트를 작성한다면 이것도 넣어 두면 편리합니다.4

inspect.exe의 어디를 볼 것인가

화면 사진 대신, 창의 어느 부분에 무엇이 나오는지를 적어 둡니다. inspect.exe는 Windows SDK 설치 위치의 bin\<버전>\<플랫폼>에 있으며, 관리자로 실행할 필요는 보통 없습니다. 기동하면 기본으로 키보드 또는 마우스 포커스를 따라가며, 선택 중인 요소의 정보가 오른쪽에 나옵니다.3

창의 부분 무엇이 나오는가 테스트에서 볼 것
트리 뷰(왼쪽) 데스크톱을 뿌리로 하는 요소의 계층. 여기를 따라가며 부모·자식 관계를 확인합니다 목적 요소가 트리에 나와 있는지, 어느 부모 아래에 있는지
데이터 뷰(오른쪽) 선택 중인 요소의 UIA 속성이 이름과 값으로 목록 표시됩니다 AutomationId, Name, ControlType, ClassName, IsEnabled, BoundingRectangle
Options 메뉴 표시 모드와 추적 방법 전환 「UI Automation Mode」(MSAA Mode가 아님), 「Raw View」「Control View」「Content View」, 「Watch Focus」「Watch Cursor」, 「Show Highlight Rectangle」
Action 메뉴 선택 중인 요소에 대해 UIA 메서드를 실행할 수 있습니다 UI Automation 모드에서는 그 요소가 구현하는 컨트롤 패턴(2.2절)이 그대로 항목으로 늘어섭니다
Options > Settings 데이터 뷰에 표시할 속성 선택 항목이 너무 많을 때는 표시를 줄입니다. 「Display unsupported properties」로 지원하지 않는 속성도 표시할 수 있습니다

실무에서의 사용법은 이렇습니다. 먼저 「UI Automation Mode」인지 확인하고, 「Watch Focus」를 켠 뒤 대상 앱에서 키보드 조작을 하면, 포커스가 옮겨질 때마다 데이터 뷰가 갱신됩니다. 거기서 AutomationId가 비어 있지 않은지 봅니다. 비어 있으면 5장의 앱 측 수정이 먼저입니다. 다음으로 Action 메뉴를 열어, 누르고 싶은 버튼에 Invoke가 늘어서 있는지, 입력하고 싶은 칸에 Value가 늘어서 있는지를 확인합니다. 여기에 늘어선 것이 그대로 테스트 코드에서 호출할 수 있는 조작입니다. 동적으로 나오는 메뉴나 툴팁처럼 포커스가 이동하지 않는 요소를 잡고 싶을 때는 「Watch Cursor」로 전환합니다.3

경험칙이지만, UI 자동 테스트 도입에서 맨 먼저 해야 할 일은 「테스트 코드를 작성하는 것」이 아니라, 주요 화면을 inspect 계열 도구로 열어 AutomationId가 얼마나 부여되어 있는지 점검하는 것입니다. 여기서 텅 비어 있다면, 먼저 앱 측 수정(5장)부터 시작하는 편이 결국 더 빠릅니다.

3. 도구의 선택지 ── FlaUI를 미는 이유와 WinAppDriver의 현황

UIA를 COM을 통해 직접 호출할 수도 있지만, 실무에서는 래퍼 라이브러리를 씁니다. 2026년 시점의 선택지와 현황을, 확인한 사실 기준으로 정리합니다.

도구 형태 현황(2026년 시점) 신규 채택의 기준
FlaUI .NET 라이브러리(MIT) 활발히 유지보수되는 OSS. v5.0.0이 2025년 2월 릴리스 4 ◎ 제1 후보
WinAppDriver WebDriver 프로토콜 서버(Microsoft) 최종 안정판 v1.2.1은 2020년 11월. v1.3은 2020년 7월 RC인 채. 미해결 issue 1,100건 초과 5 △ 사실상 정지. 신규는 피할 것
Appium Windows Driver Appium의 Windows용 드라이버 내부에서 WinAppDriver를 쓰므로 위의 제약을 그대로 이어받음 9 △ Appium 자산이 있는 경우만
Coded UI 테스트 Visual Studio 기능 VS 2019에서 비권장, VS 2026에서 삭제됨 10 × 이전 대상

3.1 FlaUI ── UIA의 얇은 래퍼로서 지금 가장 실용적

FlaUI는 Windows 앱(Win32 / WinForms / WPF / 스토어 앱)의 UI 자동 테스트를 지원하는 .NET 라이브러리로, Microsoft 네이티브 UIA 라이브러리의 래퍼로 설계되어 있습니다.4 패키지는 공통 부분인 FlaUI.Core와, UIA의 어느 구현을 쓸지로 나뉘는 FlaUI.UIA2 / FlaUI.UIA3 구성입니다.

UIA2와 UIA3의 구분은 FlaUI FAQ에 명시되어 있습니다. UIA2는 매니지드 구현만이며, 터치 등 새 기능에 대응하지 않고 WPF나 스토어 앱과의 궁합이 나쁘다. UIA3는 최신 구현으로 WPF / 스토어 앱에는 최적이지만, WinForms 앱에서는 UIA2에는 없는 문제를 만날 수 있다──라는 관계입니다.4 즉 실무의 기준은 WPF라면 UIA3, WinForms라면 UIA2부터 시험하고, 실제 앱에서 안정된 쪽을 쓰는 것입니다. 둘 다 NuGet에서 넣어 전환해 시험할 수 있는 점이, 래퍼로서 얇은 FlaUI의 장점입니다.

테스트 프레임워크는 갖지 않으므로, xUnit / NUnit / MSTest와 조합해 보통의 테스트 프로젝트로 작성합니다. 단위 테스트와 같은 테스트 러너·같은 CI 파이프라인에 타는 것은 운용상 꽤 효과가 큽니다.

3.2 WinAppDriver ── 솔직히 말하면, 사실상 멈춰 있다

WinAppDriver는 Microsoft의 UI 테스트 서버로, Selenium과 같은 WebDriver 프로토콜로 Windows 앱을 조작할 수 있다는 이유로 한때는 유력 후보 취급이었습니다. Visual Studio의 Coded UI 테스트가 비권장이 되었을 때, Microsoft 자신이 이전 대상으로 「Web은 Selenium, 데스크톱과 UWP는 Appium + WinAppDriver」를 안내했던 경위도 있습니다.10

그러나 현황을 GitHub에서 확인하면, 최종 안정판 v1.2.1의 릴리스는 2020년 11월이며 그 이후 안정판은 나오지 않았습니다. v1.3은 2020년 7월 Release Candidate(v1.2.99)인 채 정식판이 되지 않았고, 미해결 issue는 1,100건을 넘습니다.5 더 골치 아픈 점은 GitHub 리포지토리에 있는 것이 문서·샘플·issue 트래커뿐이고, 서버 본체 소스 코드는 공개되어 있지 않다는 것입니다. 즉 커뮤니티가 버그를 고칠 수도 없습니다. 「공식이니까 안심」이라는 이유로 2026년에 신규 채택할 대상이 아니라는 것이 당사 판단입니다. 이미 WinAppDriver로 돌아가는 테스트 자산이 있다면 당장 버릴 필요는 없지만, 더 늘리지는 말고 새 경로의 테스트부터 FlaUI로 옮겨 가기를 권합니다.

3.3 Appium Windows Driver와 그 밖의 선택지

Appium Windows Driver(appium-windows-driver)는 Appium에서 Windows 앱을 조작하기 위한 드라이버이지만, 그 실체는 「Microsoft가 제공하는 WinAppDriver에 대한 인터페이스」이며, 무거운 처리는 모두 WinAppDriver 측이 맡습니다.9 따라서 WinAppDriver의 답보 상태를 그대로 이어받습니다. 모바일이나 Web에서 Appium으로 통일한 팀, 테스트 코드를 Java나 Python으로 쓰고 싶은 팀에는 여전히 선택지가 되지만, 그 경우에도 WinAppDriver에서 온 제약과 장래성은 파악한 뒤에 채택하세요.

WinUI 3(Windows App SDK) 앱도 UIA에 대응하므로 FlaUI(UIA3)의 테스트 대상으로 삼을 수 있습니다. 다만 WinForms / WPF에 비해 사례 축적이 얇고, 컨트롤별 보이는 방식의 버릇은 inspect 계열 도구로 사전 확인하는 것이 더 중요해집니다. UI 프레임워크 자체의 선정은 「WinForms/WPF/WinUI의 고르는 법 - 실무 판단표」에서 정리한 대로입니다.

4. FlaUI에서의 최소 구현 ── 기동·검색·조작·검증

이론은 여기까지로 하고, 동작하는 코드를 봅니다. 본문 곳곳에 흩어진 전제를 먼저 여기서 모아 둡니다.

준비할 것

종류 준비할 것 보충
테스트 대상 빌드된 앱 본체(exe의 전체 경로) 설정 파일이나 DB를 테스트용으로 바꿔 넣을 수 있는 기동 옵션이 있으면 한결 편해집니다(5.4절)
SDK .NET SDK(8 이후를 권장) UIA는 Windows 전용이므로 테스트 프로젝트의 타깃은 net8.0-windows로 합니다
NuGet FlaUI.UIA3(WPF용) 또는 FlaUI.UIA2(WinForms용) 공통 부분인 FlaUI.Core는 의존으로 함께 들어갑니다. 둘 다 넣어 전환해 시험할 수 있습니다(3.1절)4
NuGet xunit + xunit.runner.visualstudio dotnet new xunit으로 들어갑니다. NUnit / MSTest여도 됩니다
확인 도구 inspect.exe 또는 Accessibility Insights for Windows 코드를 작성하기 전에 대상 앱의 보이는 방식을 점검합니다(2.3절)3
실행 환경 화면이 있는 Windows. 실행 중에는 그 머신을 사람이 만지지 않음 무인 실행으로 가져갈 때의 조건은 7장

프로젝트 작성은 명령 3개입니다.

dotnet new xunit -o OrderManager.UiTests
cd OrderManager.UiTests
dotnet add package FlaUI.UIA3

생성된 .csproj의 TargetFramework를 net8.0-windows로 바꿉니다(UIA가 Windows 전용이므로). 아울러 UI 테스트는 1대당 1개씩 직렬로 흘려야 하므로(7.2절), xUnit의 병렬 실행은 처음에 꺼 둡니다.

// 테스트 프로젝트 안의 어딘가(AssemblyInfo.cs 등)에 한 번만 작성
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]

여기까지면 나머지는 보통의 테스트 프로젝트입니다. 전용 러너도 전용 프로젝트 종류도 필요 없습니다.

4.1 스모크 테스트의 기본형

「앱을 기동하고, 수주 등록 대화상자를 열고, 1건 저장한 뒤, 결과가 상태에 나온다」는 경로의 테스트는 이렇게 작성할 수 있습니다.

using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Tools;
using FlaUI.UIA3;
using Xunit;

public class OrderSmokeTest
{
    [Fact]
    public void 受注登録_主要導線が通る()
    {
        using var app = Application.Launch(@"C:\App\OrderManager.exe");
        using var automation = new UIA3Automation();
        try
        {
            // 메인 창이 나올 때까지 기다려 준다
            var window = app.GetMainWindow(automation);

            // AutomationId로 요소를 찾고 Button으로 조작한다
            window.FindFirstDescendant(cf => cf.ByAutomationId("NewOrderButton"))
                  ?.AsButton().Invoke();

            // 대화상자는 비동기로 열리므로 「나올 때까지」 조건 대기한다
            var dialog = Retry.WhileNull(
                () => window.FindFirstDescendant(
                          cf => cf.ByAutomationId("OrderDialog"))?.AsWindow(),
                timeout: TimeSpan.FromSeconds(5)).Result;
            Assert.NotNull(dialog);

            // Value 패턴을 통해 입력(키보드 에뮬레이션이 아님)
            dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox"))
                  .AsTextBox().Text = "테스트상사";
            dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton"))
                  .AsButton().Invoke();

            // 저장 완료도 상태 표시를 조건 대기로 검증한다
            var saved = Retry.WhileFalse(
                () => window.FindFirstDescendant(
                          cf => cf.ByAutomationId("StatusLabel"))
                          ?.Name.Contains("저장했습니다") == true,
                timeout: TimeSpan.FromSeconds(10));
            Assert.True(saved.Success);
        }
        finally
        {
            app.Close();
        }
    }
}

구성 요소는 4가지만입니다.

  • 기동: Application.Launch로 프로세스를 기동합니다(이미 기동 중인 앱에 Application.Attach로 붙일 수도 있습니다). GetMainWindow는 메인 창을 가져올 수 있을 때까지 대기합니다.
  • 검색: FindFirstDescendant(cf => cf.ByAutomationId(...))가 트리 검색입니다. cf는 조건 팩터리로, ByName / ByControlType / And 조건도 조합할 수 있습니다. 검색 키는 앞에서 말한 대로 AutomationId를 핵심으로 합니다.
  • 조작: 찾은 요소를 AsButton() / AsTextBox() / AsComboBox() 등의 형 지정 래퍼로 변환해 조작합니다. Invoke()는 Invoke 패턴, Text 속성은 Value 패턴으로, 2.2절의 컨트롤 패턴이 그대로 뒤에 있습니다.
  • 검증: UI에 나타나는 결과(라벨, 리스트 행 수, 창 제목 등)를 assert합니다. DB 내용까지 보고 싶다면 테스트 코드에서 직접 DB를 읽어도 됩니다.

4.2 대기는 Retry로 ── Sleep을 쓰면 진다

UI 테스트의 불안정함(이른바 flaky test)의 최대 발생원은 타이밍입니다. 대화상자가 열릴 때까지, 데이터가 읽힐 때까지, 버튼이 유효해질 때까지──UI는 항상 비동기로 변하는데, 테스트 코드가 「이미 표시되어 있을 것이다」라고 단정하면 느린 머신에서만 실패하는 테스트가 됩니다.

그렇다고 Thread.Sleep(3000)을 넣는 것은 최악의 대응입니다. 빠른 머신에서는 쓸데없이 기다리고, 느린 머신에서는 모자라 실패하며, 테스트 전체 실행 시간만 쌓입니다. 답은 조건 대기, 즉 「조건이 충족될 때까지 폴링하고, 타임아웃에서 끊는다」이며, FlaUI에는 전용 Retry 클래스가 있습니다.4

// null이 아니게 될 때까지(=요소가 나타날 때까지) 최대 5초 대기
var element = Retry.WhileNull(
    () => window.FindFirstDescendant(cf => cf.ByAutomationId("ResultGrid")),
    timeout: TimeSpan.FromSeconds(5),
    interval: TimeSpan.FromMilliseconds(200),
    throwOnTimeout: true).Result;

// 조건이 true가 될 때까지(=버튼이 유효해질 때까지) 대기
Retry.WhileFalse(
    () => saveButton.IsEnabled,
    timeout: TimeSpan.FromSeconds(5),
    throwOnTimeout: true);

Retry.WhileNull / WhileFalse / WhileTrue / WhileException 등이 준비되어 있으며, 타임아웃·폴링 간격·타임아웃 시 예외를 던질지를 지정할 수 있습니다.4 주의점으로, FlaUI는 2.0 이후 Find 계열 메서드의 암시적 재시도를 폐지하고 「어디서 기다릴지는 테스트 코드가 명시한다」는 방침입니다.4 FindFirstDescendant는 「지금 이 순간의 트리」만 보므로, 비동기로 나타나는 요소 검색은 반드시 Retry로 감싼다를 규약으로 하세요. 참고로 「Sleep으로 넘기지 않고 대기 조건을 세운다」는 발상 자체는 UI 테스트에 한정되지 않고 Windows 프로그래밍 전반의 정석입니다(「Windows에서 Sleep(1)보다 이벤트 대기를 우선해야 하는 이유」).

5. 잘 깨지지 않게 하는 설계 ── 앱 측과 테스트 측의 규약

UI 테스트가 「유지보수에 버티지 못하고 포기」되는 원인은 거의 다음 4가지로 모입니다. 표시 문자열에 의존하는 검색, 좌표 클릭, Sleep, 그리고 구조 공유 부족입니다. 각각 규약으로 막습니다.

5.1 AutomationId를 개발 측에서 반드시 부여한다

가장 중요한 것은 이것입니다. 테스트가 잘 깨지지 않는지는 테스트 코드 작성법보다 먼저, 앱이 안정된 식별자를 공개하고 있는지로 정해집니다. AutomationId는 바로 그 목적의 속성이며, 로케일에 의존하지 않고 형제 요소 안에서 유일할 것이 요구되는 사양입니다.6

WPF에서는 x:Name을 붙인 요소가 그것이 UIA 측 식별자로 쓰이므로, 이름이 붙은 컨트롤은 추가 작업 없이 테스트할 수 있습니다. 데이터 템플릿 안 등에서 명시적으로 붙이고 싶을 때는 첨부 속성 AutomationProperties.AutomationId를 설정합니다.67

<!-- x:Name이 그대로 식별자가 된다 -->
<Button x:Name="SaveButton" Content="저장" Click="OnSave" />

<!-- 템플릿 안 등은 AutomationProperties.AutomationId를 명시 -->
<Button AutomationProperties.AutomationId="DeleteRowButton"
        Content="削除"
        Command="{Binding DeleteCommand}" />

WinForms에서는 디자이너에서 붙이는 Control.Name(button1을 saveButton으로 바꾸는, 그 이름입니다)이 UIA 측 식별에 쓰입니다. 당사 경험으로는 Name을 규약대로 붙인 폼은 그대로 AutomationId로 검색되는 경우가 대부분이지만, 프레임워크 세대나 컨트롤에 따라 보이는 방식에 차이가 있으므로 반드시 inspect 계열 도구로 실제 AutomationId를 확인한 뒤에 테스트 검색 키로 쓰세요. 아울러 스크린 리더용 읽기 이름이 되는 UIA의 Name 속성은 많은 컨트롤에서 Text 속성이 그대로 쓰이지만, TextBox나 ListView처럼 그대로 쓰이지 않는 종류에서는 AccessibleName의 명시 설정이 필요합니다.11 AutomationId 정비는 테스트뿐 아니라 접근성 대응과 완전히 같은 작업이라는 점은, 사내에서 예산을 통과시킬 때 좋은 재료가 됩니다.

규약은 단순해도 되며, 당사에서는 다음 2줄을 코딩 규약에 더해 달라고 하고 있습니다.

  • 화면에 두는 컨트롤에는, 조작·검증 대상이 될 수 있는 한, 의미 있는 Name(WinForms) / x:Name 또는 AutomationProperties.AutomationId(WPF)를 붙인다
  • 한 번 테스트에서 참조된 식별자는 이름 변경을 테스트 측과 동시에 한다(식별자는 공개 API로 본다)

5.2 좌표 클릭 금지

「화면 좌표 (830, 412)를 클릭한다」는 형태의 조작은 창 위치·해상도·DPI 스케일링·테마·글꼴 설정 중 무엇이 바뀌어도 깨집니다. 특히 DPI는 개발기 100% / CI 머신 150% 같은 환경 차로 「로컬에서는 통과하는데 CI에서 실패한다」를 양산합니다(DPI 구조는 「WinForms의 고DPI 대응」에서 쓴 대로입니다). 2장에서 본 것처럼 UIA 컨트롤 패턴을 통한 조작은 좌표에 의존하지 않습니다. FlaUI에도 마우스 직접 조작 API는 있지만, 써도 되는 것은 드래그 앤 드롭이나 그리기 캔버스처럼 패턴으로 표현할 수 없는 조작만으로 정해 둡니다. 그리고 그 경우에도 화면 좌표가 아니라 요소의 BoundingRectangle에서 상대 위치를 계산합니다.

5.3 Page Object 패턴으로 구조를 한곳에 모은다

FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")) 같은 검색 코드를 테스트 본체에 그대로 쓰면, 화면 구성이 바뀌었을 때 모든 테스트를 고치게 됩니다. 일반적인 해결책이 Page Object 패턴으로, 「화면(또는 대화상자) 하나에 클래스 하나」를 만들고 요소 검색과 조작을 그곳에 가둡니다.

public sealed class OrderDialogPage
{
    private readonly Window _dialog;
    public OrderDialogPage(Window dialog) => _dialog = dialog;

    // 요소 검색은 이 클래스 안에만 작성
    private TextBox CustomerName =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")).AsTextBox();
    private Button Save =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton")).AsButton();
    private Label Status =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("StatusLabel")).AsLabel();

    // 테스트에서 보이는 것은 「업무의 말」인 조작만
    public void Register(string customerName)
    {
        CustomerName.Text = customerName;
        Save.Invoke();
        // 요소 검색과 null 판정까지 재시도 안에서 수행한다. 상태 라벨이
        // 저장 후 생성·다시 그려지는 화면에서는, 검색이 순간 null이나 예외가 되는 것이
        // 정상 경로이므로(ignoreException이 없으면 첫 예외에서 그대로 실패한다)
        Retry.WhileFalse(() => Status?.Name.Contains("저장했습니다") == true,
            timeout: TimeSpan.FromSeconds(10), throwOnTimeout: true,
            ignoreException: true);
    }
}

테스트 본체는 new OrderDialogPage(dialog).Register("테스트상사")처럼 업무의 말로 작성할 수 있게 되고, 화면 변경의 영향은 Page Object 수정 한곳으로 끝납니다. 화면이 10을 넘는 앱에서 UI 테스트를 계속 운용한다면 이 패턴은 사실상 필수입니다. 한 가지만 주의로, 대기 조건에서 참조하는 요소는 위의 Status처럼 속성을 통해 매번 다시 검색하는 형태를 지키세요. 검색 결과를 필드에 캐시하면 다시 그려지며 사라진 옛 요소를 붙잡은 채 타임아웃까지 기다리게 됩니다.

5.4 테스트의 독립성 ── 상태를 들고 다니지 않는다

UI 테스트는 하나하나를 독립시킵니다. 「테스트 3은 테스트 2가 만든 데이터가 전제」 같은 순서 의존을 만들면, 하나 실패가 연쇄하고 순서 바꾸기도 못 하게 됩니다. 원칙은 다음과 같습니다.

  • 각 테스트(또는 테스트 클래스)가 스스로 앱을 기동하고, 끝나면 확실히 닫는다(finally 또는 IDisposable 픽스처로)
  • 전제 데이터는 테스트 측이 준비한다. 앱의 설정 파일·DB를 테스트용으로 바꿔 넣을 수 있는 기동 옵션을 앱 측에 마련해 두면 한결 편해집니다
  • 실패 시 뒷정리를 잊지 않는다. 닫지 못한 프로세스나 모달 대화상자의 잔해는 다음 테스트의 실패 원인이 됩니다(7장)

6. 어디까지 UI 테스트로 지킬 것인가 ── 스모크 중심의 선 긋기

도구가 갖춰지면 모든 화면을 테스트하고 싶어지지만, 여기가 분수령입니다. UI 테스트는 단위 테스트에 비해 훨씬 느리고(하나당 수 초~수십 초), 잘 깨지며(UI 변경마다 맞춰 줘야 하고), 실패 원인 파악에 시간이 걸립니다(앱 버그인지, 테스트 버그인지, 환경인지). 테스트 피라미드 최상단이 가늘게 그려지는 것은 이 비용 구조 때문이며, 아래 층에서 지킬 수 있는 것을 UI 테스트로 지키는 것은 언제나 손해입니다.

판단표 형태로 쓰면 이렇습니다.

지키고 싶은 것 맞는 층 이유
계산·변환·업무 규칙 단위 테스트 빠르고 안정. UI를 거쳐 망라하는 것은 논외
DB 접근·파일 I/O·외부 연동 통합 테스트 실물을 쓰면서도 UI는 불필요(경계 긋는 법)
ViewModel·프레젠테이션 로직 단위 테스트 MVVM이면 UI 없이 테스트 가능
기동할 수 있다·주요 경로가 통과한다·저장할 수 있다 UI 스모크 테스트 여기가 UI 테스트의 핵심 영역
과거에 수동 확인에서 놓친 치명적인 경로 UI 테스트(회귀) 실제 피해가 있었던 곳에 한정해 추가
화면 레이아웃·겉모습의 붕괴 육안·스크린샷 비교 assert로 쓰면 유지보수 지옥. 장수를 줄인다
크래시·핸들 누수 등의 비정상 시나리오 다른 기반 UI 테스트 범위 밖(Application Verifier)

당사가 권하는 시작 방법은 「스모크 10개 전후」입니다. 「기동해서 메인 화면이 나온다」「주요 마스터를 열 수 있다」「대표적인 전표를 1건 등록·검색·인쇄 미리보기할 수 있다」「종료 시 오류가 나지 않는다」──릴리스 전에 반드시 손으로 확인하던 경로의 상위 10개를 그대로 자동화합니다. 이 규모라면 작성도 1~2주, 매일 밤 실행도 20~30분에 들어가고, 유지보수 부하도 현실적입니다. 효과를 체감한 뒤에, 실제 피해가 있었던 회귀 버그의 재발 방지 테스트를 하나씩 더합니다. 반대로 「모든 화면·모든 항목」을 목표로 세운 계획은 거의 확실히 도중에 무너집니다.

또 하나 중요한 것은 UI 테스트를 늘리는 대신 로직을 UI에서 분리하는 방향의 투자입니다. 이벤트 핸들러에 업무 로직이 들어간 화면은 UI 테스트로만 지킬 수 있지만, 로직을 ViewModel이나 서비스 클래스로 옮기면 단위 테스트로 지킬 수 있게 되고, UI 테스트는 「배선 확인」만으로 끝납니다. UI 테스트가 필요하다고 느끼는 본 수가 많다면, 그것은 테스트 문제가 아니라 설계 문제인 경우가 많다는 것이 체감입니다. 테스트 전체를 어떻게 층으로 나눌지는 「유닛 테스트와 통합 테스트의 경계를 어떻게 긋는가」, 아래 층 테스트의 실무는 「자체 로거의 최소 요건과 통합 테스트 체크리스트」도 함께 참조하세요.

7. CI·무인 실행의 함정 ── 데스크톱이 없으면 UI 테스트는 동작하지 않는다

작성한 UI 테스트를 개발자 손에서 돌리는 동안은 평화롭습니다. 함정이 모이는 것은 「CI에서 매일 밤 무인 실행한다」는 단계이며, Web UI 테스트(헤드리스 브라우저로 끝남)와 달리 데스크톱 앱 테스트는 진짜 대화형 데스크톱 세션을 요구한다는 것이 모든 근원입니다.

7.1 대화형 세션 필수 ── 서비스로 기동한 에이전트에서는 동작하지 않는다

CI 에이전트(Azure Pipelines 에이전트, GitHub Actions 셀프 호스트 러너 등)는 보통 Windows 서비스로 상주합니다. 그러나 서비스에는 사용자 데스크톱이 없으므로, 거기에서 기동한 앱의 창은 조작할 수 없습니다. Azure Pipelines 공식 문서도, 데스크톱 앱 UI 테스트를 돌리는 에이전트는 서비스가 아니라 자동 로그온(autologon)을 켠 대화형 프로세스로 구성해야 한다고 명시합니다.8 또한 Microsoft 호스팅 에이전트(클라우드 측에서 마련되는 공유 러너)에서는 가시 UI 테스트가 지원되지 않으며, 헤드리스 브라우저 테스트만 동작합니다.8 즉 데스크톱 앱 UI 테스트에는 셀프 호스트 머신(물리든 VM이든)이 사실상 필수입니다.

autologon 구성에는 「그 머신에 물리적으로 접근할 수 있는 사람은 자동 로그온된 계정을 쓸 수 있다」는 보안 위험이 공식 문서에도 적혀 있습니다.8 테스트 전용 계정·전용 머신(VM)으로 하고, 프로덕션 자격 정보를 두지 않는 것이 전제입니다.

7.2 화면 잠금·RDP 연결 끊기·해상도 ── 흔한 실패 패턴

대화형 세션을 마련해도 아직 함정이 있습니다. 증상과 대책을 표로 정리합니다.

함정 증상 대책
에이전트가 서비스 기동 요소가 전혀 찾아지지 않음·앱이 기동하지 않음 대화형 프로세스+autologon으로 다시 구성 8
화면 잠금·화면 보호기 입력 계열 조작이 닿지 않아 실패 autologon 구성에서 화면 보호기 무효화. 잠금을 유발하는 GPO 제외 신청 8
RDP를 「×」로 끊기 끊는 순간에 세션이 잠기고, 이후 테스트가 모두 실패 tscon <세션ID> /dest:console로 콘솔에 되돌린 뒤 끊기 8
해상도·DPI의 환경 차 로컬에서 통과하는 테스트가 CI에서만 실패 해상도를 고정(Azure Pipelines에는 설정 태스크 있음). 스케일링은 100%로 통일 8
테스트의 병렬 실행 마우스·키보드·포커스 쟁탈로 서로 파괴 UI 테스트는 머신당 1개씩 직렬 실행. 병렬화는 머신(VM)을 늘려 수행
이전 실패의 잔해 남은 프로세스나 모달 대화상자가 다음 기동을 방해 테스트 시작 전에 대상 프로세스를 청소. 종료 처리를 finally에서 반드시 실행

RDP 함정은 특히 빠지기 쉬우므로 보충합니다. 테스트 머신에 원격 데스크톱으로 들어가 조정한 뒤 창을 닫고 나가면, 그 세션은 잠긴 상태가 되어 이후 UI 테스트가 계속 실패합니다. 공식 문서가 안내하는 회피책은 연결을 끊기 전에 관리자 명령 프롬프트에서 %windir%\System32\tscon.exe <ID> /dest:console을 실행해 세션을 콘솔로 되돌리는 것입니다.8 테스트 머신 운용 절차서에 반드시 넣어 두세요.

DPI·해상도도 주의가 필요합니다. CI용 VM이 1024×768·스케일링 기본값인 채로 있는 일은 드물지 않으며, 레이아웃이 바뀌어 「개발기에서는 보이던 버튼이 스크롤하지 않으면 보이지 않는다」 같은 차가 납니다. 좌표 클릭을 배제했다면 대부분은 흡수할 수 있지만, 환경은 고정하는 편이 낫습니다. 앱 측 DPI 대응 상황까지 포함해 「WinForms의 고DPI 대응」에서 쓴 확인 관점을 그대로 쓸 수 있습니다.

7.3 실패 시 증거를 남긴다 ── 스크린샷과 로그

무인 실행 UI 테스트가 실패했을 때, 로그에 「요소를 찾을 수 없습니다」만 남아 있어도 원인은 알 수 없습니다. 실패 시 스크린샷 저장을 처음부터 심어 둡니다. FlaUI에는 화면이나 요소를 캡처하는 기능이 있으므로, 테스트 프레임워크의 실패 훅에서 호출하고 CI 산출물(아티팩트)로 저장합니다.

// 실패 시 훅 등에서 호출. CI 아티팩트 저장 위치로 출력
FlaUI.Core.Capturing.Capture.Screen()
    .ToFile(Path.Combine(artifactDir, $"{testName}_{DateTime.Now:HHmmss}.png"));

스크린샷에 더해, 앱 자신의 로그(어디까지 처리가 진행됐는지)와 테스트 측 로그(어느 조작까지 성공했는지)를 타임스탬프로 맞출 수 있게 해 두면, 「앱 버그인지, 테스트 버그인지, 환경인지」를 가르는 일이 한결 빨라집니다. 테스트 실행을 작업 스케줄러로 야간에 돌릴 때의 주의점(세션 종류, 0x1로 끝나는 문제 등)은 「작업 스케줄러 작업이 실행되지 않거나 0x1로 끝난다」에서 쓴 대로입니다.

7.4 CI 설정의 최소 예 ── GitHub Actions 셀프 호스트 러너

7.1~7.3의 조건을 워크플로에 내리면 다음과 같습니다. 대전제로, 러너는 Windows 서비스가 아니라 대화형 프로세스로 구성하고, 자동 로그온을 켠 테스트 전용 머신에서 돌립니다(7.1절). 여기를 빼면 YAML이 아무리 올바라도 테스트는 요소를 하나도 찾지 못합니다.

name: nightly-ui-smoke

on:
  schedule:
    # cron 지정은 UTC. JST 평일 0:00에 돌리고 싶으므로 UTC에서는 일~목 15:00
    - cron: "0 15 * * 0-4"
  workflow_dispatch:   # 조사용으로 수동 실행도 남긴다

jobs:
  ui-smoke:
    # 대화형 세션에서 돌리고 있는 셀프 호스트 러너에 붙인 레이블.
    # GitHub 호스팅 러너에서는 가시 UI 테스트가 동작하지 않는다(7.1절)
    runs-on: [self-hosted, windows, ui-test]
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: 이전 잔해를 청소한다
        shell: powershell
        continue-on-error: true
        run: Get-Process OrderManager -ErrorAction SilentlyContinue | Stop-Process -Force

      - name: 앱을 빌드한다
        run: dotnet publish src/OrderManager -c Release -o artifacts/app

      - name: UI 스모크 테스트를 실행한다
        run: dotnet test tests/OrderManager.UiTests -c Release --logger "trx;LogFileName=ui.trx"

      - name: 증거를 수집한다
        if: always()   # 실패했을 때야말로 필요하므로 always
        uses: actions/upload-artifact@v4
        with:
          name: ui-test-evidence
          path: |
            **/TestResults/**
            artifacts/screenshots/**

잡아 둘 점은 4가지입니다.

  • runs-on은 셀프 호스트 레이블로 합니다. 7.1절대로, 공유 호스팅 러너에서는 가시 UI 테스트가 지원되지 않습니다.8
  • timeout-minutes를 반드시 붙인다. 모달 대화상자에서 멈춘 UI 테스트는 스스로 돌아오지 않으므로, 잡 측에서 끊습니다.
  • 청소를 스텝으로 쓴다. 이전 실패의 잔해(닫지 못한 프로세스)는 다음 기동을 방해합니다(5.4절·7.2절).
  • if: always()로 증거를 남긴다. 7.3절의 스크린샷 출력 위치(위 예에서는 artifacts/screenshots)와 테스트 결과 trx를 아티팩트로 올리지 않으면, 무인 실행 실패는 조사할 수 없습니다.

참고로 schedule 시각을 UTC로 써야 하는 것은 GitHub Actions에 한정되지 않습니다. 여기를 일본 시간으로 생각하고 써서 하루가 어긋나는 것은 흔한 실수입니다(날짜·시간과 타임존 처리는 「업무 앱의 날짜·시간과 타임존」에 정리했습니다).

8. 정리

UI 자동 테스트는 「도구를 넣으면 동작하는」 것이 아니라, 구조의 이해·앱 측의 협력·범위의 선 긋기·실행 환경 설계가 갖춰져야 비로소 계속 돌아갑니다. 요점을 압축합니다.

  • 기반은 UI Automation. 트리에서 AutomationId로 찾고, 컨트롤 패턴으로 조작한다. 먼저 inspect / Accessibility Insights로 대상 앱의 보이는 방식을 점검한다
  • 도구는 FlaUI + xUnit / NUnit. WinAppDriver는 2020년부터 안정판이 나오지 않으며, 신규 채택은 피한다
  • 잘 깨지지 않게 만드는 것은 규약이다. AutomationId를 개발 측에서 부여하고, 좌표 클릭 금지, Sleep 금지 후 Retry로 조건 대기, Page Object로 구조를 한곳에
  • 범위는 스모크 10개 전후부터. 로직은 단위·통합 테스트로 모으고, UI 테스트는 「주요 경로가 통과한다」는 확인에 철저히 한다
  • CI는 셀프 호스트 머신+대화형 세션+autologon이 기본형. 화면 잠금·RDP 연결 끊기·해상도 차의 함정을 운용 절차로 막고, 실패 시 스크린샷을 반드시 남긴다

「릴리스 전 2일간의 수동 확인」은, 올바르게 한정한 UI 자동 테스트라면 현실적인 비용으로 바꿀 수 있습니다. 반대로 현재 앱에 AutomationId가 전혀 부여되어 있지 않거나, 로직이 화면 이벤트에 바로 쓰여 있는 경우에는, 테스트를 작성하기 전에 앱 측의 작은 수정부터 시작하는 것이 결국 지름길입니다. 어디서 손을 대야 할지 가늠하거나, 스모크 테스트 한 세트를 시작하는 일은 앱 현황 조사부터 도와드릴 수 있습니다.

관련 기사

관련 상담 영역

合同会社小村ソフト에서는 WinForms / WPF 앱에 UI 자동 테스트를 도입하는 일(현황 조사, AutomationId 정비, 스모크 테스트 한 세트 구축, CI 환경 설계)이나, 기존 테스트 자산의 FlaUI 이전, 테스트 전략 전체의 설계 리뷰를 다루고 있습니다.

참고 링크

  1. Microsoft Learn, UI Automation Overview. 데스크톱을 뿌리로 하는 자동화 트리, raw / 컨트롤 / 콘텐츠 뷰, 요소의 속성과 컨트롤 패턴, 보조 기술과 테스트 자동화가 같은 기반을 쓰는 것에 대해. ↩ ↩2 ↩3

  2. Microsoft Learn, UI Automation Control Patterns Overview. 컨트롤 패턴의 설계(COM 인터페이스와의 비유), Invoke / Value / SelectionItem 등의 패턴과, 한 컨트롤이 여러 패턴을 구현할 수 있는 것에 대해. ↩ ↩2

  3. Microsoft Learn, Accessibility tools - Inspect. inspect.exe가 Windows SDK 포함(bin\<version>\<platform>)이며 UIA 속성과 패턴을 확인할 수 있다는 것, 레거시 도구로 분류되고 Accessibility Insights가 권장된다는 것, 창이 트리 뷰와 데이터 뷰로 구성되고 기본으로 포커스를 추적한다는 것, Options 메뉴의 UI Automation Mode / Raw View / Control View / Content View / Watch Focus / Watch Cursor / Show Highlight Rectangle / Settings, Action 메뉴에서 선택 중인 요소의 컨트롤 패턴을 실행할 수 있다는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7

  4. GitHub, FlaUI/FlaUI. Win32 / WinForms / WPF / 스토어 앱 대응의 UIA 래퍼라는 것, FlaUI.Core / UIA2 / UIA3의 패키지 구성, UIA2와 UIA3의 구분(FAQ), MIT 라이선스, v5.0.0(2025년 2월) 릴리스와 개발의 지속, Retry 유틸리티와 2.0 이후 Find 계열 암시적 재시도 폐지에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  5. GitHub, microsoft/WinAppDriver. 최종 안정판 v1.2.1이 2020년 11월 릴리스라는 것, v1.3이 2020년 7월 Release Candidate인 채 갱신되지 않았다는 것, 미해결 issue가 1,100건을 넘는다는 것, 리포지토리가 문서·샘플 중심이고 서버 본체 소스가 공개되어 있지 않다는 것에 대해. ↩ ↩2 ↩3

  6. Microsoft Learn, Use the AutomationID Property. AutomationId가 로케일에 의존하지 않는 식별자라는 것, 형제 요소 안에서 유일해야 한다는 것, 테스트 스크립트에서 검색 키로 쓰는 시나리오, WPF에서 ID(x:Name)나 x:Uid가 없는 컨트롤에서는 AutomationId가 지원되지 않는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  7. Microsoft Learn, AutomationProperties.AutomationId Attached Property. WPF(System.Windows.Automation 네임스페이스)에서 요소를 유일하게 식별하는 문자열을 설정하는 첨부 속성의 정의에 대해. ↩ ↩2

  8. Microsoft Learn, UI testing considerations (Azure Pipelines). 데스크톱 앱 UI 테스트에는 autologon을 켠 대화형 프로세스로서의 에이전트 구성이 필요하다는 것, Microsoft 호스팅 에이전트에서 가시 UI 테스트가 지원되지 않는다는 것, RDP 연결 종료로 인한 잠금과 tscon에 의한 회피, 화면 해상도 설정 태스크, 실패 시 스크린샷·동영상 수집에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10

  9. GitHub, appium/appium-windows-driver. Appium Windows Driver가 Microsoft 제공 WinAppDriver에 대한 인터페이스이며, WinAppDriver 서버가 장기간 유지보수되지 않는다고 README에서 주의 환기하고 있는 것에 대해. ↩ ↩2

  10. Microsoft Learn, Use Coded UI tests to test your code. Coded UI 테스트가 비권장이며 Visual Studio 2019가 완전 대응의 최종 버전이라는 것, 이전 대상으로 데스크톱 / UWP 앱에는 Appium + WinAppDriver가 안내되어 있었다는 것에 대해(VS 2026에서의 삭제는 같은 Learn 안의 이전 가이드에 기재). ↩ ↩2

  11. Microsoft Learn, WinForms: Setting the accessible name on a control. WinForms 컨트롤의 UIA Name 속성이 많은 종류에서 Text 속성에서 가져와 쓰인다는 것, TextBox / ListBox 등에서는 AccessibleName의 명시 설정이 필요하다는 것에 대해. ↩

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

WinForms/WPF 앱의 UI 자동 테스트에는 무엇을 쓰면 되나요?
당사 권장은 FlaUI+xUnit/NUnit입니다. FlaUI는 Windows UI Automation(UIA)을 얇게 감싼 MIT 라이선스 OSS로, UIA2/UIA3 모두에 대응하며 2025년 2월에 v5.0.0이 릴리스되는 등 활발히 유지보수되고 있습니다. 구분의 기준은 WPF라면 UIA3, WinForms라면 UIA2부터 시험해 보는 것입니다. 테스트 프레임워크를 갖지 않으므로 단위 테스트와 같은 테스트 러너·같은 CI 파이프라인에 올릴 수 있습니다. 코드를 작성하기 전에 inspect.exe나 Accessibility Insights for Windows로 대상 앱의 요소가 어떻게 보이는지 확인하는 것이 첫 작업입니다.
WinAppDriver는 지금부터 채택해도 되나요?
신규 채택은 권하지 않습니다. Microsoft의 WinAppDriver는 최종 안정판 v1.2.1이 2020년 11월에서 멈춰 있고, v1.3은 2020년 7월 Release Candidate인 채이며, 미해결 issue는 1,100건을 넘습니다. 게다가 GitHub 리포지토리에 있는 것은 문서와 샘플뿐이고, 서버 본체 소스 코드는 비공개라 커뮤니티가 버그를 고칠 수도 없습니다. Appium의 Windows Driver도 내부에서 WinAppDriver를 쓰므로 같은 제약을 이어받습니다. 기존 테스트 자산이 있다면 당장 버릴 필요는 없지만, 새 경로의 테스트부터 FlaUI로 옮겨 가기를 권합니다.
UI 자동 테스트가 잘 깨지는 것은 어떻게 막나요?
테스트가 잘 깨지지 않는지는 80%가 개발 측에서 AutomationId를 부여하는 규약으로 정해집니다. WPF는 x:Name 또는 AutomationProperties.AutomationId, WinForms는 Control.Name이 식별자가 됩니다. 그 위에서 표시 문자열(Name)에 의존하는 검색과 좌표 클릭을 금지하고, Thread.Sleep 대신 FlaUI의 Retry 클래스로 조건 대기를 쓰며, 화면마다 요소 검색과 조작을 가두는 Page Object 패턴으로 구조를 한곳에 모읍니다. FlaUI는 2.0 이후 Find 계열 메서드의 암시적 재시도를 폐지했으므로, 비동기로 나타나는 요소 검색은 반드시 Retry로 감싸는 것을 규약으로 하세요.
UI 자동 테스트는 어느 범위까지 써야 하나요?
스모크 테스트 10개 전후부터 시작하는 것을 권합니다. UI 테스트는 단위 테스트보다 훨씬 느리고 잘 깨지므로, 계산·업무 규칙은 단위 테스트, DB 접근은 통합 테스트로 지키고, UI 테스트는 「기동해서 주요 경로가 통과한다」는 확인에 한정합니다. 릴리스 전에 손으로 확인하던 경로의 상위 10개를 자동화하면, 작성은 1~2주, 매일 밤 실행은 20~30분에 들어갑니다. 반대로 「모든 화면·모든 항목」을 목표로 한 계획은 거의 확실히 무너집니다. UI 테스트가 필요하다고 느끼는 본 수가 많다면, 로직을 ViewModel로 분리해 단위 테스트로 지킬 수 있게 하는 설계 개선의 신호입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기