Android에서 Storybook 사용해보기

개발AndroidStorybookCMPKSP

2026년 9월 13일

배경

나는 React와 Android 양쪽에서 나름 프로젝트를 꽤 길게 해 왔는데, React 측 진영에는 Storybook이라는 꽤나 괜찮은 협업 도구가 있다. 근데 이걸 Android 진영에서는 사용을 못 하니, 어떻게 하면 Storybook과 거의 유사한 기능을 Android 프로젝트에서 써 볼 수 있을까를 고민하고 솔루션을 찾아온 과정을 간단히 소개하고자 한다.

Storybook

Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It's open source and free. from https://storybook.js.org/

Storybook은 React 앱 개발 시, FE 개발자와 디자이너 간 협업 경험을 개선해주는 툴이다.

소위 디자인 시스템이라고 불리는 공용 컴포넌트를 테스트할 수 있는 환경을 제공해주는데, 상태 변경도 가능하여 디자이너가 원하는 시나리오를 직접 모두 테스트해볼 수 있고 웹으로 배포하기 때문에 특정 OS나 디바이스에 의존하지도 않는다. 배포 속도도 빠른 건 덤이고.

체크박스 상태를 조작하는 Storybook 화면

위 사진은 내가 실제로 한 프로젝트에서 사용하는 Storybook을 캡쳐해온 것이다. 사진에 보이는 건 체크박스 컴포넌트인데, 이 체크박스에 딸린 여러 상태 - 체크 여부, 활성화 여부, 체크박스 우측에 적힐 텍스트 - 를 직접 조작할 수 있는 UI를 화면 중앙에서 찾아볼 수 있을 것이다. 함수와 같은 조금 처리하기 까다로운 것들 빼고는 거의 조작을 지원하기 때문에, 프로젝트 초기에 디자인 시스템을 밀 때 큰 도움이 되는 도구다.

접근

자, 그럼 이 도구를 어떻게 Android에서 사용할 수 있을까? 가능한 방법은 아래의 2가지가 있다:

  • 시장에 존재하는 유사한 라이브러리/툴 사용
  • 직접 구현

대안 분석

대안으로 고려할 수 있는 후보는 2가지가 있었고, 결론부터 말하면 둘 다 요구 조건을 충족하지 못했다.

Storytale

https://github.com/Kotlin/Storytale

Storytale은 쉽게 말해 Compose Multiplatform 전용 Storybook이다.

Storybook의 핵심 기능을 그대로 제공하며 웹으로 배포하는 것까지 동일하게 가져가려고 했다는 점에서, 만약 이 라이브러리에 문제가 없었다면 아마 이걸 채택했을 것이다.

그러나 안타깝게도 사용이 어려운 몇 가지 이유가 있었는데:

  • 마지막 main 브랜치 활동이 4개월 전. 유지보수가 멈췄다고 판단하기에 무리가 없는 기간이다.
  • Kotlin 버전을 내려야 함. 개발이 오래 전 멈춰서 그런 것인지 사용을 위해서는 프로젝트의 Kotlin 버전을 낮춰야 했다.

유지보수가 멈춘 건 그렇다 쳐도 Kotlin 버전을 내리는 게 큰 문제였다. 아무래도 버그 및 취약성 패치를 반영하기 위해 버전을 올리는 건데, 반대로 버전을 내리라는 건 이미 과거 버전에서 알려진 취약성도 그대로 감당하라는 소리니까. 그래서 선택하지 않았다.

Showkase

https://github.com/Airbnb/Showkase

에어비엔비에서 만든 컴포넌트 테스트 툴이다.

어노테이션 하나로 손쉽게 테스트 케이스를 생성할 수 있다는 점에서 사용이 매우 간단한 편.

다만 이 툴에도 역시 문제가 몇 개 있었는데:

  • Android 기기가 필수. 실 기기든 에뮬레이터든 무조건 Android 기기가 필요하다.
  • 스크린샷 기반 웹 배포. 웹 배포가 불가능하진 않은 것으로 보이나 상태 조작은 불가능한 정적인 스크린샷 기반 기능이다.

내가 이걸 적용하려고 했던 프로젝트는 사이드 프로젝트였기 때문에 주 소통 채널이 온라인이었다. 이런 점에서 Android Studio에 기기를 연결해 빌드 후 전달해줘야 하는 Showkase는 사용하기가 좀 어려웠다. 더해서 Storybook의 장점 중 하나인 상태 조작이 안 된다는 것도 채택하지 못했던 이유.

결론

시장에 대안이 없는 건 아니었으나 우리 환경에 실질적으로 맞는 건 없었다.

