본문 바로가기

iOS

[iOS] RemoteConfig 사용해서 A/B 테스트 실험 돌리기 🧪

안녕하세요. 오늘은 프립에서 A/B 테스트를 처음으로 도입해 본 경험을 기록으로 남겨보고자 합니다.
이전부터 A/B테스트를 해보고는 싶었으나 기회가 없어서 해보지 못했었는데요, 이번에 심플한 좋은 기회가 생겨서 도전했던 기록을 공유합니다.

어쩌다 시작..?🤔 A/B 테스트의 시작하게된 계기

프로덕트 팀의 PO님께서 탈퇴유저의 탈퇴 이유를 시작으로 새로운 문제점을 찾아 정의했고 문제를 해결하기 위해 새로울 가설을 세웠습니다. "설립한 가정을 검증하기 위해 A/B테스트를 진행하면 좋겠다." 라는 기회를 만들어주셨습니다.
실험 계획서 링크의 대략적인 내용은 다음과 같습니다.

[문제정의]

  • 상품 상세로 진입하기 전 가장 많이 보는 화면이 상품 리스트이다.
  • 탈퇴 유저 중 10%가 가격이 비싸다는 의견을 남겼다. (이 외 응답은 리스트와 관련없는 내용)
  • 고객이 상품 상세를 조회하기 전에, 리스트에서 클릭조차 하지 않는 것은 문제이다.

[가설]

  • 리스트에서 가격의 허들이 느껴져서 상품 조회수가 낮을 것이다.

위 내용을 바탕으로 가설을 조금 더 디벨롭 한 결과 아래와 같은 B 시안이 나오게되었습니다.

왼쪽: A안(현재 레이아웃), 오른쪽: B안(실험 레이아웃)

왼쪽에 있는 A 시안은 현재 상태의 상품 리스트고 오른쪽에 있는 B 시안은 새롭게 테스트할 시안입니다.
B안에서 둔 차별점은 상품 제목의 폰트를 키우고 볼드처리 했고, 상품 가격은 크기를 줄이고 크기가 큰 상품 제목 바로 밑에 위치시키는 것입니다. 그래서 가격보다는 상품의 제목에 집중시키면 상품 클릭률이 올라갈 것이다 라는 가설입니다.
이제 이 내용들을 바탕으로 A/B테스트 기능을 개발해보고자 합니다!

A/B 테스트는 어떻게 개발하지?

Firebase Remote Config

파이어 베이스에서 제공하는 여러가지 기능들 중 Remote config라는 피처가 있습니다.
Remote config는 말 그대로 원격 구성으로 앱 업데이트 없이 앱의 동작 혹은 모양을 변경할 수 있도록 하는 기능입니다.
간단한 사용 예를 들면 A라는 화면에 있는 "안녕하세요." 라는 문구를 앱 업데이트 없이 "안녕히 가세요"라는 문구로 변경하는 것입니다.
해당 기능을 서버의 API를 통해서도 심플하게 개발 가능하다고 생각하지만 서버가 없는 서비스라던지 서버의 개발 리소스가 부족한 상황에서는 활용하기 좋다고 생각합니다. 프립에서도 간단한 문구 혹은 팝업 트리거 용도로 사용하고 있습니다.
Remote config를 위에서 말한대로 간단하게만 사용할수도 있지만, A/B 테스트용도로도 사용이 가능합니다.

Remote config 변수를 설정하고 나서 우측에 더보기 버튼을 누르면 A/B테스트을 볼 수 있습니다. 버튼을 누르면 해당 변수를 사용해 A/B 테스트 환경 설정을 진행할 수 있습니다.

1. 실험의 이름과 설명 적기

2. 실험 타겟, 실험 인원 비율, 활성화 이벤트 설정

기본 사항을 입력한 다음 입력할 사항은 실험 타겟, 실험 인원 비율, 활성화 이벤트 설정입니다.
실험 타겟은 파이어베이스 해당 프로젝트에 등록된 여러개의 앱중 한개 이상을 선택해서 실험을 진행할 수 있습니다.
(실험 타겟 설정을 통해 상황에 따라 안드로이드만, 혹은 iOS만 실험 가능하기도 합니다.)

