Skip to content

A/B Test Altyapısı

Genel Bakış

A/B test altyapısı, mobil uygulamalarda kontrollü deneyler yaparak veri odaklı karar vermeyi sağlar. Bu dokümantasyon, Android, iOS, React Native ve Flutter platformlarında kapsamlı A/B test uygulamasını, istatistiksel analiz ve otomatik karar verme mekanizmalarıyla birlikte ele alır.

Firebase A/B Test

Android Uygulaması

kotlin
// ABTestManager.kt
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
import kotlinx.coroutines.tasks.await
import android.content.Context
import java.util.concurrent.ConcurrentHashMap

class ABTestManager(private val context: Context) {
    private val remoteConfig = FirebaseRemoteConfig.getInstance()
    private val analytics = FirebaseAnalytics.getInstance(context)
    private val activeExperiments = ConcurrentHashMap<String, ExperimentVariant>()
    
    companion object {
        private const val EXPERIMENT_PREFIX = "experiment_"
        private const val CACHE_EXPIRATION = 3600L // 1 saat
    }
    
    init {
        setupRemoteConfig()
    }
    
    private fun setupRemoteConfig() {
        val configSettings = FirebaseRemoteConfigSettings.Builder()
            .setMinimumFetchIntervalInSeconds(CACHE_EXPIRATION)
            .build()
        
        remoteConfig.setConfigSettingsAsync(configSettings)
        setExperimentDefaults()
    }
    
    private fun setExperimentDefaults() {
        val defaults = mapOf(
            "${EXPERIMENT_PREFIX}yeni_odeme_akisi" to "kontrol",
            "${EXPERIMENT_PREFIX}urun_onerim_algoritmasi" to "isbirlikci_filtreleme",
            "${EXPERIMENT_PREFIX}hosgeldin_akisi" to "standart",
            "${EXPERIMENT_PREFIX}fiyat_gosterimi" to "orijinal",
            "${EXPERIMENT_PREFIX}buton_rengi" to "#2196F3"
        )
        
        remoteConfig.setDefaultsAsync(defaults)
    }
    
    suspend fun initializeExperiments(): Boolean {
        return try {
            remoteConfig.fetchAndActivate().await()
            loadActiveExperiments()
            true
        } catch (e: Exception) {
            false
        }
    }
    
    private fun loadActiveExperiments() {
        val allKeys = remoteConfig.getKeysByPrefix(EXPERIMENT_PREFIX)
        
        for (key in allKeys) {
            val experimentName = key.removePrefix(EXPERIMENT_PREFIX)
            val variantName = remoteConfig.getString(key)
            
            val variant = ExperimentVariant(
                experimentName = experimentName,
                variantName = variantName,
                parameters = getExperimentParameters(experimentName, variantName)
            )
            
            activeExperiments[experimentName] = variant
            
            // Deney atamasını izle
            analytics.logEvent("deney_atandi") {
                param("deney_adi", experimentName)
                param("varyant_adi", variantName)
            }
        }
    }
    
    fun getExperimentVariant(experimentName: String): ExperimentVariant? {
        return activeExperiments[experimentName]
    }
    
    fun isExperimentActive(experimentName: String): Boolean {
        return activeExperiments.containsKey(experimentName)
    }
    
    fun trackExperimentEvent(experimentName: String, eventName: String, parameters: Map<String, Any> = emptyMap()) {
        val variant = activeExperiments[experimentName] ?: return
        
        val eventParameters = mutableMapOf<String, Any>().apply {
            put("deney_adi", experimentName)
            put("varyant_adi", variant.variantName)
            putAll(parameters)
        }
        
        analytics.logEvent("deney_$eventName", eventParameters)
    }
    
    fun trackConversion(experimentName: String, conversionType: String, value: Double = 0.0) {
        trackExperimentEvent(
            experimentName, 
            "donusum",
            mapOf(
                "donusum_tipi" to conversionType,
                "donusum_degeri" to value
            )
        )
    }
    
    private fun getExperimentParameters(experimentName: String, variantName: String): Map<String, Any> {
        return when (experimentName) {
            "yeni_odeme_akisi" -> when (variantName) {
                "tek_sayfa" -> mapOf("adimlar" to 1, "duzen" to "kompakt")
                "coklu_adim" -> mapOf("adimlar" to 3, "duzen" to "detayli")
                else -> mapOf("adimlar" to 2, "duzen" to "standart")
            }
            "buton_rengi" -> mapOf("renk" to variantName)
            "fiyat_gosterimi" -> mapOf("format" to variantName)
            else -> emptyMap()
        }
    }
}

