Monday, August 28, 2023

Flutter 패키지 제작, 아이디어부터 pub.dev 배포까지

Google이 선보인 Flutter는 단일 코드베이스로 모바일, 웹, 데스크톱을 아우르는 미려하고 성능 좋은 애플리케이션을 구축할 수 있는 혁신적인 UI 툴킷입니다. 이 강력한 Flutter 프레임워크의 성장을 견인하는 핵심 동력 중 하나는 바로 방대하고 활발한 생태계이며, 그 심장에는 패키지 매니저인 pub.dev가 있습니다.

패키지는 잘 만들어진 바퀴와 같습니다. 다른 개발자들이 이미 해결해 놓은 문제들을 내 프로젝트에 손쉽게 통합하여, 개발 속도를 비약적으로 향상시키고 코드의 안정성을 높여줍니다. 우리는 대부분 패키지의 '소비자'로서 이러한 혜택을 누려왔습니다. 하지만 진정한 개발자로서의 성장은 소비자를 넘어 '생산자'가 되는 경험에서 비롯됩니다. 나만의 아이디어를 코드로 구현하고, 그것을 체계적으로 패키지화하여 전 세계 개발자들과 공유하는 과정은 단순히 코드를 재사용하는 차원을 넘어섭니다.

이 글은 단순한 명령어 나열식 가이드가 아닙니다. 왜 우리가 패키지를 만들어야 하는지에 대한 근본적인 고찰에서 시작하여, 잘 설계된 패키지의 구조를 해부하고, 신뢰성 있는 코드를 작성하며, 자동화된 테스트를 구축하고, 마침내 pub.dev에 당신의 이름을 건 작품을 공개하고 지속적으로 관리하는 전 과정을 아우르는 깊이 있는 여정이 될 것입니다. 이 여정의 끝에서 당신은 Flutter 생태계에 기여하는 자랑스러운 개발자로 거듭날 것입니다.

1. 왜 우리는 Flutter 패키지를 만들어야 하는가?

이미 수많은 훌륭한 패키지가 존재하는 세상에서, 굳이 시간과 노력을 들여 나만의 패키지를 만들어야 할 이유가 있을까요? 대답은 명확합니다. '그렇습니다'. 패키지 제작은 단순히 코드를 어딘가에 저장하는 행위가 아니라, 소프트웨어 공학의 핵심 원칙들을 실천하고 개발자로서 한 단계 도약하는 과정입니다.

  • 궁극의 코드 재사용성 (DRY 원칙의 실현): "반복하지 마라(Don't Repeat Yourself)"는 프로그래밍의 오랜 격언입니다. 여러 프로젝트에서 공통적으로 사용되는 커스텀 위젯, API 통신 모듈, 상태 관리 로직, 유틸리티 함수 등이 있다면, 이를 패키지로 분리하는 것은 최고의 선택입니다. 새로운 프로젝트를 시작할 때마다 기존 코드를 찾아 복사-붙여넣기 하는 방식은 잠재적인 버그를 여러 프로젝트에 동시에 전파하는 위험한 행위입니다. 버그 수정이 필요할 때 모든 프로젝트를 찾아다니며 동일한 코드를 수정해야 하는 악몽을 겪게 될 것입니다. 패키지는 pubspec.yaml에 단 한 줄을 추가하는 것으로 이 모든 문제를 해결합니다.
  • 견고한 아키텍처 구축과 관심사 분리: 애플리케이션의 규모가 커질수록 모든 기능이 한곳에 뒤엉킨 거대한 단일체(Monolithic) 구조는 관리가 불가능해집니다. 인증, 데이터, UI 컴포넌트 등 핵심 기능들을 독립적인 패키지로 분리하면 프로젝트의 구조가 명확해집니다. 이는 자연스럽게 '관심사 분리(Separation of Concerns)' 원칙을 따르게 만들어, 각 모듈의 독립성을 높이고 테스트 용이성을 극대화하며, 유지보수를 용이하게 만듭니다. 예를 들어, 기업에서는 사내 디자인 시스템을 별도의 패키지로 만들어 여러 앱에서 일관된 UI/UX를 유지하기도 합니다.
  • 효율적인 팀 협업 촉진: 기능 단위로 잘 분리된 패키지는 팀의 협업 방식을 혁신합니다. 인증 패키지는 A팀이, 결제 패키지는 B팀이 책임지는 식으로 역할과 책임 소재가 명확해집니다. 각 팀은 다른 팀의 코드에 직접적인 영향을 주지 않고 독립적으로 기능을 개발하고 테스트하며 배포할 수 있습니다. 이는 개발 속도를 높이고 병목 현상을 줄이는 효과적인 전략입니다.
  • 오픈소스 생태계 기여와 폭발적인 성장: 당신이 만든 유용한 기능이 전 세계 수많은 개발자들의 시간을 절약해주고 문제를 해결해준다고 상상해보세요. 당신의 코드를 패키지로 만들어 공개하는 것은 Flutter 생태계를 더욱 풍성하게 만드는 의미 있는 기여입니다. 또한, 다른 개발자들로부터 받는 피드백, 이슈 리포트, 코드 기여(Pull Request)를 통해 혼자서는 발견하기 어려웠던 문제점을 개선하고, 더 나은 코드 작성법을 배우며 폭발적으로 성장하는 기회를 얻게 됩니다.
  • 강력한 기술적 브랜딩과 포트폴리오: 이력서에 나열된 프로젝트 목록보다, 많은 '좋아요(Likes)'와 높은 'Pub Points'를 받은 잘 관리된 pub.dev 패키지 하나가 당신의 실력을 훨씬 더 강력하게 증명할 수 있습니다. 이는 당신이 단순히 코드를 작성할 줄 아는 것을 넘어, 문서화, 테스트, 버전 관리, 다른 개발자와의 소통 능력까지 갖춘 전문가임을 보여주는 확실한 증거가 됩니다.

