Skip to content

Instantly share code, notes, and snippets.

@zawadz88
Last active February 14, 2018 19:52
Show Gist options
  • Save zawadz88/9e2c3e7b109e11b59536cfbe74778b6d to your computer and use it in GitHub Desktop.
Save zawadz88/9e2c3e7b109e11b59536cfbe74778b6d to your computer and use it in GitHub Desktop.
Multi-flavored UI test filtering
package com.stepstone.base.test.runner.filter
import android.os.Bundle
import com.stepstone.base.BuildConfig
import com.stepstone.base.test.runner.annotation.TestFilter
import org.junit.runner.Description
import timber.log.Timber
/**
* Filter used to determine if a test should be executed or not.
*
* This does the following:
*
* - runs all tests if 'class' bundle argument was provided to the runner (this means that either Android Test Orchestrator wants to run a single test or that we want to run a test from Android Studio directly)
* - checks if [TestFilter] is present on a test method -> if so it filters the test based on that annotation
* - checks if [TestFilter] is present on a test class -> if so it filters the test based on that annotation
* - otherwise it runs the test
*
*
* [TestFilter] filtering runs the following checks in order:
*
* - if brand is not present in [TestFilter.brands] it will not run the test
* - if brand is present in [TestFilter.notBrands] it will not run the test
*
* To run a test on a single flavor e.g. flavor1 you need to add [TestFilter] to a test method like this:
*
* `@TestFilter(brands = [FLAVOR_1])`
*
* To run a test on all brands but one (flavor4) you need to add [TestFilter] to a test method like this:
*
* `@TestFilter(notBrands = [FLAVOR_4])`
*
* @see org.junit.runner.manipulation.Filter
*/
class BrandFilter(bundle: Bundle) : ParentFilter() {
companion object {
private const val CLASS_BUNDLE_KEY = "class"
}
private val shouldFilterTests: Boolean
init {
Timber.i("BrandFilter bundle: %s", bundle)
val listTestsForOrchestrator = java.lang.Boolean.parseBoolean(bundle.getString("listTestsForOrchestrator", "false"))
val runsSingleClass = bundle.containsKey(CLASS_BUNDLE_KEY)
shouldFilterTests = listTestsForOrchestrator && !runsSingleClass
}
override fun evaluateTest(description: Description): Boolean {
if (!shouldFilterTests) {
return true
}
val classTestFilter = description.testClass.getAnnotation(TestFilter::class.java)
val testFilter = description.getAnnotation(TestFilter::class.java)
if (testFilter != null) {
return evaluateTestWithFilter(testFilter)
} else if (classTestFilter != null) {
return evaluateTestWithFilter(classTestFilter)
}
return true
}
private fun evaluateTestWithFilter(testFilter: TestFilter): Boolean {
val brands = testFilter.brands
val notBrands = testFilter.notBrands
return (brands.contains(BuildConfig.FLAVOR)
&& !notBrands.contains(BuildConfig.FLAVOR))
}
override fun describe(): String {
return "Brand filter"
}
}
package com.stepstone.base.test.runner;
import android.os.Bundle;
import android.support.test.runner.AndroidJUnitRunner;
import com.stepstone.base.test.runner.filter.BrandFilter;
public class MyAndroidJUnitRunner extends AndroidJUnitRunner {
private static final String FILTER_BUNDLE_KEY = "filter";
@Override
public void onCreate(final Bundle bundle) {
bundle.putString(FILTER_BUNDLE_KEY, SCBrandFilter.class.getName());
super.onCreate(bundle);
}
}
package com.stepstone.base.test.runner.filter
import org.junit.runner.Description
import org.junit.runner.manipulation.Filter
/**
* Helper parent class for [Filter] that allows suites to run if any child matches.
*/
abstract class ParentFilter : Filter() {
/**
* {@inheritDoc}
*/
override fun shouldRun(description: Description): Boolean {
if (description.isTest) {
return evaluateTest(description)
}
// this is a suite, explicitly check if any children should run
// no children to run, filter this out
return description.children.any { shouldRun(it) }
}
/**
* Determine if given test description matches filter.
*
* @param description the [Description] describing the test
* @return `true` if matched
*/
protected abstract fun evaluateTest(description: Description): Boolean
}
package com.stepstone.base.test.runner.annotation
/**
* Used for narrowing down on which brands any given test/test suite should be executed.
*
* @see com.stepstone.base.test.runner.filter.BrandFilter
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class TestFilter(
val brands: Array<String> = [
FLAVOR_1, FLAVOR_2, FLAVOR_3, FLAVOR_4],
val notBrands: Array<String> = [])
const val FLAVOR_1 = "flavor1"
const val FLAVOR_2 = "flavor2"
const val FLAVOR_3 = "flavor2"
const val FLAVOR_4 = "flavor2"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment