Test Store 개요
0:02:00iOS용 RevenueCat Test Store 설정 가이드에 오신 것을 환영합니다!
인앱 구매 테스트는 항상 어려웠습니다. 샌드박스 계정을 설정하고, 테스트 상품을 만들고, 앱 검토를 기다리고, 불안정한 네트워크 상태를 처리해야 했습니다. RevenueCat의 Test Store는 결정론적이고 빠르며 신뢰할 수 있는 테스트 환경을 제공하여 이러한 문제점들을 해결합니다.
Test Store란 무엇입니까?
Test Store는 App Store에 연결하지 않고도 인앱 구매 플로우를 테스트할 수 있게 해주는 RevenueCat의 내장 테스트 환경입니다. 모든 새로운 RevenueCat 프로젝트에 자동으로 프로비저닝되며 구매 결과에 대한 완전한 제어를 제공합니다.
학습할 내용
이 codelab에서는 iOS 개발을 위한 RevenueCat의 Test Store 설정 및 사용의 전체 과정을 안내합니다. RevenueCat 대시보드에서 Test Store를 활성화하고 Test Store API 키를 사용하도록 iOS 애플리케이션을 설정하는 것부터 시작합니다. 그런 다음 결정론적 결과로 구매 플로우를 테스트하고, 인앱 구매 로직에 대한 자동화된 단위 테스트를 작성하고, GitHub Actions를 사용하여 이러한 테스트를 CI/CD 파이프라인에 통합하는 방법을 배웁니다.
Test Store의 이점
Test Store는 인앱 구매 테스트 방식을 혁신합니다. App Store Connect 설정이나 앱 승인을 기다려야 하는 기존 테스트 방법과 달리 Test Store는 즉각적인 테스트 기능을 제공합니다. 구매 결과에 대한 완전한 결정론적 제어가 가능합니다. 트랜잭션이 성공하거나, 실패하거나, 취소되는 것은 전적으로 여러분에게 달려 있습니다. 이는 불안정한 네트워크 연결이나 신뢰할 수 없는 샌드박스 환경을 다룰 필요가 없다는 것을 의미합니다.
진정한 강력함은 자동화된 테스트를 작성하기 시작할 때 나타납니다. Test Store를 사용하면 GitHub Actions와 같은 CI/CD 시스템을 포함한 모든 환경에서 일관되게 실행되는 구매 로직에 대한 신뢰할 수 있는 단위 테스트를 구축할 수 있습니다. 이를 통해 구매 플로우를 빠르게 반복하여 몇 분 또는 몇 시간이 아닌 몇 초 만에 변경 사항을 테스트할 수 있습니다.
사전 요구 사항
시작하기 전에 다음을 확인하십시오:
- RevenueCat 계정 (revenuecat.com에서 무료)
- RevenueCat SDK가 통합된 iOS 프로젝트 (버전 5.x 이상)
- Swift 및 iOS 개발에 대한 기본 지식
- SwiftUI에 대한 이해 (선택 사항, UI 예제용)
대시보드에서 Test Store 활성화하기
0:03:00첫 번째 단계는 RevenueCat 대시보드에서 Test Store를 활성화하고 Test Store API 키를 얻는 것입니다.
대시보드 액세스
RevenueCat 대시보드에 로그인하고 왼쪽 사이드바 메뉴의 Apps & providers 섹션으로 이동하여 시작하십시오. 여기에서 연결된 모든 앱과 사용 가능한 스토어 통합을 찾을 수 있습니다.
Test Store 생성하기
Apps & providers 섹션에서 사용 가능한 공급자 중 Test Store 옵션을 찾으십시오. Test Store는 모든 RevenueCat 프로젝트에 자동으로 프로비저닝되므로 Create Test Store 또는 Enable Test Store를 클릭하여 활성화하기만 하면 됩니다. 설정은 승인이나 설정 동기화를 기다릴 필요 없이 즉시 완료됩니다.
API 키 얻기
Test Store가 활성화되면 앱 목록에서 Test Store 항목을 클릭하여 세부 정보를 확인하십시오. 여기에서 test_ 접두사로 시작하는 Test Store API Key를 찾을 수 있습니다. 이 키는 일반 RevenueCat API 키처럼 작동하지만 한 가지 중요한 차이점이 있습니다. 모든 구매 요청을 App Store 대신 Test Store로 라우팅하여 테스트 환경에 대한 완전한 제어를 제공합니다.
API 키 분리 이해하기: Test Store API 키는 프로덕션 및 샌드박스 키와 완전히 분리되어 있습니다. 이러한 분리는 의도적이며 보안상 중요합니다. 프로덕션 빌드에서는 절대 Test Store 키를 사용하지 마십시오. 디버그 및 테스트 환경에서만 사용해야 합니다. 필요한 경우 다른 테스트 시나리오를 위해 프로젝트당 여러 Test Store 구성을 만들 수 있습니다.
다음 단계
이제 Test Store API 키를 얻었으므로 테스트용 Test Store를 사용하도록 iOS 애플리케이션을 설정할 준비가 되었습니다.
iOS 앱에서 Test Store 설정하기
0:05:00이제 Test Store를 사용하도록 iOS 애플리케이션을 설정하겠습니다. 핵심은 테스트를 실행할 때 프로덕션 API 키 대신 Test Store API 키를 사용하는 것입니다.
1단계: Xcode 설정을 통한 Test Store API 키 추가하기
xcconfig 파일을 생성하거나 Xcode 빌드 설정을 사용하여 설정별로 API 키를 관리하십시오. Debug.xcconfig라는 파일을 생성하십시오:
// Debug.xcconfig
REVENUECAT_API_KEY = test_YOUR_KEY_HERE그리고 Release.xcconfig을 생성하십시오:
// Release.xcconfig
REVENUECAT_API_KEY = appl_YOUR_PRODUCTION_KEY그런 다음 Info.plist에 REVENUECAT_API_KEY를 추가하십시오:
<key>RevenueCatAPIKey</key>
<string>$(REVENUECAT_API_KEY)</string>2단계: 앱에서 SDK 초기화하기
적절한 API 키로 RevenueCat을 설정하도록 @main App 구조체를 업데이트하십시오:
import RevenueCat
import SwiftUI
@main
struct MyApp: App {
init() {
// Read API key from Info.plist (set via xcconfig)
let apiKey = Bundle.main.infoDictionary?["RevenueCatAPIKey"] as? String
?? "test_YOUR_KEY_HERE"
Purchases.configure(withAPIKey: apiKey)
// Enable debug logs for test builds
#if DEBUG
Purchases.logLevel = .debug
#endif
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}3단계: 환경별 설정
고급 설정의 경우 #if DEBUG 전처리기 지시문을 사용하여 설정 헬퍼를 만들 수 있습니다:
enum RevenueCatConfig {
static var apiKey: String {
#if DEBUG
return "test_YOUR_KEY_HERE"
#else
return "appl_YOUR_PRODUCTION_KEY"
#endif
}
static func configure() {
Purchases.configure(withAPIKey: apiKey)
#if DEBUG
Purchases.logLevel = .verbose
#endif
}
}그런 다음 앱 초기화에서 사용하십시오:
@main
struct MyApp: App {
init() {
RevenueCatConfig.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}검증
Test Store가 작동하는지 확인하려면:
- Xcode에서 디버그 모드로 앱을 실행하십시오
- Xcode 콘솔에서
"Purchases SDK initialized with Test Store"를 확인하십시오 - SDK는 App Store 대신 Test Store에 연결되어 있음을 표시합니다
대화형으로 구매 플로우 테스트하기
0:05:00Test Store가 활성화되면 이제 결과를 완전히 제어하면서 구매 플로우를 테스트할 수 있습니다. Test Store가 구매 대화 상자를 표시하면 여러분이 결과를 결정합니다.
Test Store 구매 대화 상자 이해하기
Test Store로 구매를 시작하면 표준 App Store 구매 시트 대신 맞춤 Test Store 대화 상자가 표시됩니다. 이 대화 상자는 실제 구매 대화 상자처럼 상품 세부 정보와 가격을 표시하지만 한 가지 주요 차이점이 있습니다. 결과를 제어할 수 있습니다. 대화 상자는 완료된 트랜잭션을 시뮬레이션하는 Successful Purchase, 결제 실패를 테스트하는 Failed Purchase, 사용자 취소를 시뮬레이션하는 Cancel의 세 가지 명확한 옵션을 제공합니다. 이러한 결정론적 동작이 Test Store를 테스트에 매우 강력하게 만드는 이유입니다.
성공 플로우 테스트하기
성공적인 구매 플로우를 테스트해 보겠습니다:
func purchaseProduct() async {
do {
// Fetch products from Test Store
let offerings = try await Purchases.shared.offerings()
guard let package = offerings.current?.availablePackages.first else {
return
}
// Initiate purchase
let (_, customerInfo, _) = try await Purchases.shared.purchase(package: package)
// Check the result
let isPremium = customerInfo.entitlements["premium"]?.isActive == true
if isPremium {
// Purchase successful!
showPremiumContent()
}
} catch {
// Handle error
handlePurchaseError(error)
}
}해피 패스 테스트: 앱을 실행하고 평소처럼 구매 플로우를 트리거하십시오. Test Store 대화 상자가 나타나면 "Successful Purchase"를 탭하십시오. 앱은 즉시 프리미엄 액세스를 부여해야 하며 권한이 올바르게 업데이트되었는지 확인할 수 있습니다. 이를 통해 실제 결제 수단이나 샌드박스 계정이 없어도 성공 플로우가 올바르게 작동하는지 빠르게 확인할 수 있습니다.
실패 시나리오 테스트하기
이제 앱이 실패를 처리하는 방법을 테스트하십시오:
func handlePurchaseError(_ error: Error) {
guard let purchasesError = error as? RevenueCat.ErrorCode else {
showError("Unexpected error: \(error.localizedDescription)")
return
}
switch purchasesError {
case .purchaseCancelledError:
// User cancelled - don't show error
print("User cancelled purchase")
case .purchaseInvalidError:
// Invalid purchase
showError("This purchase is not available")
case .paymentPendingError:
// Payment pending (e.g., awaiting parental approval)
showPendingMessage()
default:
// Other errors
showError("Purchase failed: \(error.localizedDescription)")
}
}오류 처리 테스트: 구매 플로우를 다시 트리거하되 이번에는 Test Store 대화 상자가 나타나면 "Failed Purchase"를 탭하십시오. 앱은 오류를 정상적으로 처리하여 사용자에게 적절한 오류 메시지를 표시해야 합니다. 사용자가 프리미엄 액세스를 받지 못하고 앱의 상태가 일관성을 유지하는지 확인하십시오. 이는 프로덕션에서 오류 처리 로직이 올바르게 작동하는지 확인하는 데 중요합니다.
취소 테스트하기
struct PaywallView: View {
@State private var isPurchasing = false
var onDismiss: () -> Void
var body: some View {
Button(action: {
Task {
isPurchasing = true
do {
try await purchaseProduct()
} catch let error as RevenueCat.ErrorCode
where error == .purchaseCancelledError {
// User cancelled - just dismiss
onDismiss()
} catch {
// Handle other errors
handlePurchaseError(error)
}
isPurchasing = false
}
}) {
Text(isPurchasing ? "Processing..." : "Subscribe")
}
.disabled(isPurchasing)
}
}사용자 취소 테스트: 페이월을 열고 구독 버튼을 탭하십시오. Test Store 대화 상자가 나타나면 "Cancel"을 탭하여 구매를 취소하는 사용자를 시뮬레이션하십시오. 페이월은 오류 메시지를 표시하지 않고 깔끔하게 닫혀야 합니다 (취소는 정상적인 사용자 작업이지 오류 상태가 아닙니다). 구매가 기록되지 않았고 앱의 상태가 변경되지 않았는지 확인하십시오.
핵심 요점
Test Store의 장점은 결정론적 제어에 있습니다. 각 구매 시도에서 정확히 무슨 일이 일어날지 여러분이 결정합니다. 이는 실제 결제 수단이 필요하거나 샌드박스 계정의 지연 및 복잡성을 처리할 필요 없이 몇 분 안에 모든 시나리오(성공, 실패 및 취소)를 철저히 테스트할 수 있음을 의미합니다. App Store와 통합할 준비가 되면 구매 처리 로직이 견고하다는 확신을 가질 수 있습니다.
자동화된 단위 테스트 작성하기
0:08:00Test Store의 가장 강력한 기능 중 하나는 인앱 구매 로직의 자동화된 단위 테스트를 가능하게 하는 것입니다. CI/CD에서 안정적으로 실행되는 테스트를 작성해 보겠습니다.
1단계: XCTest 설정하기
XCTest는 Xcode에 내장되어 있으므로 외부 종속성이 필요하지 않습니다. 프로젝트에 테스트 타겟이 있는지 확인하기만 하면 됩니다. Xcode의 기본 프로젝트 템플릿을 사용했다면 이미 포함되어 있습니다. Package.swift 또는 Xcode 프로젝트에 RevenueCat이 종속성으로 이미 추가되어 있어야 합니다:
// In your Package.swift or via Xcode SPM
dependencies: [
.package(url: "https://github.com/RevenueCat/purchases-ios.git", from: "5.58.0")
]2단계: Purchase Repository Protocol 생성하기
먼저 Swift protocol을 사용하여 테스트 가능한 purchase repository를 생성하겠습니다:
protocol PurchaseRepository {
func getOfferings() async throws -> Offerings
func purchase(package: Package) async throws -> CustomerInfo
func getCustomerInfo() async throws -> CustomerInfo
}
final class PurchaseRepositoryImpl: PurchaseRepository {
func getOfferings() async throws -> Offerings {
return try await Purchases.shared.offerings()
}
func purchase(package: Package) async throws -> CustomerInfo {
let (_, customerInfo, _) = try await Purchases.shared.purchase(package: package)
return customerInfo
}
func getCustomerInfo() async throws -> CustomerInfo {
return try await Purchases.shared.customerInfo()
}
}3단계: 비즈니스 로직이 포함된 ViewModel 생성하기
enum PaywallState {
case loading
case success(packages: [Package])
case purchasing
case purchaseSuccess
case purchaseCancelled
case error(message: String)
}
@MainActor
final class PaywallViewModel: ObservableObject {
@Published var state: PaywallState = .loading
private let repository: PurchaseRepository
init(repository: PurchaseRepository) {
self.repository = repository
}
func loadProducts() async {
do {
let offerings = try await repository.getOfferings()
let packages = offerings.current?.availablePackages ?? []
state = .success(packages: packages)
} catch {
state = .error(message: error.localizedDescription)
}
}
func purchase(package: Package) async {
state = .purchasing
do {
let customerInfo = try await repository.purchase(package: package)
let isPremium = customerInfo.entitlements["premium"]?.isActive == true
if isPremium {
state = .purchaseSuccess
} else {
state = .error(message: "Purchase completed but entitlement not active")
}
} catch let error as RevenueCat.ErrorCode
where error == .purchaseCancelledError {
state = .purchaseCancelled
} catch {
state = .error(message: error.localizedDescription)
}
}
}4단계: XCTest로 단위 테스트 작성하기
이제 mock repository를 사용하여 단위 테스트를 생성하십시오:
import XCTest
@testable import MyApp
final class MockPurchaseRepository: PurchaseRepository {
var mockOfferings: Offerings?
var mockCustomerInfo: CustomerInfo?
var mockError: Error?
func getOfferings() async throws -> Offerings {
if let error = mockError { throw error }
return mockOfferings!
}
func purchase(package: Package) async throws -> CustomerInfo {
if let error = mockError { throw error }
return mockCustomerInfo!
}
func getCustomerInfo() async throws -> CustomerInfo {
if let error = mockError { throw error }
return mockCustomerInfo!
}
}
@MainActor
final class PaywallViewModelTests: XCTestCase {
private var mockRepository: MockPurchaseRepository!
private var viewModel: PaywallViewModel!
override func setUp() async throws {
mockRepository = MockPurchaseRepository()
viewModel = PaywallViewModel(repository: mockRepository)
}
func testPurchaseSuccess() async {
// Given: Mock successful purchase with active entitlement
// Set up mockCustomerInfo with active "premium" entitlement
// When: Purchase is initiated
// await viewModel.purchase(package: mockPackage)
// Then: State should be purchaseSuccess
// XCTAssertEqual(viewModel.state, .purchaseSuccess)
}
func testPurchaseCancelled() async {
// Given: Mock cancelled purchase
mockRepository.mockError = RevenueCat.ErrorCode.purchaseCancelledError
// When: Purchase is initiated
// await viewModel.purchase(package: mockPackage)
// Then: State should be purchaseCancelled
// XCTAssertEqual(viewModel.state, .purchaseCancelled)
}
func testPurchaseFailed() async {
// Given: Mock failed purchase
mockRepository.mockError = RevenueCat.ErrorCode.paymentPendingError
// When: Purchase is initiated
// await viewModel.purchase(package: mockPackage)
// Then: State should be error
// if case .error(let message) = viewModel.state {
// XCTAssertTrue(message.contains("pending"))
// } else {
// XCTFail("Expected error state")
// }
}
}5단계: Mock 기반 테스트 대안
더 많은 제어를 위해 완전히 mock된 접근 방식을 사용할 수 있습니다:
final class SpyPurchaseRepository: PurchaseRepository {
var getOfferingsCalled = false
var purchaseCalled = false
var lastPurchasedPackage: Package?
var stubbedOfferings: Offerings!
var stubbedCustomerInfo: CustomerInfo!
var stubbedError: Error?
func getOfferings() async throws -> Offerings {
getOfferingsCalled = true
if let error = stubbedError { throw error }
return stubbedOfferings
}
func purchase(package: Package) async throws -> CustomerInfo {
purchaseCalled = true
lastPurchasedPackage = package
if let error = stubbedError { throw error }
return stubbedCustomerInfo
}
func getCustomerInfo() async throws -> CustomerInfo {
if let error = stubbedError { throw error }
return stubbedCustomerInfo
}
}이 접근 방식이 작동하는 이유
이 테스트 접근 방식은 인앱 구매 테스트의 전통적인 문제점을 제거합니다. Test Store에는 네트워크 종속성이 없기 때문에 테스트가 매번 안정적으로 실행됩니다. 더 이상 연결 문제로 인한 불안정한 실패가 없습니다. 테스트는 몇 분이 아닌 몇 초 만에 실행되어 개발 중에 빠른 피드백을 제공합니다. 실제 결제 시스템으로는 재현하기 어렵거나 시간이 많이 걸리는 엣지 케이스를 포함하여 모든 시나리오에 걸쳐 완전한 테스트 커버리지를 달성할 수 있습니다.
우리가 구축한 아키텍처(비즈니스 로직을 SDK에서 분리하는 protocol 패턴 사용)는 이러한 테스트를 빠르고 유지 관리하기 쉽게 만듭니다. GitHub Actions와 같은 모든 CI/CD 환경에서 실행할 수 있어 프로덕션에 도달하기 전에 버그를 포착할 수 있습니다.
CI/CD에서 테스트 실행하기
0:04:00이제 GitHub Actions에서 테스트가 자동으로 실행되도록 설정하여 모든 코드 변경 시 구매 로직이 안정적으로 유지되도록 하겠습니다.
1단계: GitHub Actions Workflow 생성하기
.github/workflows/ios-tests.yml을 생성하십시오:
name: iOS Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Select Xcode version
run: sudo xcode-select -s /Applications/Xcode.app
- name: Cache SPM packages
uses: actions/cache@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
.build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Run unit tests
env:
REVENUECAT_TEST_STORE_API_KEY: ${{ secrets.REVENUECAT_TEST_STORE_API_KEY }}
run: |
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' \
-resultBundlePath TestResults.xcresult \
REVENUECAT_API_KEY=$REVENUECAT_TEST_STORE_API_KEY
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: TestResults.xcresult2단계: GitHub Secrets에 Test Store API 키 추가하기
- GitHub 리포지토리로 이동하십시오
- Settings → Secrets and variables → Actions로 이동하십시오
- New repository secret을 클릭하십시오
- 이름:
REVENUECAT_TEST_STORE_API_KEY - 값: Test Store API 키 (예:
test_xxxxx) - Add secret을 클릭하십시오
3단계: Secret을 사용하도록 Xcode 설정하기
환경 변수를 사용할 수 있을 때 이를 읽도록 xcconfig을 업데이트하십시오:
// Debug.xcconfig
// Use environment variable if available (CI), fallback to local key
REVENUECAT_API_KEY = $(REVENUECAT_API_KEY:default=test_YOUR_KEY_HERE)4단계: Fastlane 대안
iOS CI/CD에 Fastlane을 사용하는 경우 테스트 lane을 추가할 수 있습니다:
lane :test do
run_tests(
scheme: "MyApp",
devices: ["iPhone 15"],
result_bundle: true,
xcargs: "REVENUECAT_API_KEY=#{ENV['REVENUECAT_TEST_STORE_API_KEY']}"
)
end설정 확인하기
변경 사항을 커밋하고 푸시하여 workflow를 트리거하십시오. 리포지토리의 GitHub Actions 탭으로 이동하여 테스트가 자동으로 실행되는 것을 확인하십시오. workflow가 코드를 체크아웃하고, macOS 환경을 설정하고, 테스트 스위트를 실행하는 동안 실시간 진행 상황을 볼 수 있습니다. 몇 분 안에 CI 환경에서 모든 테스트가 통과하는지 알 수 있습니다.
자동화된 테스트의 강력함
일단 설정되면 모든 pull request가 병합되기 전에 자동으로 검증됩니다. 변경 사항이 구매 로직을 손상시키면 즉시 피드백을 받게 되며 모든 시나리오를 수동으로 테스트할 필요가 없습니다. Test Store는 모든 실행에서 테스트가 일관된 결과를 생성하도록 보장하여 불안정한 테스트의 좌절감을 제거합니다. 이는 버그가 있는 코드가 main 브랜치에 도달하는 것을 방지하는 품질 게이트를 만들고 반복적인 수동 테스트에 소비될 수많은 시간을 절약합니다.
모범 사례 및 프로덕션 설정
0:03:00Test Store를 설정했으므로 이제 모범 사례와 Test Store와 프로덕션 환경 간 전환 방법을 다루겠습니다.
환경 분리
항상 #if DEBUG를 사용하여 Test Store와 프로덕션 키를 분리하여 유지하십시오:
enum RevenueCatKeys {
// Test Store - for testing only
static let testStoreKey = "test_xxxxx"
// App Store - for production
static let appStoreKey = "appl_xxxxx"
static var apiKey: String {
#if DEBUG
return testStoreKey
#else
return appStoreKey
#endif
}
}Xcode Scheme/Configuration 기반 키 전환
Xcode 빌드 설정을 사용하여 더 세밀한 제어를 구현하십시오:
// Debug.xcconfig (Test Store)
REVENUECAT_API_KEY = test_xxxxx
USE_TEST_STORE = YES
// Staging.xcconfig (Test Store)
REVENUECAT_API_KEY = test_xxxxx
USE_TEST_STORE = YES
// Release.xcconfig (App Store)
REVENUECAT_API_KEY = appl_xxxxx
USE_TEST_STORE = NO포괄적인 테스트 전략 구축하기
인앱 구매에 대한 강력한 테스트 전략은 피라미드 접근 방식을 따라야 합니다. Test Store를 사용한 단위 테스트로 시작하여 구매 로직을 격리된 상태에서 검증하십시오. 이는 빠르게 실행되며 대부분의 문제를 조기에 포착합니다. 그 기반 위에 역시 Test Store를 사용하는 통합 테스트를 구축하여 완전한 구매 플로우를 엔드투엔드로 확인하십시오. 구현에 확신이 생기면 Apple Sandbox로 수동 테스트로 넘어가 플랫폼 통합을 확인하십시오. 마지막으로 실제 결제로 소규모 프로덕션 테스트를 수행하여 라이브 환경에서 모든 것이 작동하는지 확인하십시오.
Test Store와 Apple Sandbox 중 선택하기
초기 개발 및 반복 중에는 Test Store가 가장 좋은 선택입니다. 구매 플로우의 신속한 프로토타이핑, 단위 및 통합 테스트 작성, 오류 처리 및 엣지 케이스 테스트에 완벽합니다. 즉각적인 피드백 루프는 신뢰할 수 있고 빠른 결과가 필요한 CI/CD 자동화 테스트에 이상적입니다.
Test Store가 시뮬레이션하지 않는 플랫폼별 기능을 테스트해야 할 때 Apple Sandbox로 전환하십시오. 여기에는 대기 중인 트랜잭션(예: Ask to Buy 승인 대기), 영수증 검증 세부 사항, 구독 갱신 주기 및 지역별 가격 책정이 포함됩니다. Sandbox는 프로덕션 전 검증 환경으로 생각하십시오. 핵심 로직이 견고하고 App Store 통합을 확인해야 할 때 사용하십시오.
보안 모범 사례
프로덕션에서는 절대 Test Store API 키를 노출하지 마십시오:
// ❌ BAD: Hardcoded keys
let apiKey = "test_xxxxx"
// ✅ GOOD: Compile-time configuration
#if DEBUG
let apiKey = "test_xxxxx"
#else
let apiKey = "appl_xxxxx"
#endif
// ✅ BETTER: Read from xcconfig via Info.plist
let apiKey = Bundle.main.infoDictionary?["RevenueCatAPIKey"] as! String로깅 및 디버깅
적절한 로깅 수준을 활성화하십시오:
#if DEBUG
Purchases.logLevel = .verbose
print("RevenueCat: Using Test Store for testing")
#else
Purchases.logLevel = .info
#endif프로덕션으로 전환하기
앱을 출시하기 전에 철저한 체크리스트를 완료했는지 확인하십시오. 모든 테스트가 Test Store와 함께 통과해야 하며 Apple Sandbox와의 통합을 수동으로 확인해야 합니다. Xcode 설정이 각 환경에서 올바른 API 키를 사용하도록 올바르게 설정되어 있는지 다시 확인하십시오. 프로덕션 API 키는 보안이 유지되어야 하며 버전 관리에 커밋되어서는 안 됩니다. Test Store 키가 릴리스 빌드에서 완전히 제거되었는지 확인하십시오. 디버그 설정에만 존재해야 합니다. 로깅이 적절하게 설정되어 있는지 확인하십시오(디버그에서는 상세, 프로덕션에서는 최소). 모든 시나리오에 걸쳐 오류 처리가 테스트되었는지 확인하십시오.
결론
축하합니다! iOS용 RevenueCat의 Test Store를 성공적으로 설정했습니다. 이 codelab을 통해 RevenueCat 대시보드에서 Test Store를 활성화하고, iOS 애플리케이션에서 Test Store API 키를 설정하고, 결정론적 결과로 구매 플로우를 테스트하고, 구매 로직에 대한 자동화된 단위 테스트를 작성하고, GitHub Actions와 같은 CI/CD 환경에서 해당 테스트를 실행하고, 환경 분리를 위한 모범 사례를 따르는 방법을 배웠습니다.
핵심 요점
Test Store는 인앱 구매 테스트에 접근하는 방식을 근본적으로 변화시킵니다. 샌드박스 계정과 씨름하고 불안정한 네트워크 종속 테스트를 다루던 시대는 지났습니다. 구매 결과에 대한 결정론적 제어를 통해 구현에 대한 진정한 확신을 주는 신뢰할 수 있는 테스트를 작성할 수 있습니다. CI/CD 통합은 모든 코드 변경 시 구매 로직이 지속적으로 검증됨을 의미하며, 몇 시간이 아닌 몇 초 만에 구매 플로우를 반복할 수 있습니다. 가장 중요한 것은 구매 관련 버그가 프로덕션에 도달하기 전에 포착하여 매출과 사용자 경험을 모두 보호할 수 있다는 것입니다.
다음 단계
이제 Test Store를 설정했으므로 이 기반 위에 구축할 시간입니다. 모든 구매 시나리오를 포괄하는 포괄적인 테스트 작성부터 시작하십시오. 해피 패스만 테스트하지 마십시오. 이러한 테스트를 CI/CD 파이프라인에 통합하여 모든 pull request에서 자동으로 실행되도록 하십시오. 다양한 실패 모드로 오류 처리를 철저히 테스트하여 앱이 정상적으로 저하되는지 확인하십시오. 준비가 되면 플랫폼별 테스트를 위해 Apple Sandbox로 전환하여 구독 갱신과 같은 사항을 확인하십시오. 마지막으로 구매 로직이 모든 단계에서 철저히 검증되었음을 알고 자신 있게 출시하십시오.
추가 리소스
RevenueCat Test Store 블로그 게시물 RevenueCat iOS SDK 문서 테스트 가이드 GitHub: purchases-ios즐거운 테스트 되세요!