2. 패키지와 플러그인: 근본적인 차이 이해하기

Flutter에서 '패키지'라는 용어는 넓은 의미로 사용되지만, 기술적으로는 두 가지 유형으로 나뉩니다. 무엇을 만들고 싶은지에 따라 올바른 유형을 선택하는 것이 매우 중요합니다.

패키지 (Package, 또는 Dart-only Package)

가장 일반적인 형태의 패키지로, 순수하게 Dart 언어로만 작성된 코드를 포함합니다. 플랫폼(Android, iOS 등)의 네이티브 기능에 접근할 필요가 없는 모든 것이 여기에 해당됩니다.

  • 주요 사용 사례:
    • 재사용 가능한 UI 위젯 (예: 커스텀 버튼, 차트 라이브러리)
    • 상태 관리 솔루션 (예: BLoC, Provider의 핵심 로직)
    • 네트워크 통신, 직렬화(JSON parsing) 등 비즈니스 로직
    • 날짜/시간 포맷팅, 문자열 조작 등 유틸리티 함수 모음
    • Dart 언어 자체의 기능을 확장하는 라이브러리
  • 특징: 플랫폼 종속성이 없기 때문에 Flutter가 지원하는 모든 플랫폼(모바일, 웹, 데스크톱)에서 동일하게 동작합니다. 이 글에서는 바로 이 Dart-only 패키지 제작에 집중할 것입니다.

플러그인 (Plugin, 또는 Platform-specific Package)

플러그인은 Dart 코드와 함께 Android(Kotlin/Java) 또는 iOS(Swift/Objective-C) 같은 플랫폼별 네이티브 코드를 포함하는 특별한 종류의 패키지입니다. Dart만으로는 접근할 수 없는 디바이스의 고유 기능이나 OS 서비스를 사용해야 할 때 필요합니다.

  • 주요 사용 사례:
    • 하드웨어 API 접근: 카메라, GPS, 블루투스, 자이로스코프 센서, 배터리 상태 등
    • OS 고유 서비스 이용: 푸시 알림, 인앱 결제, 파일 시스템 접근, 주소록, 캘린더 등
    • 기존 네이티브 라이브러리나 SDK 연동 (예: Firebase, 지도 SDK)
    • 네이티브 UI 컴포넌트를 Flutter 위젯 트리에 통합 (예: WebView, 광고 배너)
  • 작동 원리: 플랫폼 채널 (Platform Channels)

    플러그인은 '플랫폼 채널'이라는 메커니즘을 통해 Flutter(Dart) 코드와 네이티브 코드 간의 통신을 수행합니다. 이는 마치 비동기 메시지를 주고받는 다리와 같습니다. Dart 코드에서 네이티브 기능을 호출하면(예: `MethodChannel.invokeMethod`), 해당 메시지가 플랫폼 채널을 통해 네이티브 코드로 전달되고, 네이티브 코드는 요청을 처리한 후 그 결과를 다시 채널을 통해 Dart 코드로 반환합니다.

  • (심화) FFI (Foreign Function Interface): 최근에는 `dart:ffi`를 사용하여 C/C++/Rust 등으로 작성된 네이티브 라이브러리의 함수를 Dart에서 직접 호출하는 방식도 사용됩니다. 플랫폼 채널을 거치는 것보다 오버헤드가 적어 고성능 연산이나 실시간 데이터 처리에 더 유리할 수 있습니다.

결론적으로, 모든 플러그인은 패키지이지만, 모든 패키지가 플러그인은 아닙니다. 만들고자 하는 기능이 순수 Dart 코드로 해결 가능한지, 아니면 네이티브 API 호출이 필수적인지를 먼저 판단하는 것이 첫걸음입니다.

3. 완벽한 시작을 위한 개발 환경 구축

본격적인 패키지 개발에 앞서, 개발 환경이 올바르게 설정되었는지 확인하는 것은 필수입니다. 이미 Flutter 앱 개발에 익숙하다면 대부분의 준비가 되어 있겠지만, 다시 한번 점검하고 넘어가겠습니다.

