Multivariate & A/B Testing for iOS and Mac

Related tags

Testing SkyLab
Overview

This library is no longer being maintained. You can continue to use SkyLab in your projects, but we recommend switching another solution whenever you have the opportunity.

SkyLab

Multivariate & A/B Testing for iOS and Mac

SkyLab is a backend-agnostic framework for multivariate and A/B testing.

Test conditions are persisted across sessions and launches using NSUserDefaults, ensuring that every user will have a consistent experience, no matter which testing bucket they end up in.

SkyLab integrates easily into any existing statistics web service. Depending on your particular needs, this may include posting to an endpoint in test blocks, or perhaps setting an HTTP header for a shared API client.

Requests for integration with any particular backend are heartily encouraged.

This project is part of a series of open source libraries covering the mission-critical aspects of an iOS app's infrastructure. Be sure to check out its sister projects: GroundControl, CargoBay, houston, and Orbiter.

Usage

Check out the included example project to see everything in action.

Simple A/B Test

[SkyLab abTestWithName:@"Title" A:^{
    self.titleLabel.text = NSLocalizedString(@"Hello, World!", nil);
} B:^{
    self.titleLabel.text = NSLocalizedString(@"Greetings, Planet!", nil);
}];

Split Test with Weighted Probabilities

You can pass either an NSDictionary (with values representing the weighted probability of their corresponding key) or an NSArray (with each value having an equal chance of being chosen) into the choices parameter.

[SkyLab splitTestWithName:@"Subtitle" conditions:@{
    @"Red" : @(0.15),
    @"Green" : @(0.10),
    @"Blue" : @(0.50),
    @"Purple" : @(0.25)
} block:^(id choice) {
    self.subtitleLabel.text = NSLocalizedString(@"Please Enjoy This Colorful Message", nil);

    if ([choice isEqualToString:@"Red"]) {
        self.subtitleLabel.textColor = [UIColor redColor];
    } else if ([choice isEqualToString:@"Green"]) {
        self.subtitleLabel.textColor = [UIColor greenColor];
    } else if ([choice isEqualToString:@"Blue"]) {
        self.subtitleLabel.textColor = [UIColor blueColor];
    } else if ([choice isEqualToString:@"Purple"]) {
        self.subtitleLabel.textColor = [UIColor purpleColor];
    }
}];

Multivariate Test

[SkyLab multivariateTestWithName:@"Switches" variables:@{
    @"Left" : @(0.5),
    @"Center" : @(0.5),
    @"Right" : @(0.5)
 } block:^(NSSet *activeVariables) {
     self.leftSwitch.on = [activeVariables containsObject:@"Left"];
     self.centerSwitch.on = [activeVariables containsObject:@"Center"];
     self.rightSwitch.on = [activeVariables containsObject:@"Right"];
}];

License

SkyLab is available under the MIT license. See the LICENSE file for more info.