그럼 남은 건? 직접 구현하는 방법 말고는 없다! 나는 Codex 그리고 Claude와 함께였기에 무서울 게 없기도 했다. 그럼 다음 파트에서는 이걸 구현하기 위해 어떻게 설계했는지를 간단히 소개해보겠다.

설계

가장 먼저 일러둘 것은 나는 내가 구현할 Android 환경에서 사용 가능한 Storybook을 '디자인 시스템 카탈로그'(줄여서 카탈로그)로 명명했다는 점. 따라서 아래부터는 '카탈로그'라는 단어가 나오면 Android 측 툴을, 'Storybook'은 React 측 툴을 의미한다고 알고 가면 된다.

이어서, Storybook을 카탈로그로 구현하기 위해서는 Storybook의 주요 특징을 먼저 살펴볼 필요가 있다. 이걸 알아야 적합한 설계 방향을 결정할 수 있으니까.

  • 하나의 UI 코드를 Storybook과 프로덕션에서 재사용한다.
  • 웹 빌드 및 배포가 된다.
  • 공용 컴포넌트의 상태를 직접 조작할 수 있어야 한다.
  • Storybook이 프로덕션에 영향을 주어서는 안 된다.

이 4가지 특성을 Android 환경에서 어떻게 충족할 수 있었을까? 다음을 보라:

단일 UI 코드 재사용 및 Storybook 모듈 고립

이건 멀티 모듈 아키텍처 적용 및 디자인 시스템 모듈 및 카탈로그 모듈 분리로 가능하다.

사실상 대부분의 Android 프로젝트에서 멀티 모듈 아키텍처를 쓸 것이고, 일반적으로 :designsystem 모듈을 분리해서 쓰는 것도 멀티 모듈 아키텍처라면 거의 같이 따라오는 부분이므로 그렇게 큰 요구 사항은 아니다. 여기에 카탈로그 모듈 :catalog만 새로 만들어주면 된다. 그러면, :catalog 모듈과 :app 및 :feature:* 모듈이 :designsystem 모듈의 공용 Composable을 재사용하는 구조가 자연스럽게 구성된다.

또한 :catalog가 Android 앱에서 사용되는 모듈에 의존되지 않도록 제한하여, 앱과는 아무런 관련이 없는 테스트 전용 도구가 앱에 영향을 주지 않게 했다. 다만 이건 단순히 규칙 수준에 해당하는 결정이었고, 의존 관계를 컴파일 타임에 정적으로 검증하도록 한 건 아니었다는 점은 별도로 알린다. 그래도 프로젝트 내 에이전트를 위한 지침에 강하게 넣어 두었었기 때문에, 에이전트가 이를 위반하는 일은 다행히도 없었다.

웹 빌드

이건 Compose Multiplatform(이하 CMP)으로 가능하다.

CMP는 Android, iOS와 Web 환경에서의 크로스 플랫폼 빌드를 지원한다. 단일 UI 코드를 웹에서도 빌드 가능하도록 하기 위해서는 CMP는 사실상 필수적인 선택인 것이다. 따라서, :designsystem 모듈을 기존처럼 Android Native 모듈이 아닌 Kotlin Multiplatform(이하 KMP) 모듈로 선언해줘야 한다. 이렇게 하면 :designsystem 모듈에서 Android API를 직접 호출할 수 없다는 불편함이 발생하긴 하지만, :designsystem 모듈이 더 이상 Android API에 의존하지 않게 되어 결합도를 낮추는 효과가 부수적으로 있다.

공용 컴포넌트의 상태 직접 조작

코드 확인하기: 자료형별 콘솔

이게 좀 어려운 문제였는데, 나는 비교적 간단한 자료형만 조작이 가능하도록 제한했고, 조작을 위한 콘솔 Composable은 KSP가 컴파일 타임에 생성하도록 했다.

여기서 간단한 자료형이라 함은 정수, 실수, 부울, 문자열과 열거형을 말한다. 이 5개 자료형은 매우 간단하고 Kotlin에서도 거의 원시형 수준으로 지원해주기 때문에 구현이 어렵지 않았다. 반면, 이 5개의 복잡도를 넘어서는 사용자 지정 자료형은 구현 난이도가 많이 높아지는 것에 비하여 얻을 수 있는 실익이 적어 포기했고, 함수는 이 카탈로그의 주 목적이 디자인 시스템 컴포넌트 테스트임을 생각했을 때 비즈니스 로직 검증이 이 도구의 책임이 아니었기 때문에 제외하기로 결정했다.

코드 확인하기: KSP 프로세서