Flutter SDK 설치 및 확인

Flutter 패키지는 Flutter SDK를 기반으로 동작합니다. 당연히 Flutter SDK가 설치되어 있어야 합니다.

  1. Flutter 공식 웹사이트에서 자신의 운영체제에 맞는 최신 안정(Stable) 버전의 Flutter SDK를 다운로드하고 압축을 해제합니다.
  2. 시스템 환경 변수 PATH에 압축 해제한 폴더의 flutter/bin 디렉토리 경로를 추가하여 터미널 어디에서든 flutter 명령어를 사용할 수 있도록 설정합니다.
  3. 터미널을 새로 열고 아래 명령어를 실행하여 설치 상태를 종합적으로 점검합니다.
$ flutter doctor -v

flutter doctor 명령어는 Flutter 개발에 필요한 모든 요소(Android toolchain, Xcode, Chrome, 연결된 디바이스 등)의 상태를 진단해주는 매우 유용한 도구입니다. -v 플래그를 추가하면 설치된 경로 등 더 상세한 정보를 보여줍니다. 만약 체크리스트 앞에 [!] 또는 [X] 표시와 함께 문제점이 보고된다면, 해당 메시지의 안내에 따라 문제를 반드시 해결해야 합니다.

Dart SDK 확인

Flutter SDK는 특정 버전의 Dart SDK를 내장하고 있습니다. 따라서 Flutter를 정상적으로 설치했다면 별도로 Dart SDK를 설치할 필요가 없습니다. 다음 명령어로 Dart가 정상적으로 인식되는지 확인할 수 있습니다.

$ dart --version

코드 에디터와 확장 프로그램

메모장으로도 코딩은 가능하지만, 생산성을 극대화하기 위해서는 IDE(통합 개발 환경) 사용이 필수적입니다. VS Code 또는 Android Studio/IntelliJ를 사용하는 것을 강력히 권장합니다.

  • VS Code 추천 확장 프로그램:
    • Dart: Dart 언어 지원(코드 분석, 자동 완성, 포맷팅).
    • Flutter: Flutter 프레임워크 지원(디버깅, 핫 리로드, 위젯 가이드).
    • Awesome Flutter Snippets: 자주 사용되는 위젯과 코드를 빠르게 작성할 수 있는 스니펫 모음.
    • Pubspec Assist: VS Code 내에서 pubspec.yaml 파일에 의존성을 쉽게 추가할 수 있게 도와줍니다.

(심화) 버전 관리 전략: FVM

패키지 개발자는 다양한 Flutter 버전에 대한 호환성을 고려해야 할 때가 많습니다. 이럴 때 FVM (Flutter Version Management)은 매우 유용한 도구입니다. FVM을 사용하면 프로젝트별로 다른 Flutter SDK 버전을 지정하고 손쉽게 전환할 수 있어, 특정 버전에서 패키지가 정상적으로 동작하는지 테스트하기 용이합니다.

4. 패키지 프로젝트 생성 및 구조 해부

모든 준비가 끝났다면, 이제 드디어 당신만의 패키지를 생성할 차례입니다. Flutter CLI는 패키지 개발에 필요한 표준 디렉토리 구조와 기본 파일들을 자동으로 생성해주는 편리한 명령어를 제공합니다.

패키지 프로젝트 생성 명령어

터미널에서 패키지를 생성하고 싶은 상위 디렉토리로 이동한 후, 다음 명령어를 실행합니다. 패키지 이름은 Dart 컨벤션에 따라 소문자와 언더스코어(_)로만 구성해야 합니다(lower_case_with_underscores).

# 기본 패키지 생성 명령어
$ flutter create --template=package simple_validator

# (심화) 조직명(org)을 지정하여 생성
$ flutter create --template=package --org com.example simple_validator

--template=package 플래그는 Flutter 앱이 아닌 재사용 가능한 패키지를 생성하도록 지시하는 핵심적인 옵션입니다. --org 옵션을 사용하면 생성되는 네이티브 관련 파일(플러그인의 경우)의 패키지 이름을 지정할 수 있어, 이름 충돌을 방지하고 프로젝트를 체계적으로 관리하는 데 도움이 됩니다.

생성된 패키지 구조 상세 분석

명령어를 실행하면 패키지 이름과 동일한 디렉토리(simple_validator/)가 생성됩니다. 이 안에 있는 각 파일과 폴더는 저마다 중요한 역할을 수행하며, 이 구조를 이해하는 것은 패키지 개발의 첫걸음입니다.


