Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JUnit rules and test parameterization

JUnit rules and test parameterization

Slides for my talk about JUnit rules and test parameterization at Coders on Beers. The content is Portuguese.

Matheus Cassiano Candido

September 18, 2018
Tweet

More Decks by Matheus Cassiano Candido

Other Decks in Programming

Transcript

  1. JUnit rules e parametrização: tenha orgulho do seus testes de

    novo Matheus C. Candido Android @ Concrete
  2. Rules • Componente do JUnit 4 que serve para interceptar

    testes • Casos de uso comum incluem: orquestração de tarefas, preparação de recursos temporários (pastas, servidores, etc) • Evitam as famosas classes BaseTest
  3. ExternalResource • Uma base class para quando é necessário configurar

    algum recurso antes da execução de cada teste e desligá-lo logo após • Dois métodos helper: before() e after()
  4. ExternalResource class ServerRule : ExternalResource() { val server = Server()

    override fun before() { server.connect() } override fun after() { server.disconnect() } }
  5. ExternalResource class ServerRule : ExternalResource() { val server = Server()

    override fun before() { server.connect() } override fun after() { server.disconnect() } }
  6. ExternalResource class ServerRule : ExternalResource() { val server = Server()

    override fun before() { server.connect() } override fun after() { server.disconnect() } }
  7. ExternalResource class ApiTest : ExternalResource() { @get:Rule private val serverRule

    = ServerRule() private val api = Api() @Test fun shouldDisplay404PageForPath() { serverRule.server.mockPath("/hello", 404) assertEquals("not found", api.sendHello()) } }
  8. ExternalResource class ApiTest : ExternalResource() { @get:Rule private val serverRule

    = ServerRule() private val api = Api() @Test fun shouldDisplay404PageForPath() { serverRule.server.mockPath("/hello", 404) assertEquals("not found", api.sendHello()) } }
  9. ExternalResource class ApiTest : ExternalResource() { @get:Rule private val serverRule

    = ServerRule() private val api = Api() @Test fun shouldDisplay404PageForPath() { serverRule.server.mockPath("/hello", 404) assertEquals("not found", api.sendHello()) } }
  10. RuleChain • Possibilita a execução de rules de forma encadeada

    (em ordem) • Útil quando é preciso combinar rules em novas, evitando acoplamento, aumentando o código reutilizável
  11. Parametrização de testes https://github.com/junit-team/junit4/wiki/parameterized-tests [0,0] [1,1] [2,1] [3,2] [4,3] [5,5]

    [6,8] @RunWith(Parameterized::class) class FibonacciTest( val input: Int, val expected: Int ) { companion object { @JvmStatic @Parameterized.Parameters fun data() = // input data } @Test fun assertNFibonacci() { assertEquals( expected, Fibonacci.compute(input) ) } } Input
  12. Parametrização de testes https://github.com/junit-team/junit4/wiki/parameterized-tests [0,0] [1,1] [2,1] [3,2] [4,3] [5,5]

    [6,8] @RunWith(Parameterized::class) class FibonacciTest( val input: Int, val expected: Int ) { companion object { @JvmStatic @Parameterized.Parameters fun data() = // input data } @Test fun assertNFibonacci() { assertEquals( expected, Fibonacci.compute(input) ) } } Input
  13. Parametrização de testes https://github.com/junit-team/junit4/wiki/parameterized-tests [0,0] [1,1] [2,1] [3,2] [4,3] [5,5]

    [6,8] @RunWith(Parameterized::class) class FibonacciTest( val input: Int, val expected: Int ) { companion object { @JvmStatic @Parameterized.Parameters fun data() = // input data } @Test fun assertNFibonacci() { assertEquals( expected, Fibonacci.compute(input) ) } } Input
  14. Parametrização de testes https://github.com/junit-team/junit4/wiki/parameterized-tests [0,0] [1,1] [2,1] [3,2] [4,3] [5,5]

    [6,8] @RunWith(Parameterized::class) class FibonacciTest( val input: Int, val expected: Int ) { companion object { @JvmStatic @Parameterized.Parameters fun data() = // input data } @Test fun assertNFibonacci() { assertEquals( expected, Fibonacci.compute(input) ) } } Input
  15. Burst A unit testing library for varying test data. •

    Implementação melhorada da parametrização do JUnit • Não é necessário campos estáticos na hora de prover os dados de entrada • Desenvolvida pela Square • Usa Enums em vez de Object https://github.com/square/burst
  16. Boilerplate de frameworks class StartAndCloseKoinRule( private val app: Application, private

    val modules: List<Module>, private val baseUrl: String ) : ExternalResource() { override fun before() { startKoin( modules, extraProperties = mapOf(PROPERTY_BASE_URL to baseUrl) ) with app } override fun after() { closeKoin() } }
  17. Boilerplate de frameworks class StartAndCloseKoinRule( private val app: Application, private

    val modules: List<Module>, private val baseUrl: String ) : ExternalResource() { override fun before() { startKoin( modules, extraProperties = mapOf(PROPERTY_BASE_URL to baseUrl) ) with app } override fun after() { closeKoin() } }
  18. Boilerplate de frameworks class StartAndCloseKoinRule( private val app: Application, private

    val modules: List<Module>, private val baseUrl: String ) : ExternalResource() { override fun before() { startKoin( modules, extraProperties = mapOf(PROPERTY_BASE_URL to baseUrl) ) with app } override fun after() { closeKoin() } }
  19. Integrando rules comuns class ActivityRule<T : Activity> ( activityClass: Class<T>,

    initialTouchMode: Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { val activityRule = ... val requestMatcherRule = ... val koinRule = ... private val delegateRule = RuleChain .outerRule(requestMatcherRule) .around(koinRule) .around(activityRule) override fun apply( base: Statement, description: Description ) = delegateRule.apply(base, description) }
  20. Integrando rules comuns class ActivityRule<T : Activity> ( activityClass: Class<T>,

    initialTouchMode: Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { val activityRule = ... val requestMatcherRule = ... val koinRule = ... private val delegateRule = RuleChain .outerRule(requestMatcherRule) .around(koinRule) .around(activityRule) override fun apply( base: Statement, description: Description ) = delegateRule.apply(base, description) }
  21. Integrando rules comuns class ActivityRule<T : Activity> ( activityClass: Class<T>,

    initialTouchMode: Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { val activityRule = ... val requestMatcherRule = ... val koinRule = ... private val delegateRule = RuleChain .outerRule(requestMatcherRule) .around(koinRule) .around(activityRule) override fun apply( base: Statement, description: Description ) = delegateRule.apply(base, description) }
  22. Parametrizando temas class StartAndCloseKoinRule( private val app: Application, private val

    modules: List<Module>, private val baseUrl: String ) : ExternalResource() { override fun before() { startKoin( modules, extraProperties = mapOf(PROPERTY_BASE_URL to baseUrl) ) with app } override fun after() { closeKoin() } }
  23. Parametrizando temas class StartAndCloseKoinRule( private val app: Application, private val

    modules: List<Module>, private val baseUrl: String, private val theme: Int ) : ExternalResource() { override fun before() { startKoin( modules, extraProperties = mapOf(PROPERTY_BASE_URL to baseUrl) ) with app val appConfig = get<AppConfig>() appConfig.theme = theme } override fun after() { closeKoin() } }
  24. Parametrizando temas class DashboardActivity : AppCompatActivity() { private val appConfig

    by inject<AppConfig>() override fun onCreate(savedInstanceState: Bundle?) { setTheme(appConfig.theme) super.onCreate(savedInstanceState) // ... } }
  25. Parametrizando temas class DashboardActivity : AppCompatActivity() { private val appConfig

    by inject<AppConfig>() override fun onCreate(savedInstanceState: Bundle?) { setTheme(appConfig.theme) super.onCreate(savedInstanceState) // ... } }
  26. Parametrizando temas class DashboardActivity : AppCompatActivity() { private val appConfig

    by inject<AppConfig>() override fun onCreate(savedInstanceState: Bundle?) { setTheme(appConfig.theme) super.onCreate(savedInstanceState) // ... } } class DashboardActivityTest { @get:Rule val activityRule = ActivityRule(DashboardActivity::class.java) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  27. Parametrizando temas class DashboardActivity : AppCompatActivity() { private val appConfig

    by inject<AppConfig>() override fun onCreate(savedInstanceState: Bundle?) { setTheme(appConfig.theme) super.onCreate(savedInstanceState) // ... } } @RunWith(BurstJUnit4::class) class DashboardActivityTest { @get:Rule val activityRule = ActivityRule(DashboardActivity::class.java) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  28. Parametrizando temas class DashboardActivity : AppCompatActivity() { private val appConfig

    by inject<AppConfig>() override fun onCreate(savedInstanceState: Bundle?) { setTheme(appConfig.theme) super.onCreate(savedInstanceState) // ... } } @RunWith(BurstJUnit4::class) class DashboardActivityTest { @get:Rule val activityRule = ActivityRule(DashboardActivity::class.java) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  29. Parametrizando temas class ActivityRule<T : Activity> ( activityClass: Class<T>, initialTouchMode:

    Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { // ... fun snapActivity(name: String? = null) { val prefixName = getScreenshotPrefix() val fullName = name?.let { "${prefixName}_$it" } ?: prefixName Screenshot.snapActivity(activity).setName(fullName).record() } private fun getScreenshotPrefix(): String { val testClass = TestNameDetector.getTestClass() val testName = TestNameDetector.getTestName() return "$testClass.$testName.${themeName}" } }
  30. Parametrizando temas class ActivityRule<T : Activity> ( activityClass: Class<T>, initialTouchMode:

    Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { // ... fun snapActivity(name: String? = null) { val prefixName = getScreenshotPrefix() val fullName = name?.let { "${prefixName}_$it" } ?: prefixName Screenshot.snapActivity(activity).setName(fullName).record() } private fun getScreenshotPrefix(): String { val testClass = TestNameDetector.getTestClass() val testName = TestNameDetector.getTestName() return "$testClass.$testName.${themeName}" } }
  31. Parametrizando temas class ActivityRule<T : Activity> ( activityClass: Class<T>, initialTouchMode:

    Boolean = true, launchActivity: Boolean = true ) : TestRule, KoinComponent { // ... fun snapActivity(name: String? = null) { val prefixName = getScreenshotPrefix() val fullName = name?.let { "${prefixName}_$it" } ?: prefixName Screenshot.snapActivity(activity).setName(fullName).record() } private fun getScreenshotPrefix(): String { val testClass = TestNameDetector.getTestClass() val testName = TestNameDetector.getTestName() return "$testClass.$testName.${themeName}" } }
  32. Parametrizando temas enum class Theme { THEME_A, THEME_B } @RunWith(BurstJUnit4::class)

    class DashboardActivityTest(theme: Theme) { @get:Rule val activityRule = ActivityRule( DashboardActivity::class.java, overrideTheme = theme ) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  33. Parametrizando temas enum class Theme { THEME_A, THEME_B } @RunWith(BurstJUnit4::class)

    class DashboardActivityTest(theme: Theme) { @get:Rule val activityRule = ActivityRule( DashboardActivity::class.java, overrideTheme = theme ) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  34. Filtrando parâmetros enum class Theme { THEME_A, THEME_B } @RunTestsFor([Theme.THEME_A])

    @RunWith(BurstJUnit4::class) class DashboardActivityTest(theme: Theme) { @get:Rule val activityRule = ActivityRule( DashboardActivity::class.java, overrideTheme = theme ) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  35. Filtrando parâmetros class ScreenshotFilterRule( private val testClass: Class<*>, private val

    themeBeingTested: Theme ) : TestRule { override fun apply(base: Statement, description: Description) = IgnoreStatement(base, testClass, themeBeingTested) class IgnoreStatement( private val baseStatement: Statement, private val clazz: Class<*>, private val themeBeingTested: Theme ) : Statement() { override fun evaluate() { val message = "Test ignored" val annotation = clazz.getAnnotation(RunTestsFor::class.java) val ignore = annotation != null && !annotation.themes.contains(themeBeingTested) assumeFalse(message, ignore) baseStatement.evaluate() } } }
  36. Filtrando parâmetros class ScreenshotFilterRule( private val testClass: Class<*>, private val

    themeBeingTested: Theme ) : TestRule { override fun apply(base: Statement, description: Description) = IgnoreStatement(base, testClass, themeBeingTested) class IgnoreStatement( private val baseStatement: Statement, private val clazz: Class<*>, private val themeBeingTested: Theme ) : Statement() { override fun evaluate() { val message = "Test ignored" val annotation = clazz.getAnnotation(RunTestsFor::class.java) val ignore = annotation != null && !annotation.themes.contains(themeBeingTested) assumeFalse(message, ignore) baseStatement.evaluate() } } }
  37. Filtrando parâmetros class ScreenshotFilterRule( private val testClass: Class<*>, private val

    themeBeingTested: Theme ) : TestRule { override fun apply(base: Statement, description: Description) = IgnoreStatement(base, testClass, themeBeingTested) class IgnoreStatement( private val baseStatement: Statement, private val clazz: Class<*>, private val themeBeingTested: Theme ) : Statement() { override fun evaluate() { val message = "Test ignored" val annotation = clazz.getAnnotation(RunTestsFor::class.java) val ignore = annotation != null && !annotation.themes.contains(themeBeingTested) assumeFalse(message, ignore) baseStatement.evaluate() } } }
  38. Filtrando parâmetros class ScreenshotFilterRule( private val testClass: Class<*>, private val

    themeBeingTested: Theme ) : TestRule { override fun apply(base: Statement, description: Description) = IgnoreStatement(base, testClass, themeBeingTested) class IgnoreStatement( private val baseStatement: Statement, private val clazz: Class<*>, private val themeBeingTested: Theme ) : Statement() { override fun evaluate() { val message = "Test ignored" val annotation = clazz.getAnnotation(RunTestsFor::class.java) val ignore = annotation != null && !annotation.themes.contains(themeBeingTested) assumeFalse(message, ignore) baseStatement.evaluate() } } }
  39. Filtrando parâmetros enum class Theme { THEME_A, THEME_B } @RunTestsFor([Theme.THEME_A])

    @RunWith(BurstJUnit4::class) class DashboardActivityTest(theme: Theme) { @get:Rule val activityRule = ActivityRule( DashboardActivity::class.java, overrideTheme = theme ) @get:Rule val screenShotFilter = ScreenshotFilterRule(javaClass, theme) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  40. Filtrando parâmetros enum class Theme { THEME_A, THEME_B } @RunTestsFor([Theme.THEME_A])

    @RunWith(BurstJUnit4::class) class DashboardActivityTest(theme: Theme) { @get:Rule val activityRule = ActivityRule( DashboardActivity::class.java, overrideTheme = theme ) @get:Rule val screenShotFilter = ScreenshotFilterRule(javaClass, theme) @Test fun dashboardIsBeingDisplayedCorrectly() { activityRule.snapActivity() } }
  41. Resumo • Nova ActivityRule com mais funcionalidades (setup de DI,

    mock web server) sem usar uma classe base • Execução de testes com diferentes parâmetros (temas) e filtros