또한, 이 부분 구현에서도 역시 구현 난이도가 높아 타협한 부분이 있다. 에이전트에게 함수이기도 한 Composable의 매개변수 타입을 컴파일 타임에 전부 긁어오는 게 KSP로는 꽤나 어려울 거라는 기술 분석을 받아서, 대안이 필요했다. 그래서 선택한 방식은 다음과 같았다:

  • 카탈로그에 공개하고 싶은 타입을 어댑터로 빼도록 했다. 정적 분석이 가능하면 좋았겠지만 그렇지 않았기에, 사용자가 함수나 사용자 지정 자료형 등 지원되지 않는 타입은 뺄 수 있게 기반을 마련했다.
  • 어댑터 클래스를 KSP가 읽고 컴파일 타임에 카탈로그 코드를 생성하도록 했다. KSP는 개발자가 공개한 계약을 어댑터에서 확인하고, 공개된 변수에 대해서 각 자료형마다 미리 정해진 콘솔 코드를 조합함으로써 상태 조작을 위한 콘솔 코드를 생성한다.

이렇게 하면 빠른 시간 내에 구현이 가능하다는 장점이 있었고, 시간 부족으로 정적 검증에서 걸러내지 못한 제약 사항은 위에서 언급한 것과 비슷하게 에이전트 지침에 추가하여 에이전트가 허용되지 않은 자료형을 어댑터에 공개하지 않도록 제한했다.

웹 배포

웹 배포는 GitHub Actions CI/CD와 GitHub Pages로 했다.

PR이 요청될 때, CI가 실행되며 해당 브랜치의 카탈로그를 미리 빌드해놓는다. 빌드한 결과물은 Actions 아티팩트로 달아두고. 그리고 PR이 병합되면 CD가 실행되며 해당 PR의 아티팩트를 찾아 거기서 빌드 결과물을 모아서 GitHub Pages에 배포한다.

또한, URL은 단일 고정하여 PR이 병합될 때마다 다시 링크를 전달해줘야 할 수고를 덜었다. 물론, 각 PR 요청에 딸린 프리뷰 링크를 필요로 한다면 그건 별도로 복사해줘야 하기는 하다.

평가

긍정적이었던 점

일단 의외로 CMP를 채택하면서 얻었던 장점은 :designsystem 모듈에서 Android 의존성이 완전히 떨어져나가고 모듈 책임이 더 명확해진다는 점이었다.

기존대로였다면 폼 팩터를 Android OS로만 전제하기에 :designsystem 모듈도 Android Native 모듈로 선언해야 했었을 것이다. 그러나 나의 경우 웹과 앱 모두에서 빌드가 가능해야 했고, 웹이 모르는 Android API를 :designsystem 모듈에 포함할 수 없었기에, :designsystem 모듈을 순수 Kotlin 모듈로 구성해야 했다. 이렇게 하니까 :designsystem 모듈이 Android API에 접근할 수 있는 가능성이 아예 차단되었고, Android API를 사용한 로직 구현 책임 역시 원래 이 부분을 맡았어야 했을 앱 및 기능 모듈로 넘어갔기 때문에, 모듈 간 책임 분리가 더욱 명확하게 이루어지는 결과를 낼 수 있었다.

그리고 역시 의도했던 대로 초반 디자인 시스템 피드백을 할 때, 카탈로그 하나만 넘겨주면 되니까 소통 비용이 줄긴 했다.

부정적이었던 점

가장 큰 문제는 디자인 시스템 구현이 끝나면 붕 뜬다는 거다.

얘의 장점이자 단점이기도 한데, :designsystem 모듈이 CMP라서 Android Native API를 쓸 수가 없다. 그래서 개발을 하면서 화면을 조립하다 보면 결국 어느 시점에서 기능 모듈로 들어와 Android API를 호출해야 하는데, 이 지점부터는 카탈로그에 해당 Composable을 배포할 수 없기 때문에 카탈로그를 통한 테스트가 불가능하다. 쉽게 말해, 공용 Composable 테스트에는 효과적이지만, 화면 단으로 올라오게 되면 테스트가 어려워진다는 것이다. 그래서 실제 화면을 쳐내던 단계에서는 카탈로그를 사실상 사용하지 않았었다.

이 부분을 해결하려면 화면조차도 :designsystem 모듈에서 CMP로만 구현하고, 외부 로직은 전부 매개변수로 넣어주는 식으로 구현을 해야 할 텐데, 그게 이론적으로나 되지 실무적으로 도움이 되는 방식일지에 대해서는 잘 모르겠다. 확신이 어렵다.

결론

아무튼 꽤나 인상적인 경험이었다. React의 접근 방식을 Android에 가져와 녹여낸 경험이었다.

전체 게시글

1 / 4