Comments
  • Synchronous AB tests with SkyLab

    Synchronous AB tests with SkyLab

    All of SkyLab's methods rely on blocks -- which is a great structure for control-flow between tests. Things look a little strange, though, once you want something synchronously. ie,

    - (BOOL)showNewFancyButton 
    {
         BOOL showNewFancyButton;
        [SkyLab abTestWithName:@"ShowNewFancyButton" A:^{
            showNewFancyButton = YES;
        } B:^{
            showNewFancyButton = NO;
        }]
    
        return showNewFancyButton;
    }
    

    Looking at the implementation of SkyLab, the above code will work just fine, but we thought it's a little weird to rely on blocks to run synchronously (even though we know the implementation does).

    Do you think it'd be cool for SkyLabs to have non-block counterparts to its block-based methods? Or would you just suggest doing synchronous operations like above?

    opened by marklarr 2
  • Overflow in expression; result is -2147483648 with type 'int'

    Overflow in expression; result is -2147483648 with type 'int'

    This warning is emanated from SkyLab.m line 67:

    double r = (random() % (INT_MAX + 1)) * (total / INT_MAX);

    I assume INT_MAX + 1 is supposed to be INT_MIN? Or is the overflow intentional?

    opened by maxcabral 2
  • about @[ @

    about @[ @"A", @"B" ]

    Thank first. I am already using SkyLab and GroundControl.

    It makes life easy to init a array or dictionary using @[] or @{}. But it will be a problem for those whose XCode is still under 4.2. I encountered this problem.

    In the code you init a array using this:

    @[ @"A", @"B" ]

    would you please change it to [NSArray arrayWithObjects:@"A", @"B", nil]?

    opened by fjun99 2
  • Add documentation about backend integration

    Add documentation about backend integration

    It would be nice to hear thoughts on how to integration with a backend. Not about how to do network requests, but things like should I just read keys out of NSUserDefaults, what should I do with them to make a meaningful conclusion?

    I also didn't see an API for recording outcomes (positive/negative). How do you handle that?

    Thanks

    opened by objectiveSee 1
  • splitTestWithName triggers NSUserDefaultsDidChangeNotification every time it's called

    splitTestWithName triggers NSUserDefaultsDidChangeNotification every time it's called

    It looks like:

        if ([choices isKindOfClass:[NSArray class]]) {
            if (!choice || ![choices containsObject:choice]) {
                choice = SLRandomValueFromArray(choices);
            }
        } else if ([choices isKindOfClass:[NSDictionary class]]) {
            if (!choice || ![[choices allKeys] containsObject:choice]) {
                choice = SLRandomKeyFromDictionaryWithWeightedValues(choices);
            }
        } else {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:NSLocalizedString(@"Parameter `choices` must be either array or dictionary", nil) userInfo:nil];
        }
    
        [[NSUserDefaults standardUserDefaults] setObject:choice forKey:SLUserDefaultsKeyForTestName(name)];
        [[NSUserDefaults standardUserDefaults] synchronize];
    
    

    should change to:

        if ([choices isKindOfClass:[NSArray class]]) {
            if (!choice || ![choices containsObject:choice]) {
                choice = SLRandomValueFromArray(choices);
            }
            [[NSUserDefaults standardUserDefaults] setObject:choice forKey:SLUserDefaultsKeyForTestName(name)];
            [[NSUserDefaults standardUserDefaults] synchronize];
        } else if ([choices isKindOfClass:[NSDictionary class]]) {
            if (!choice || ![[choices allKeys] containsObject:choice]) {
                choice = SLRandomKeyFromDictionaryWithWeightedValues(choices);
            }
            [[NSUserDefaults standardUserDefaults] setObject:choice forKey:SLUserDefaultsKeyForTestName(name)];
            [[NSUserDefaults standardUserDefaults] synchronize];
        } else {
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:NSLocalizedString(@"Parameter `choices` must be either array or dictionary", nil) userInfo:nil];
        }
    

    It looks like the same pattern is used in multivariateTestWithName:variables:block as well.

    We ran across this because we were calling splitTestWithName:choices:block: from inside tableView:cellForRowAtIndexPath: and noticed repeated NSUserDefaultsDidChangeNotifications in the logs. Granted, that's not a great way to use it and we've refactored.

    Please let me know if you'd rather have a pull request.

    opened by JohnLemberger 1
Owner
Mattt
GitHub engineer working on Dependabot
Mattt
SwiftCheck is a testing library that automatically generates random data for testing of program properties

SwiftCheck QuickCheck for Swift. For those already familiar with the Haskell library, check out the source. For everybody else, see the Tutorial Playg

TypeLift 1.4k Dec 21, 2022
Testing the UI without UI Testing, a Swift experiment.

UI tests without UI Testing experiment This repo is a small experiment to see if there's an "in-between" for testing iOS applications. More feature-le

Joe Masilotti 20 Sep 26, 2022
A Mac and iOS Playgrounds Unit Testing library based on Nimble.

Spry Spry is a Swift Playgrounds Unit Testing library based on Nimble. The best thing about Spry is that the API matches Nimble perfectly. Which means