simple_validator/
├── .gitignore             # Git에서 추적하지 않을 파일/폴더 목록
├── .metadata              # Flutter 도구가 사용하는 메타데이터 파일
├── CHANGELOG.md           # 버전별 변경 이력 문서
├── LICENSE                # 패키지의 사용 허가 조건(라이선스)
├── README.md              # 패키지 사용 설명서
├── analysis_options.yaml  # 정적 코드 분석(Linter) 규칙 설정
├── example/               # 패키지 사용 예제 Flutter 앱
│   ├── ...
├── lib/                   # 패키지의 핵심 Dart 코드
│   ├── simple_validator.dart # 패키지의 공개 API를 노출하는 메인 파일
│   └── src/                 # 패키지 내부에서만 사용하는 비공개 구현 코드
│       └── ...
├── pubspec.yaml           # 패키지의 모든 메타데이터와 의존성 정의
└── test/                  # 패키지 테스트 코드
    └── simple_validator_test.dart
  • lib/: 패키지의 심장입니다. 패키지 사용자가 import 할 수 있는 모든 코드는 이 디렉토리 내에 있어야 합니다.
    • lib/package_name.dart: 이 파일은 패키지의 공개 API(Public API)를 외부에 노출하는 관문 역할을 합니다. 일반적으로 lib/src/ 폴더에 있는 내부 구현 파일들을 export 하는 코드가 작성됩니다.
    • lib/src/: 패키지의 실제 로직과 구현 코드가 위치하는 곳입니다. 이 폴더 안의 파일들은 패키지 외부에서 직접 import 하지 않는 것이 관례입니다. 이렇게 구현을 숨기는 것은 캡슐화 원칙을 지키고, 향후 내부 코드를 리팩토링하더라도 패키지 사용자에게 영향을 주지 않도록(breaking change를 방지) 도와줍니다.
  • pubspec.yaml: 패키지의 신분증이자 명세서입니다. 이 파일에는 패키지의 이름, 설명, 버전, 홈페이지 주소 등 모든 메타데이터와 다른 패키지에 대한 의존성이 정의됩니다. pub.dev는 이 파일의 정보를 기반으로 패키지 페이지를 구성하므로 매우 정확하고 상세하게 작성해야 합니다. (자세한 내용은 뒷부분에서 다룹니다.)
  • README.md: 패키지의 얼굴이자 가장 중요한 사용자 매뉴얼입니다. 다른 개발자가 pub.dev에서 당신의 패키지를 처음 발견했을 때 가장 먼저 보게 되는 내용입니다. 이 패키지가 어떤 문제를 해결하는지, 어떻게 설치하고 사용하는지, 주요 기능은 무엇인지 명확하고 친절한 예제 코드와 함께 설명해야 합니다.
  • CHANGELOG.md: 패키지의 성장 일기입니다. 각 버전을 릴리스할 때마다 어떤 기능이 추가(Added)되었고, 무엇이 변경(Changed)되었으며, 어떤 버그가 수정(Fixed)되었는지 상세히 기록하는 파일입니다. 사용자는 이 파일을 통해 버전 업데이트 시 어떤 변화가 있는지 쉽게 파악할 수 있습니다.
  • LICENSE: 패키지의 법적 계약서입니다. 다른 개발자들이 당신의 코드를 어떤 조건 하에서 사용할 수 있는지(복제, 수정, 배포, 상업적 이용 등)를 명시하는 법적 문서입니다. 특별한 이유가 없다면 MIT, Apache 2.0, BSD-3-Clause 등 널리 사용되는 관대한 오픈소스 라이선스를 사용하는 것이 좋습니다.
  • test/: 패키지의 품질과 신뢰성을 보장하기 위한 자동화된 테스트 코드가 위치하는 곳입니다. 잘 작성된 테스트는 버그를 예방하고, 향후 리팩토링에 대한 자신감을 줍니다.
  • example/: 패키지의 살아있는 쇼케이스이자 가장 강력한 문서입니다. 이 폴더는 그 자체로 완전한 Flutter 애플리케이션이며, 개발 중인 패키지를 실제로 어떻게 사용하는지 보여주는 예제 코드를 담고 있습니다. 사용자는 이 앱을 직접 실행해보며 패키지의 기능을 직관적으로 이해할 수 있고, 개발자에게는 실제 앱 환경에서 패키지를 테스트하는 훌륭한 놀이터가 됩니다.
  • analysis_options.yaml: 코드 스타일과 잠재적 오류를 검사하는 정적 분석기(Linter)의 규칙을 설정하는 파일입니다. 기본적으로 flutter_lints 패키지의 규칙을 따르도록 설정되어 있으며, 이를 통해 일관되고 품질 높은 코드를 작성하도록 강제할 수 있습니다.

5. 핵심 기능 구현: 실용적인 Validator 제작

이제 패키지의 뼈대에 살을 붙여 실제 기능을 구현해 보겠습니다. 이메일 형식, 빈 문자열, 최소/최대 길이 등을 검증하는 간단하면서도 실용적인 Validator 클래스를 만들어 봅시다.

API 설계 및 파일 구조화

좋은 패키지는 사용하기 쉬운 API를 제공해야 합니다. 우리는 Validator.isEmail('...'), Validator.hasMinLength('...', 6) 과 같이 직관적으로 사용할 수 있도록 모든 검증 메서드를 정적(static) 메서드로 제공할 것입니다. 또한 앞서 설명한 대로, 구현은 lib/src/에, 공개는 lib/simple_validator.dart에서 하는 구조를 따릅니다.