// Veri Sınıfları
data class ExperimentVariant(
    val experimentName: String,
    val variantName: String,
    val parameters: Map<String, Any>
)

// Activity'de Kullanım
class OdemeActivity : AppCompatActivity() {
    private lateinit var abTestManager: ABTestManager
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        abTestManager = ABTestManager(this)
        
        lifecycleScope.launch {
            abTestManager.initializeExperiments()
            setupUIBasedOnExperiments()
        }
    }
    
    private fun setupUIBasedOnExperiments() {
        val odemeDeneyi = abTestManager.getExperimentVariant("yeni_odeme_akisi")
        
        odemeDeneyi?.let { variant ->
            when (variant.variantName) {
                "tek_sayfa" -> setupTekSayfaOdeme()
                "coklu_adim" -> setupCokluAdimOdeme()
                else -> setupStandartOdeme()
            }
            
            // Deney maruziyetini izle
            abTestManager.trackExperimentEvent("yeni_odeme_akisi", "maruziyet")
        }
    }
    
    private fun onPurchaseCompleted(amount: Double) {
        abTestManager.trackConversion("yeni_odeme_akisi", "satis", amount)
    }
}

iOS Uygulaması

swift
// ABTestManager.swift
import FirebaseRemoteConfig
import FirebaseAnalytics
import Foundation

class ABTestManager: ObservableObject {
    static let shared = ABTestManager()
    
    private let remoteConfig = RemoteConfig.remoteConfig()
    private let experimentPrefix = "experiment_"
    private let cacheExpiration: TimeInterval = 3600 // 1 saat
    
    @Published var activeExperiments: [String: ExperimentVariant] = [:]
    @Published var isInitialized = false
    
    private init() {
        setupRemoteConfig()
    }
    
    private func setupRemoteConfig() {
        let settings = RemoteConfigSettings()
        settings.minimumFetchInterval = cacheExpiration
        remoteConfig.configSettings = settings
        
        setExperimentDefaults()
    }
    
    private func setExperimentDefaults() {
        let defaults: [String: NSObject] = [
            "\(experimentPrefix)yeni_odeme_akisi": "kontrol" as NSObject,
            "\(experimentPrefix)urun_onerim_algoritmasi": "isbirlikci_filtreleme" as NSObject,
            "\(experimentPrefix)hosgeldin_akisi": "standart" as NSObject,
            "\(experimentPrefix)fiyat_gosterimi": "orijinal" as NSObject,
            "\(experimentPrefix)buton_rengi": "#2196F3" as NSObject
        ]
        
        remoteConfig.setDefaults(defaults)
    }
    
    func initializeExperiments() async -> Bool {
        do {
            _ = try await remoteConfig.fetch(withExpirationDuration: cacheExpiration)
            let activated = try await remoteConfig.activate()
            
            if activated {
                loadActiveExperiments()
                isInitialized = true
            }
            
            return activated
        } catch {
            print("Deneyler başlatılırken hata: \(error)")
            return false
        }
    }
    
    private func loadActiveExperiments() {
        let allKeys = remoteConfig.allKeys(from: .remote, namespace: .default)
        let experimentKeys = allKeys.filter { $0.hasPrefix(experimentPrefix) }
        
        var experiments: [String: ExperimentVariant] = [:]
        
        for key in experimentKeys {
            let experimentName = String(key.dropFirst(experimentPrefix.count))
            let variantName = remoteConfig.configValue(forKey: key).stringValue ?? "kontrol"
            
            let variant = ExperimentVariant(
                experimentName: experimentName,
                variantName: variantName,
                parameters: getExperimentParameters(experimentName: experimentName, variantName: variantName)
            )
            
            experiments[experimentName] = variant
            
            // Deney atamasını izle
            Analytics.logEvent("deney_atandi", parameters: [
                "deney_adi": experimentName,
                "varyant_adi": variantName
            ])
        }
        
        DispatchQueue.main.async {
            self.activeExperiments = experiments
        }
    }
    