다음으로는 실험 인원 비율을 퍼센트로 설정합니다. 여기에서 퍼센트 설정에 따라 예상 실험 인원수가 노출됩니다. 예상 실험 인원은 최근 7일동안 Remote config 변수를 로드한 유저들의 수를 바탕으로 계산됩니다. 즉, 아직 remote config를 한번도 사용 안 한 상황이라면 예상 인원은 0명이 노출될 수 있습니다.

다음으로 있는 있는 활성화 이벤트는 선택사항으로 설정을 할 수 있는데요, 활성화 이벤트는 유저가 이 실험에 참가하는 트리거 이벤트입니다.
다시 말해서 유저가 Remote config로 변수를 가지고 온 뒤, 활성화 이벤트로 설정된 이벤트를 보내야 해당 실험에 참가됩니다.
여기서 말하는 활성화 이벤트는 GA에 설정되어 있는 이벤트입니다.

프립에서는 홈 화면 보는 이벤트를 활성화 이벤트로 설정했습니다.
(앱 시작하자마자 항상 불려지는 이벤트기도하고, 테스트를 하는 상품 리스트가 홈화면에서부터 보여지기 때문에 홈 화면으로 설정했습니다.)

💡활성화 이벤트를 설정하지 않으면 어떻게 되나요??💡

활성화 이벤트를 설정하지 않아도 사용자는 A/B테스트 설정한 비율에 따라 값을 전달받습니다. 다만 파이어베이스 콘솔창에 유저가 집계되지 않을뿐 입니다.
(활성화 이벤트가 없기 때문이죠)

3. 실험 목표 설정

다음으로 설정하는 값은 목표 이벤트 입니다. 바로 직전 단계에서는 실험에 참가하는 트리거 이벤트를 활성화 이벤트로 설정했는데요, 트리거된 유저들 중에서 통계를 낼 이벤트를 설정하는 단계입니다.
A/B테스트를 나누는 목적이 일반적으로 유저를 A, B두개의 그룹으로 나누고 어떤 그룹이 더 반응이 좋은지 보기 위함이잖아요??
여기서 더 반응이 좋은지를 확인하기 위한 지표가 목표 이벤트 입니다.

프립에서는 상품 리스트의 디자인 시안에 따라 누가 더 상품 상세화면을 많이 보는가?? 에 대한 실험이므로 상품 상세를 보는 view_product 이벤트로 설정했습니다.

4. 변형 설정하기

마지막으로 하는 일은 각각의 그룹에 어떤 값을 떨어뜨려줄지에 대한 설정입니다.
이 값은 개발자분들이 알아야 하는 값입니다. 해당 매개변수로 조회했을 경우 위에서 설정한 값들이 떨어지기 떄문에 해당 값을 통해 A그룹인지 B그룹인지 판별할 수 있어야합니다.
프립에서는 심플하게 구분하기 위해 직관적인 A, B로 설정했습니다.
대안 가중치 조정 섹션은 A/B의 비율을 조정할 수 있는 섹션입니다. 각자 상황에 맞게 설정해주시면 됩니다. 저는 50:50으로 설정했습니다.

위 내용대로 설정한 뒤 검토를 눌러 발생시키면 그 순간부터 위 매개변수를 조회했을 때, A 또는 B 값이 나눠져서 떨어집니다.

실험 환경 설정은 끝났으니 이제 개발에서 받아보자구요?

매개변수 받기

저는 Remote config기능을 사용할 수 있는 RemteConfigManager를 생성해서 사용했습니다.
해당 매니저의 fetchABTestParameter() 함수를 호출하면 RemoteConfig에서 A그룹인지 B그룹인지 값이 떨어지게됩니다. 
위에서 실험 설정을 해두면 실험 변수에 맞게 떨어지고 실험중이 아니라면 기본 값이 떨어지게됩니다.

/// RemoteConfigManager.swift 
final class RemoteConfigManager {

    static let shared = RemoteConfigManager()
    
    var testGroup = ABTestGroup.none
    
    private let remoteConfig = RemoteConfig.remoteConfig()
    private let remoteConfigSettings = RemoteConfigSettings()
    private let expirationDuration: TimeInterval = {
        #if (RELEASE && PRODUCTION)
        return 60 * 60
        #else
        return 0
        #endif
    }()
    