1. lib/simple_validator.dart 파일 수정

이 파일은 외부에서 우리 패키지를 사용할 때 진입점이 되는 파일입니다. 기존 내용을 모두 지우고, 내부 구현 파일을 외부에 노출시키기 위한 export 구문만 남깁니다.


/// 간단하면서도 실용적인 문자열 유효성 검사 기능을 제공하는 라이브러리입니다.
///
/// 이메일 형식, 빈 값 여부, 최소/최대 길이 등 일반적인 검증 시나리오를 지원합니다.
library simple_validator;

// `src` 디렉토리에 있는 내부 구현 파일을 외부에 공개(export)합니다.
// 이렇게 하면 사용자는 `package:simple_validator/simple_validator.dart`만 import하면
// Validator 클래스를 사용할 수 있습니다.
export 'src/validator.dart';

2. lib/src/validator.dart 파일 생성 및 구현

이제 실제 로직이 들어갈 파일을 만듭니다. lib/ 폴더 아래에 src 폴더를 만들고, 그 안에 validator.dart 파일을 새로 생성하여 아래 코드를 작성합니다. 이때, 문서 주석(Doc Comments)을 꼼꼼하게 작성하는 것이 매우 중요합니다.


/// 다양한 문자열 유효성 검사 메서드를 제공하는 유틸리티 클래스입니다.
/// 모든 메서드는 정적(static)으로 제공되어 인스턴스화할 필요 없이 사용할 수 있습니다.
class Validator {
  // 클래스가 인스턴스화되는 것을 방지하기 위해 private 생성자를 선언합니다.
  Validator._();

  /// 주어진 문자열이 유효한 이메일 형식인지 확인합니다.
  ///
  /// 간단한 정규 표현식을 사용하여 일반적인 이메일 형식을 검사합니다.
  /// 완벽한 RFC 5322 표준을 검증하지는 않습니다.
  ///
  /// 예시:
  /// ```dart
  /// Validator.isEmail('test@example.com'); // true
  /// Validator.isEmail('test.com'); // false
  /// ```
  static bool isEmail(String email) {
    if (email.isEmpty) return false;
    // 일반적인 이메일 검증을 위한 정규 표현식
    final RegExp emailRegExp = RegExp(
      r'^[a-zA-Z0-9.a-zA-Z0-9.!#$%&\'*+-/=?^_`{|}~-]+@[a-zA-Z0-9]+\.[a-zA-Z]+',
    );
    return emailRegExp.hasMatch(email);
  }

  /// 주어진 문자열이 `null`이 아니며 비어있지 않은지 확인합니다.
  ///
  /// `trim()`을 호출하여 문자열의 앞뒤 공백을 제거한 후 비어있는지 검사합니다.
  ///
  /// 예시:
  /// ```dart
  /// Validator.isNotEmpty('hello'); // true
  /// Validator.isNotEmpty('  ');    // false
  /// Validator.isNotEmpty(null);    // false
  /// Validator.isNotEmpty('');      // false
  /// ```
  static bool isNotEmpty(String? text) {
    return text?.trim().isNotEmpty ?? false;
  }

  /// 주어진 문자열이 최소 `minLength` 이상의 길이를 갖는지 확인합니다.
  ///
  /// `null`이거나 `minLength`보다 짧으면 `false`를 반환합니다.
  ///
  /// [text] 검사할 문자열.
  /// [minLength] 최소 길이 조건.
  static bool hasMinLength(String? text, int minLength) {
    if (text == null) return false;
    return text.length >= minLength;
  }
}

///로 시작하는 문서 주석은 IDE에서 코드를 사용할 때 툴팁으로 표시되거나, dart doc 명령어를 통해 공식 API 문서를 생성하는 데 사용됩니다. 다른 개발자들이 내 코드를 쉽게 이해하고 사용할 수 있도록 돕는 매우 중요한 습관입니다. 특히, ```dart ... ``` 코드 블록을 사용하여 실제 사용 예제를 보여주는 것은 매우 효과적입니다.

6. 신뢰의 기반, 자동화된 테스트 구축

기능 구현을 마쳤다면, 이제 이 코드가 우리가 의도한 대로 정확하게 동작하는지 증명해야 합니다. 자동화된 테스트는 패키지의 신뢰성을 보장하는 안전망입니다. 테스트는 단순히 버그를 찾는 행위를 넘어, 코드의 동작을 보증하고, 살아있는 문서의 역할을 하며, 미래의 리팩토링 과정에서 기능이 망가지는 회귀(Regression)를 방지합니다.

단위 테스트(Unit Test) 작성하기

단위 테스트는 코드의 가장 작은 독립적인 단위(함수, 메서드, 클래스)를 개별적으로 테스트하는 것입니다. 우리는 방금 만든 Validator 클래스의 각 메서드에 대해 예상되는 모든 시나리오를 검증하는 테스트 코드를 작성할 것입니다.

