use_frameworks!
pod "ABTestKit"
Copy ABTestKit.swift
file to your project.
Copy ABTestKit+.swift
found in the Example project and define your tests inside the extension to ABTestKit.Test
struct.
An A/B Test is initialized with a name and variants using an enum with the following options:
ab
: control and test variant with 50% weight eachsplit
: n number of variants where weight for each equals1÷n
weighted
: custom defined weights
Optionally add a convenience initializer to add defaults values, formatting, etc.
extension ABTestKit.Test {
static let featureNumberOne = ABTestKit.Test(name: "feature_1", date: "010101") // 50-50 split
static let featureNumberTwo = ABTestKit.Test(name: "feature_2", date: "030202", variants: .split([.control, .test, "test2", "test3"])) // even 25% each
static let featureNumberThree = ABTestKit.Test(name: "feature_3", date: "030303", variants: .weighted([(.control, 0.9), (.test, 0.1)])) // 90-10 split
// Convenience initializer
init(name: String, date: String, variants: ABTestKit.Variants = .ab) {
let formattedName = "\(date)_\(name)"
self.init(name: formattedName, variants: variants)
}
}
Use the shared
singleton property to setup the framework:
-
Enable all the active tests
-
React to bucketing events (optional)
-
Migrate the buckets in UserDefaults to the key defined in the
Configuration
object (optional)
extension ABTestKit {
static let shared: ABTestKit = {
let testKit = ABTestKit(tests:
.featureNumberOne, .featureNumberTwo, .featureNumberThree
);
testKit.migrate(from: "PreviousABTestValues")
testKit.variantAllocated = { variant, test in
// Make API call to update the server, report an event, print log, etc.
}
return testKit
}()
}
Optional: Expose the tests to Objective-C by returning their name, since Obj-C can't see the Test
struct.
extension ABTestKit {
static var featureNumberOne: TestName { return Test.featureNumberOne.name }
static var featureNumberTwo: TestName { return Test.featureNumberTwo.name }
static var featureNumberThree: TestName { return Test.featureNumberThree.name }
}
try! ABTestKit.shared.runTest(.featureNumberOne
, control: {
performSegue(withIdentifier: "control", sender: self)
}
, test: {
performSegue(withIdentifier: "test", sender: self)
})
// Trailing closure will default to `test`
try! ABTestKit.shared.runTest(.featureNumberOne) {
performSegue(withIdentifier: "test", sender: self)
}
try! ABTestKit.shared.runTest(.featureNumberTwo, handlers: {
// control
view.backgroundColor = .white
}, {
// test
view.backgroundColor = .red
}, {
// test2
view.backgroundColor = .green
}, {
// test3
view.backgroundColor = .blue
})
button.isHidden = try! ABTestKit.shared.isTestVariant(for: .featureNumberThree)
guard try! ABTestKit.shared.isTestVariant(for: .featureNumberThree) else { return }
if try! ABTestKit.shared.isTestVariant(for: .featureNumberTwo) {
// Any test bucket
print("Not control bucket")
}
let configuration = ABTestKit.Configuration(tests: .featureNumberOne, .featureNumberTwo, .featureNumberThree,
userDefaultsKey: "com.acme.ABTests")
let testKit = ABTestKit(configuration: configuration)
try! ABTestKit.shared.setVariant(.control, for: .featureNumberOne)
ABTestKit.shared.reset()
print(ABTestKit.shared.allTests) // ["feature_1", "feature_2", "feature_3"]
print(ABTestKit.shared.variantsByTestName) // [["feature_1: "control"], ["feature_2": "test2"], ["feature_3: "test"]]