    func getExperimentVariant(_ experimentName: String) -> ExperimentVariant? {
        return activeExperiments[experimentName]
    }
    
    func isExperimentActive(_ experimentName: String) -> Bool {
        return activeExperiments[experimentName] != nil
    }
    
    func trackExperimentEvent(_ experimentName: String, eventName: String, parameters: [String: Any] = [:]) {
        guard let variant = activeExperiments[experimentName] else { return }
        
        var eventParameters = parameters
        eventParameters["deney_adi"] = experimentName
        eventParameters["varyant_adi"] = variant.variantName
        
        Analytics.logEvent("deney_\(eventName)", parameters: eventParameters)
    }
    
    func trackConversion(_ experimentName: String, conversionType: String, value: Double = 0.0) {
        trackExperimentEvent(
            experimentName,
            eventName: "donusum",
            parameters: [
                "donusum_tipi": conversionType,
                "donusum_degeri": value
            ]
        )
    }
    
    private func getExperimentParameters(experimentName: String, variantName: String) -> [String: Any] {
        switch experimentName {
        case "yeni_odeme_akisi":
            switch variantName {
            case "tek_sayfa":
                return ["adimlar": 1, "duzen": "kompakt"]
            case "coklu_adim":
                return ["adimlar": 3, "duzen": "detayli"]
            default:
                return ["adimlar": 2, "duzen": "standart"]
            }
        case "buton_rengi":
            return ["renk": variantName]
        case "fiyat_gosterimi":
            return ["format": variantName]
        default:
            return [:]
        }
    }
}

// Veri Yapıları
struct ExperimentVariant {
    let experimentName: String
    let variantName: String
    let parameters: [String: Any]
}

// SwiftUI'da Kullanım
struct OdemeView: View {
    @StateObject private var abTestManager = ABTestManager.shared
    @State private var odemeVaryanti: ExperimentVariant?
    
    var body: some View {
        VStack {
            if let variant = odemeVaryanti {
                switch variant.variantName {
                case "tek_sayfa":
                    TekSayfaOdemeView()
                case "coklu_adim":
                    CokluAdimOdemeView()
                default:
                    StandartOdemeView()
                }
            } else {
                StandartOdemeView()
            }
        }
        .onAppear {
            if abTestManager.isInitialized {
                setupExperiment()
            }
        }
        .onReceive(abTestManager.$isInitialized) { initialized in
            if initialized {
                setupExperiment()
            }
        }
    }
    
    private func setupExperiment() {
        odemeVaryanti = abTestManager.getExperimentVariant("yeni_odeme_akisi")
        
        if let variant = odemeVaryanti {
            abTestManager.trackExperimentEvent("yeni_odeme_akisi", eventName: "maruziyet")
        }
    }
    
    private func onPurchaseCompleted(amount: Double) {
        abTestManager.trackConversion("yeni_odeme_akisi", conversionType: "satis", value: amount)
    }
}

En İyi Uygulamalar

1. Deney Tasarımı

  • Net başarı metrikleri tanımlayın
  • Yeterli örneklem büyüklüğü sağlayın
  • Uygun randomizasyon kullanın
  • Karıştırıcı değişkenleri kontrol edin

2. İstatistiksel Sağlamlık

  • İstatistiksel gücü hesaplayın
  • Anlamlılık eşiklerini belirleyin
  • Çoklu karşılaştırmaları hesaba katın
  • Erken durdurma için izleme yapın

3. Teknik Uygulama

  • Tutarlı kullanıcı ataması
  • Performans etkisini minimize edin
  • Sınır durumlarını düzgün şekilde ele alın
  • Uygun loglama uygulayın

4. İş Düşünceleri

  • İş hedefleriyle uyumlu olun
  • Uzun vadeli etkileri göz önünde bulundurun
  • Yayın stratejilerini planlayın
  • Öğrenilenleri belgelendirin

5. Etik ve Uyumluluk

  • Gerektiğinde kullanıcı onayı alın
  • Kullanıcı gizliliğini koruyun
  • Zararlı deneylerden kaçının
  • Veri düzenlemelerine uyun

Bu kapsamlı A/B test altyapısı, tüm mobil platformlarda uygun istatistiksel analiz, otomatik karar verme ve sağlam izleme ile kurumsal düzeyde deney yapma yetenekleri sağlar.

Eren Demir tarafından oluşturulmuştur.