test/simple_validator_test.dart 파일을 열고, 기존 내용을 다음 코드로 교체합니다. 좋은 테스트 코드는 **AAA 패턴(Arrange-Act-Assert)**을 따르는 것이 좋습니다.

  • Arrange (준비): 테스트에 필요한 변수나 상태를 설정합니다.
  • Act (실행): 테스트하려는 메서드를 호출합니다.
  • Assert (검증): 실행 결과가 예상과 일치하는지 확인합니다.

// flutter_test 패키지는 테스트를 위한 다양한 유틸리티를 제공합니다.
import 'package:flutter_test/flutter_test.dart';
// 테스트할 대상인 우리 패키지를 import 합니다.
import 'package:simple_validator/simple_validator.dart';

void main() {
  // `group` 함수를 사용하여 연관된 테스트들을 묶어 가독성을 높입니다.
  group('Validator.isEmail', () {
    test('유효한 이메일 주소를 전달하면 true를 반환한다', () {
      // Arrange (준비)
      const validEmail = 'test@example.com';
      // Act (실행)
      final result = Validator.isEmail(validEmail);
      // Assert (검증)
      expect(result, isTrue);
    });

    test('여러 유효한 형식의 이메일 주소에 대해 true를 반환한다', () {
      expect(Validator.isEmail('user.name+tag@example.co.uk'), isTrue);
      expect(Validator.isEmail('john.doe123@sub.domain.org'), isTrue);
    });

    test('골뱅이(@)가 없는 이메일 주소는 false를 반환한다', () {
      expect(Validator.isEmail('testexample.com'), isFalse);
    });

    test('도메인이 없는 이메일 주소는 false를 반환한다', () {
      expect(Validator.isEmail('test@'), isFalse);
    });

    test('빈 문자열은 false를 반환한다', () {
      expect(Validator.isEmail(''), isFalse);
    });
  });

  group('Validator.isNotEmpty', () {
    test('내용이 있는 문자열은 true를 반환한다', () {
      expect(Validator.isNotEmpty('hello world'), isTrue);
    });

    test('공백만 있는 문자열은 trim 처리 후 false를 반환한다', () {
      expect(Validator.isNotEmpty('   '), isFalse);
    });

    test('빈 문자열은 false를 반환한다', () {
      expect(Validator.isNotEmpty(''), isFalse);
    });

    test('null 값은 false를 반환한다', () {
      expect(Validator.isNotEmpty(null), isFalse);
    });
  });

  group('Validator.hasMinLength', () {
    test('문자열 길이가 최소 길이와 같으면 true를 반환한다', () {
      expect(Validator.hasMinLength('password', 8), isTrue);
    });

    test('문자열 길이가 최소 길이보다 길면 true를 반환한다', () {
      expect(Validator.hasMinLength('long_password', 8), isTrue);
    });

    test('문자열 길이가 최소 길이보다 짧으면 false를 반환한다', () {
      expect(Validator.hasMinLength('short', 8), isFalse);
    });

    test('null 값을 전달하면 false를 반환한다', () {
      expect(Validator.hasMinLength(null, 5), isFalse);
    });
  });
}

테스트 작성이 완료되었다면, 터미널에서 패키지의 루트 디렉토리로 이동하여 다음 명령어를 실행합니다.

$ flutter test

All tests passed! 라는 메시지가 출력되면, 우리 패키지의 핵심 로직이 다양한 엣지 케이스에서도 안정적으로 동작함을 확신할 수 있습니다.

예제 앱으로 실제 동작 확인하기

단위 테스트가 논리의 정확성을 증명한다면, 예제 앱은 실제 Flutter UI 환경에서 패키지가 어떻게 사용되고 렌더링되는지 확인하는 데 필수적입니다. example/ 폴더는 그 자체로 완전한 Flutter 프로젝트이므로, 평소에 앱을 개발하듯 코드를 작성하고 실행할 수 있습니다.

example/lib/main.dart 파일을 열어 우리가 만든 Validator를 사용하는 간단한 로그인 폼을 만들어 봅시다. 이를 통해 개발 중인 패키지를 실제 앱처럼 사용하며 테스트하고 디버깅할 수 있습니다.

7. 세상에 공개하기: pub.dev 배포

패키지 개발과 테스트를 성공적으로 마쳤다면, 이제 당신의 결과물을 전 세계 Flutter 개발자들과 공유할 시간입니다. Flutter 패키지의 공식 저장소인 pub.dev에 배포하는 것은 몇 가지 준비와 명령어만으로 가능하지만, 되돌릴 수 없는 작업이므로 신중하게 진행해야 합니다.

배포 전 최종 체크리스트