Quick 327 Jul 24, 2022
Automatic testing of your Pull Requests on GitHub and BitBucket using Xcode Server. Keep your team productive and safe. Get up and running in minutes. @buildasaur

Buildasaur Automatic testing of your Pull Requests on GitHub and BitBucket using Xcode Server. Keep your team productive and safe. Get up and running

Buildasaurs 774 Dec 11, 2022
Implementing and testing In-App Purchases with StoreKit2 in Xcode 13, Swift 5.5 and iOS 15.

StoreHelper Demo Implementing and testing In-App Purchases with StoreKit2 in Xcode 13, Swift 5.5, iOS 15. See also In-App Purchases with Xcode 12 and

Russell Archer 192 Dec 17, 2022
A flexible mock server for automated and regression testing of iOS, Android and other apps.

Note: This document is intended as a quick introduction to Voodoo. As Voodoo has a large number of features, please refer to Voodoo's Github Wiki for

Derek Clarkson 7 Nov 23, 2022
I built this application with unit testing and test-driven development to understand TDD theory and practice

TestDrivenDevelopment Description I built this application with unit testing and test-driven development to understand TDD theory and practice, to wri

null 1 Dec 21, 2021
Snapshot testing tool for iOS and tvOS

SnapshotTest is a simple view testing tool written completely in Swift to aid with development for Apple platforms. It's like unit testing for views.

Pär Strindevall 44 Sep 29, 2022
Remote configuration and A/B Testing framework for iOS

MSActiveConfig v1.0.1 Remote configuration and A/B Testing framework for iOS. Documentation available online. MSActiveConfig at a glance One of the bi

Elevate 78 Jan 13, 2021
Sample project for testing out focus in SwiftUI and iOS 15

This project was to test out different ways of enabling focus in a SwiftUI app.

null 3 Dec 21, 2021
The Swift (and Objective-C) testing framework.

Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo. // Swift import Quick import Nimbl

Quick 9.6k Dec 31, 2022
UI Testing Cheat Sheet and Examples.

UI Testing Cheat Sheet This repository is complementary code for my post, UI Testing Cheat Sheet and Examples. The post goes into more detail with exa

Joe Masilotti 2.1k Dec 25, 2022
Runtime introspection and unit testing of SwiftUI views

ViewInspector ??️‍♂️ for SwiftUI ViewInspector is a library for unit testing SwiftUI views. It allows for traversing a view hierarchy at runtime provi

Alexey Naumov 1.5k Jan 8, 2023
Swifty tool for visual testing iPhone and iPad apps. Every pixel counts.

Cribble Cribble - a tool for visual testing iPhone and iPad apps. Every pixel counts. Getting Started An example app is included demonstrating Cribble

Max Sokolov 273 Nov 4, 2022
T - A simple testing framework using closures and errors

t Quickly test expectations What is t? t is a simple testing framework using clo

OpenBytes 6 Nov 7, 2022
AB testing framework for iOS

ABKit Split Testing for Swift. ABKit is a library for implementing a simple Split Test that: Doesn't require an HTTP client written in Pure Swift Inst

Recruit Marketing Partners Co.,Ltd 113 Nov 11, 2022
Keep It Functional - An iOS Functional Testing Framework

IMPORTANT! Even though KIF is used to test your UI, you need to add it to your Unit Test target, not your UI Test target. The magic of KIF is that it

KIF Framework 6.2k Dec 29, 2022
An understated approach to iOS integration testing.

Subliminal is a framework for writing iOS integration tests. Subliminal provides a familiar OCUnit/XCTest-like interface to Apple's UIAutomation frame

Inkling 762 Nov 8, 2022
Bluepill is a reliable iOS testing tool that runs UI tests using multiple simulators on a single machine

Bluepill is a tool to run iOS tests in parallel using multiple simulators. Motivation LinkedIn created Bluepill to run its large iOS test suite in a r

Mobile Native Foundation 3.1k Jan 3, 2023