    private init() {
        self.remoteConfigSettings.minimumFetchInterval = self.expirationDuration
        self.remoteConfig.configSettings = self.remoteConfigSettings
    }
    
    func fetchABTestParameter() -> Observable<ABTestGroup> {
        let remoteConfigKey = "frip_ab_test"
        
        return self.generateFetchStringObservable(remoteConfigKey: remoteConfigKey)
            .map(ABTestGroup.init(rawValue:)) // A or B가 비율에 맞게 떨어집니다.
    }
}

/// ABTestGroup.swift
enum ABTestGroup: String {
    case groupA = "A"
    case groupB = "B"
    case none = "NONE"
    
    init(rawValue: String) {
        switch rawValue {
        case ABTestGroup.groupA.rawValue:
            self = .groupA
            
        case ABTestGroup.groupB.rawValue:
            self = .groupB
            
        default:
            self = .none
        }
    }
}


그럼 매개변수를 언제 받고 어디에 저장해두지?

받는 시점은 실험이 어디에서 진행이 되는가? 에 대한 질문의 답에 따라 달라질 것으로 예상합니다.
특정 화면에서 A/B테스트를 한다고 가정하면 해당 화면 생성하면서 테스트 그룹을 받아올 수도 있고, 화면과는 무관하게 앱을 실행하면서 받아오는 방법도 있을 것으로 예상합니다.

프립에서는 A/B 테스트 범위에 해당하는 상품 리스트가 앱을 실행시키는 홈 화면에서부터 노출되기 때문에 스플래시 화면에서 미리 받아오도록 설정했습니다.

받아온 그룹을 저장해두는 곳은  UserDefaults에 저장하거나 앱이 실행되어있는 동안 메모리에 가지고 있도록 하는 두가지 방법을 생각했는데요, 앱이 실행될때마다 그룹을 조회하기 때문에 메모리에 가지고 있어도 괜찮다는 결론을 내려서 RemoteConfig 내부에 변수를 두어 저장했습니다.

final class RemoteConfigManager: RemoteConfigManagerProtocol {
    static let shared = RemoteConfigManager()
    
    var testGroup = ABTestGroup.none /// 테스트 그룹 저장 변수
    
    // ...
}

 

테스트 그룹을 받았으니 그룹에 따라 다른 화면을 보여주자

/// ProductCollectionViewCell.swift
final class ProductCollectionViewCell: UICollectionViewCell {
	//...
    
    override init(frame: CGRect) {
    	super.init(frame)
        
        let testGroup = RemoteConfigManager.shared.testGroup
        
        switch testGroup {
        case .groupA:
        	// A 그룹에 속해있은 상황의 레이아웃 설정
            
        case .groupB:
        	// B 그룹에 속해있는 상황의 레이아웃 설정
            
        case .none:
        	// A/B 테스트가 진행되지 않는 환경에서의 레이아웃 설정
        }
    }
}

실제 상품 뷰 바인딩 하기 전에 받아온 테스트 그룹을 판별하여 각각의 상황에 맞게 레이아웃을 바인딩 시켰습니다.
이번 실험에서는 상품 리스트의 레이아웃이 달라지니 ProductCollectionViewCell 콜렉션뷰 셀 클래스 내부에서 테스트 그룹을 가지고 왔습니다. (실제 프로덕트의 일부를 심플하게 변형해서 예시를 적었습니다 위 코드는 RemoteConfigManager에 대한 디팬던시가 생깁니다.)

결과

앞에서 설명한 내용대로 A/B테스트 환경 설정 결과 회사 내에서도 A/B 테스트 그룹이 적절하게 갈려서 나타나는 것을 볼 수 있었습니다.
해당 포스트는 구현 방법에 대한 내용 위주이므로 데이터에 대한 내용은 다음에..! 💫


다음 기회에 조금 더 발전시켰으면 좋은 점

이번에는 파이어베이스의 Remote config를 사용해서 A/B테스트를 진행했었는데, 다음번이나 추후에는 자체 시스템으로 테스팅을 진행하면 좋겠다는 생각이 듭니다. 자체 시스템으로 더 뾰족한 타겟으로 진행해볼 수 있지 않을까 생각이 듭니다.. (특정 관심사를 좋아하는 유저를 대상으로만, 특정 나이대의 사용자를 대상으로만 진행 등등)