패키지를 게시하기 전에, 다른 개발자들이 당신의 패키지를 신뢰하고 쉽게 사용할 수 있도록 몇 가지 중요한 파일을 최종적으로 점검해야 합니다. 이 과정은 pub.dev에서 높은 점수를 받는 데에도 큰 영향을 미칩니다.

  1. pubspec.yaml 업데이트: 패키지의 메타데이터를 완성합니다.
    
    name: simple_validator
    # 설명은 최소 60자 이상으로, 패키지의 목적과 기능을 명확하게 작성합니다.
    description: A simple and practical string validation library for Flutter. Supports email, not-empty, and min/max length checks.
    version: 1.0.0 # 유의적 버전(Semantic Versioning)을 따릅니다. 첫 배포는 1.0.0 또는 0.1.0으로 시작합니다.
    # 홈페이지와 소스코드 저장소 주소를 반드시 기입합니다.
    homepage: https://github.com/your_username/simple_validator 
    repository: https://github.com/your_username/simple_validator
    # 사용자들이 이슈를 제기할 수 있는 링크를 제공하면 좋습니다.
    issue_tracker: https://github.com/your_username/simple_validator/issues
    
    environment:
      sdk: '>=2.19.0 <4.0.0' # 패키지가 호환되는 Dart SDK 버전을 명시합니다.
      flutter: ">=1.17.0"
    
    dependencies:
      flutter:
        sdk: flutter
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
      flutter_lints: ^2.0.0
            
  2. README.md 작성: 패키지의 첫인상입니다. 패키지가 무엇인지, 어떤 기능이 있는지, 어떻게 설치(dependencies:)하고 사용하는지(간단한 코드 예제)를 상세하게 작성합니다. 필요하다면 GIF나 스크린샷을 첨부하여 사용성을 보여주는 것도 좋은 방법입니다.
  3. CHANGELOG.md 작성: 첫 배포이므로, 초기 버전에 대한 설명을 작성합니다. 예: ## 1.0.0 - Initial release. Added email, isNotEmpty, and hasMinLength validators.
  4. LICENSE 파일 확인: 어떤 라이선스를 사용할지 결정하고 파일의 내용이 올바른지 확인합니다. GitHub에서 프로젝트 생성 시 라이선스를 선택하면 자동으로 내용이 채워집니다. MIT 라이선스가 가장 널리 사용됩니다.
  5. 정적 분석 및 포맷팅: 코드에 잠재적인 문제가 없는지, 그리고 일관된 스타일을 유지하는지 확인합니다.
    
    # 코드 분석 실행 (경고나 에러가 없어야 합니다)
    $ flutter analyze
    
    # 전체 코드 포맷팅 실행
    $ dart format .
            
  6. 테스트 최종 실행: 배포 직전 모든 테스트가 통과하는지 다시 한번 확인합니다.
    $ flutter test

배포 명령어 실행

모든 준비가 끝났다면, 드디어 배포 명령어를 실행할 차례입니다.

1. 드라이 런 (Dry Run - 예행 연습):

실제로 패키지를 게시하기 전에, 배포 과정에서 발생할 수 있는 문제점들을 미리 시뮬레이션 해보는 것이 매우 중요합니다. --dry-run 플래그는 실제 서버에 업로드하는 것 외에 모든 검증 과정을 동일하게 수행합니다.

$ flutter pub publish --dry-run

이 명령어는 pubspec.yaml, README.md, LICENSE 등의 파일이 잘 준비되었는지, 문서 주석이 잘 달려있는지, 코드에 문제는 없는지 등을 종합적으로 검사합니다. "Package has 0 warnings." 라는 메시지가 나타나면 모든 준비가 완료된 것입니다. 만약 경고나 에러가 출력된다면, 메시지를 주의 깊게 읽고 해당 문제를 모두 수정한 후 다시 시도해야 합니다.

2. 실제 배포 (Publish):

드라이 런을 성공적으로 통과했다면, 이제 진짜 배포를 진행합니다. 이 명령어는 되돌릴 수 없으니 신중하게 실행하세요.

$ flutter pub publish

이 명령어를 처음 실행한다면, pub.dev에 인증하기 위해 Google 계정으로 로그인하라는 메시지와 함께 웹 브라우저가 열립니다. 로그인을 완료하고 권한을 부여하면, 터미널에서 배포가 계속 진행됩니다. 정말로 이 버전의 패키지를 게시할 것인지 마지막으로 확인하는 메시지(Are you ready to publish simple_validator 1.0.0 (y/N)?)가 나타나면 y를 입력하고 엔터를 누릅니다.

잠시 후 "Successfully uploaded package." 메시지가 보이면 배포가 성공적으로 완료된 것입니다! 이제 당신은 전 세계 Flutter 개발자 커뮤니티의 일원이 되었습니다.

배포 후 확인

웹 브라우저에서 https://pub.dev/packages/패키지이름 (예: https://pub.dev/packages/simple_validator)으로 접속하여 당신의 패키지가 세상에 공개된 것을 확인해보세요. README 내용이 잘 보이는지, 버전 정보와 홈페이지 링크 등 메타데이터가 정확한지 꼼꼼히 점검합니다.

