Code Monkey home page Code Monkey logo

junit5-watcher's Introduction

Junit5 Watcher Extension

Maven Central


Watcher is Junit5 extension that can measure the execution of JUnit tests and provides detailed metrics, for different test stages (before/after, all/each). The metrics report is the first thing you may want to do before optimizing the time of execution of your test suite.

Detailed test metrics result is beneficial for integration tests particularly. In long-lived projects, the integration tests may take a significant part of your CI/CD pipeline. In that case, the bottleneck of your tests may be hidden in the initialization phase (e.g. before all or before each methods) which usually tends to be overlooked by developers.

Some IDEs report test execution time, but provided values tend to ignore pre-/post-class callbacks. Most likely you may see value representing only pure test execution time + pre-test callback (BeforeEach). However, some more advanced Spring Boot integration tests can spend significant time on Spring context initialization or database data prefilling.

This extension lets you measure not only the cumulative time of your test classes' execution time but also callbacks before/after triggered by other test extensions (Spring Extension particularly)

How to use the extension

Dependencies

Extension requires JDK 17.

You have to define Maven or Gradle new dependencies (in test scope).

Maven dependencies:

<dependency>
    <groupId>pl.appga.junit5</groupId>
    <artifactId>junit5-watcher-ext</artifactId>
    <version>1.2</version>
    <scope>test</scope>
</dependency>

The extension is compiled with Kotlin 1.9. If your project uses directly or indirectly Kotlin 1.6.x or older already, you may need to update to at least 1.8.x. In that case please update kotlin-stdlib:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>1.8.20</version>
    <scope>test</scope>
</dependency>

The extension can be used in your project in one of two methods provided by JUnit5.

Automatic usage

Watcher Extension can be automatically launched in your test suite by running it with Java property -Djunit.jupiter.extensions.autodetection.enabled=true

See more details in JUnit5 documentation

Declarative usage

The second way of using the extension is by explicit declaration using JUnit5 @ExtendWith annotation.

Extensions order

If the extension is defined in a declarative way, please make sure the Watcher is the first extension registered in the class (or base class). This is required to measure properly other extensions' callbacks. This condition includes also @SpringBootTest annotation (as it effectively registers SpringExtension declaration)

watcher-extension-annotations-order

Analysis output

Watcher Extension generates the metrics report on process shutdown. At this moment CSV file format is supported only.

CSV Report

CSV metrics report is generated in default directory with fixed name test-metrics-report.csv

csv-result

Disabled classes/tests

If the whole test class is disabled by JUnit annotation @Disabled then it's not included at all in the final report, as it's never triggered by JUnit. Otherwise, the report will certainly contain at least a cumulative time metric. The existence of other metrics depends on how many tests were triggered by JUnit engine.

If a test class doesn't contain any active test, or all individual tests are disabled, then JUnit does not trigger any before/after callback. In that case, the report will contain only cumulative value. However, if at least one test is executed, then before/after metrics will be measured too.

The same behavior applies to nested classes.

Order of callbacks execution

The schema of callback executions is as following:

  • For test class:
    • (1) JUnit5-watcher -> beforeAll
    • Other extension -> beforeAll (TestExecutionListener -> beforeTestClass)
    • @BeforeAll for test class
      • For every @Test
        • (2) JUnit5-watcher -> beforeEach
        • Other extension -> beforeEach (TestExecutionListener -> beforeTestMethod)
        • @BeforeEach for test
          • (3) JUnit5-watcher -> beforeTestExecution
          • Actual test code
          • (4) JUnit5-watcher -> afterTestExecution
        • @AfterEach for test
        • Other extension -> afterEach (TestExecutionListener -> afterTestMethod)
        • (5) JUnit5-watcher -> afterEach
      • For every @Nested test
        • (6) JUnit5-watcher -> beforeAll
        • Other extension -> beforeAll (TestExecutionListener -> beforeTestClass)
        • @BeforeAll for nested test class
          • For every @Test in @Nested class
            • (7) JUnit5-watcher -> beforeEach
            • Other extension -> beforeEach (TestExecutionListener -> beforeTestMethod)
            • @BeforeEach for test
              • (8) JUnit5-watcher -> beforeTestExecution
              • Actual test code in nested class
              • (9) JUnit5-watcher -> afterTestExecution
            • @AfterEach for test
            • Other extension -> afterEach (TestExecutionListener -> afterTestMethod)
            • (10) JUnit5-watcher -> afterEach
        • @AfterAll for nested test class
        • Other extension -> afterAll (TestExecutionListener -> afterTestClass)
        • (11) JUnit5-watcher -> afterAll
    • @AfterAll for test class
    • Other extension -> afterAll (TestExecutionListener -> afterTestClass)
    • (12) JUnit5-watcher -> afterAll

If you use Spring integration tests then SpringExtension allows you to define additional test callback class (implementing TestExecutionListener interface). In that case junit5-watcher will count in the metrics time spent on these callbacks too.

Nested test in the example is optional. @BeforeAll and @AfterAll callbacks for nested tests can work only with JDK16+. See JUnit5 documentation for nested classes.

Metrics are calculated in the following way:

  • BeforeAllMs -> time elapsed between (1) and (2)
  • BeforeEachMs -> time elapsed between (2) and (3)
  • TestOnlyMs -> time elapsed between (3) and (4)
  • AfterEachMs -> time elapsed between (4) and (5)
  • AfterAllMs -> time elapsed between last call to (5) and (12) (or (11) and (12) if nested class is used)
  • CumulativeMs -> time elapsed between (1) and (12)

A similar approach is used for calculating metrics for nested classes using time marks starting (6) up to (11).

In rare situations, every individual test in a class may be disabled. In that case, callback before/after each test won't be triggered. The extension should handle this case properly and measure just before/after all callbacks.

Test cumulative value

The cumulative time value for every enclosing class matches the following formula:

  • Cumulative time = Before All + Before Each + Test time + After Each + After All + (Nested classes cumulative time) + DELTA

    • where DELTA represents the additional time spent in between callbacks (mostly JUnit5 internal processing or other)

The formula is explained visually in this picture:

csv-result

junit5-watcher's People

Contributors

grzegorz-aniol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

junit5-watcher's Issues

Exclude junit dependencies for kotlin-test-junit5 to prevent issues with Gradle

We're running into issues building our Gradle project because we're very strict on version conflicts (we use failOnVersionConflict())

The culprit is kotlin-test-junit5 for including some older junit dependencies (which are already defined in the pom.xml of junit5-watcher-ext. So we're safe to exclude them:

        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-test-junit5</artifactId>
            <exclusions>
                <!--
                Not excluding org.junit.* causes version conflict in Gradle when using failOnVersionConflict().
                Force use of junit libraries defined in this pom.xml
                -->
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

I tried to make a fork, but my machine probably is too slow since the 20% tolerance in the tests is not enough.

support for @Nested

Hi,

Using v1.0.0, I am facing same issue as described in #2 , but in my case it's because my tests are using org.junit.jupiter.api.Nested. Then I get :

java.lang.RuntimeException: Cannot find time mark for type AFTER_EACH
at pl.appga.junit5watcher.BenchmarkExtension.getTimeMark-Agz8NFE(BenchmarkExtension.kt:116)
at pl.appga.junit5watcher.BenchmarkExtension.measureAndSaveMetric(BenchmarkExtension.kt:90)
at pl.appga.junit5watcher.BenchmarkExtension.afterAll(BenchmarkExtension.kt:85)
at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Is it the same root cause and it will be fixed with next release ?

Note : the report gets generated though, with one record for each @Nested . There's no record for individual tests, and I don't know if it's intended or not ;-)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.