Skip to content

Commit

Permalink
Add support for inherited tests (#288)
Browse files Browse the repository at this point in the history
* Add new classes for verification of inherited tests (to :core and :runner)

* Make instrumentation detect inherited JU5 tests from superclass and interfaces

* Gradle 7.5.1

* Changelog
  • Loading branch information
mannodermaus committed Oct 22, 2022
1 parent c3428b4 commit fd6c07f
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 18 deletions.
1 change: 1 addition & 0 deletions instrumentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Change Log
## Unreleased
- Update formatting of instrumentation test names to prevent breaking generation of log files in newer versions of AGP (#263)
- Add support for test sharding (#270)
- Add support for inherited tests (#288)

## 1.3.0 (2021-09-17)

Expand Down
1 change: 1 addition & 0 deletions instrumentation/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ junitPlatform {

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = javaVersion.toString()
kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all")
}

tasks.withType<Test> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.mannodermaus.junit5.inheritance;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.junit.jupiter.api.Test;

abstract class JavaAbstractClass {
@Test
void javaTest() {
assertNotNull(getJavaFileName());
}

abstract String getJavaFileName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.mannodermaus.junit5.inheritance;

import androidx.annotation.Nullable;

public class JavaAbstractClassTest extends JavaAbstractClass {
@Nullable
@Override
public String getJavaFileName() {
return "hello world";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package de.mannodermaus.junit5.inheritance;

import org.junit.jupiter.api.Test;

interface JavaInterface {
@Test
default void javaTest() {
assert(getJavaValue() > 0L);
}

long getJavaValue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.mannodermaus.junit5.inheritance;

public class JavaInterfaceTest implements JavaInterface {
@Override
public long getJavaValue() {
return 4815162342L;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.mannodermaus.junit5.inheritance;

public class JavaMixedInterfaceTest implements JavaInterface, KotlinInterface {
@Override
public long getJavaValue() {
return 4815162342L;
}

@Override
public int getKotlinValue() {
return 10101010;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.mannodermaus.junit5.inheritance

import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test

abstract class KotlinAbstractClass {
@Test
fun kotlinTest() {
assertNotNull(getKotlinFileName())
}

abstract fun getKotlinFileName(): String?
}

interface KotlinInterface {
@Test
fun kotlinTest() {
assert(kotlinValue > 0)
}

val kotlinValue: Int
}

class KotlinAbstractClassTest : KotlinAbstractClass() {
override fun getKotlinFileName() = "hello world"
}

class KotlinInterfaceTest : KotlinInterface {
override val kotlinValue: Int = 1337
}

class KotlinMixedInterfaceTest : KotlinInterface, JavaInterface {
override val kotlinValue: Int = 1337
override fun getJavaValue(): Long = 1234L
}
2 changes: 1 addition & 1 deletion instrumentation/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,35 @@ private val jupiterTestAnnotations = listOf(
"org.junit.jupiter.api.TestFactory",
"org.junit.jupiter.api.RepeatedTest",
"org.junit.jupiter.api.TestTemplate",
"org.junit.jupiter.params.ParameterizedTest"
"org.junit.jupiter.params.ParameterizedTest",
)

internal fun Class<*>.jupiterTestMethods(): List<Method> {
val allJupiterMethods = mutableListOf<Method>()
internal fun Class<*>.jupiterTestMethods(): Set<Method> =
jupiterTestMethods(includeInherited = true)

private fun Class<*>.jupiterTestMethods(includeInherited: Boolean): Set<Method> = buildSet {
try {
// Check each method in the Class for the presence
// of the well-known list of JUnit Jupiter annotations
allJupiterMethods += declaredMethods.filter { method ->
val annotationClassNames =
method.declaredAnnotations.map { it.annotationClass.qualifiedName }
jupiterTestAnnotations.firstOrNull { annotation ->
annotationClassNames.contains(annotation)
} != null
}
// of the well-known list of JUnit Jupiter annotations.
addAll(declaredMethods.filterAnnotatedByJUnitJupiter())

// Recursively check inner classes as well
declaredClasses.forEach { inner ->
allJupiterMethods += inner.jupiterTestMethods()
addAll(inner.jupiterTestMethods(includeInherited = false))
}

// Attach methods from inherited superclass or (for Java) implemented interfaces, too
if (includeInherited) {
addAll(superclass?.jupiterTestMethods(includeInherited = true).orEmpty())
interfaces.forEach { i -> addAll(i.jupiterTestMethods(includeInherited = true)) }
}
} catch (t: Throwable) {
Log.w(LOG_TAG, "${t.javaClass.name} in 'hasJupiterTestMethods()' for $name", t)
}

return allJupiterMethods
}

private fun Array<Method>.filterAnnotatedByJUnitJupiter(): List<Method> =
filter { method ->
val names = method.declaredAnnotations.map { it.annotationClass.qualifiedName }
jupiterTestAnnotations.any { it in names }
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,24 @@ class HasTaggedTest {
fun method() {

}
}
}

abstract class AbstractTestClass {
@Test
fun abstractTest() {
}
}

interface AbstractTestInterface {
@Test
fun interfaceTest() {
}
}

class HasInheritedTestsFromClass : AbstractTestClass() {
@Test
fun method() {
}
}

class HasInheritedTestsFromInterface : AbstractTestInterface
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package de.mannodermaus.junit5.internal
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import de.mannodermaus.junit5.DoesntHaveTestMethods
import de.mannodermaus.junit5.HasInheritedTestsFromClass
import de.mannodermaus.junit5.HasInheritedTestsFromInterface
import de.mannodermaus.junit5.HasInnerClassWithTest
import de.mannodermaus.junit5.HasParameterizedTest
import de.mannodermaus.junit5.HasRepeatedTest
Expand Down Expand Up @@ -49,7 +51,7 @@ class ExtensionsTests {
)
AndroidJUnit5(klass, params).run(notifier)

assertWithMessage("Executed ${listener.count()} instead of $expectExecutedTests tests: '${listener.methodNames()}'")
assertWithMessage("Executed ${listener.count()} instead of $expectExecutedTests tests on class '${klass.simpleName}': '${listener.methodNames()}'")
.that(listener.count())
.isEqualTo(expectExecutedTests)
}
Expand Down Expand Up @@ -87,7 +89,9 @@ class ExtensionsTests {
Arguments.of(HasTestFactory::class.java, 2),
Arguments.of(HasTestTemplate::class.java, 2),
Arguments.of(HasParameterizedTest::class.java, 2),
Arguments.of(HasInnerClassWithTest::class.java, 1)
Arguments.of(HasInnerClassWithTest::class.java, 1),
Arguments.of(HasInheritedTestsFromClass::class.java, 2),
Arguments.of(HasInheritedTestsFromInterface::class.java, 1),
)
}
}
Expand Down

0 comments on commit fd6c07f

Please sign in to comment.