8. 패키지 관리와 성장: 배포는 끝이 아닌 시작

패키지를 성공적으로 배포한 것은 여정의 끝이 아니라, 패키지 관리자(Maintainer)로서의 새로운 시작입니다. 당신의 패키지가 사용자들에게 사랑받고 지속적으로 성장하기 위해서는 꾸준한 관심과 노력이 필요합니다.

Pub Points 이해하고 개선하기

pub.dev는 각 패키지의 품질을 객관적인 지표로 평가하여 점수를 매깁니다. 이 점수는 크게 세 가지로 구성됩니다.

  • Likes: 개발자들이 패키지에 얼마나 만족하는지를 나타내는 직접적인 지표입니다.
  • Pub Points: 코드 품질, 문서화, 플랫폼 지원 여부 등 기술적인 완성도를 평가합니다.
  • Popularity: 얼마나 많은 다른 패키지나 앱들이 이 패키지를 사용하는지를 나타내는 인기도 지표입니다.

Pub Points를 높이기 위해서는 다음 항목들을 충족시키는 것이 좋습니다.

  • 모든 공개 API에 대해 문서 주석(Doc Comments)을 작성하세요.
  • 지원하는 플랫폼(Android, iOS, Web 등)을 pubspec.yaml에 명시하세요.
  • 유의적 버전(Semantic Versioning) 규칙을 따르세요.
  • 최신 Dart 및 Flutter SDK 버전을 지원하도록 꾸준히 업데이트하세요.
  • README.md, CHANGELOG.md 등 문서화를 충실하게 제공하세요.

이슈 및 Pull Request 관리

사용자들은 당신의 패키지를 사용하다가 버그를 발견하거나 새로운 기능을 제안하기 위해 GitHub 저장소에 이슈(Issue)를 등록할 것입니다. 또한, 직접 코드를 수정하여 기여(Pull Request)를 보내오기도 합니다. 이러한 피드백에 신속하고 친절하게 응답하는 것은 건강한 커뮤니티를 만들고 패키지에 대한 신뢰를 쌓는 데 매우 중요합니다. 이슈 및 PR 템플릿을 만들어두면 사용자들이 체계적으로 피드백을 제공하도록 유도할 수 있습니다.

새로운 버전 릴리스하기

버그를 수정하거나 새로운 기능을 추가했다면, 새로운 버전을 릴리스해야 합니다. 이때 유의적 버전(SemVer) 규칙을 반드시 준수해야 합니다.

  • PATCH 버전 증가 (1.0.0 → 1.0.1): 기존 기능과 완벽하게 호환되는 버그 수정.
  • MINOR 버전 증가 (1.0.1 → 1.1.0): 기존 기능과 호환되면서 새로운 기능이 추가됨.
  • MAJOR 버전 증가 (1.1.0 → 2.0.0): 기존 API에 호환되지 않는 변경(Breaking Change)이 포함됨. 가장 신중하게 결정해야 합니다.

버전을 결정했다면, pubspec.yamlversion 필드를 수정하고, CHANGELOG.md에 해당 버전의 변경 사항을 상세히 기록한 후, 다시 flutter pub publish 명령어를 실행하여 새로운 버전을 배포하면 됩니다.

마치며: 당신의 여정은 이제 시작입니다

이 글을 통해 우리는 하나의 아이디어를 구체화하여 Flutter 패키지로 만들고, 테스트를 통해 신뢰성을 확보하며, 전 세계 개발자들과 공유하는 전체 과정을 함께 여행했습니다. 이제 당신은 단순히 다른 사람의 코드를 사용하는 개발자를 넘어, Flutter 생태계의 성장에 직접 기여할 수 있는 패키지 제작자의 역량을 갖추게 되었습니다.

첫 번째 패키지를 만드는 경험은 때로는 어렵고 도전적으로 느껴질 수 있습니다. 하지만 그 과정에서 얻는 소프트웨어 설계, 테스트 자동화, 버전 관리, 오픈소스 협업에 대한 깊이 있는 이해는 무엇과도 바꿀 수 없는 소중한 자산이 될 것입니다.

지금 당장 당신의 프로젝트에서 반복되는 코드를 찾아보세요. 혹은 "이런 기능이 있으면 편할 텐데" 라고 생각했던 작은 아이디어를 떠올려보세요. 거창할 필요는 없습니다. 작고 사소한 유틸리티라도 좋습니다. 당신의 작은 시작이 지구 반대편에 있는 누군가에게는 큰 도움이 될 수 있습니다.

궁금한 점이 있다면 언제나 Flutter 공식 문서를 참고하고, pub.dev에서 다른 훌륭한 패키지들의 소스 코드를 분석하며 영감을 얻는 것을 추천합니다. 당신의 멋진 아이디어가 담긴 패키지를 pub.dev에서 만나기를 기대하겠습니다. Happy coding!


0 개의 댓글:

Post a Comment