Continuous testing for java - Keppler - Evaluation of chosen technologies for MVP

This article is updated continuously.

In the recent weeks I did some discovery of what tools look good enough to build the Keppler software. This is its name: Keppler.

Terminal UI

The terminal will be done by Terminal.GUI. The plan is that this UI will communicate with the Server responsible executing the test cases.

Expectations:

  • it must be able to display a tree

    • the tree can be opened, closed, partially opened and closed
  • leafs of the tree can be colored differently

  • leafs of the tree can be selected

  • the tree elements can be selected and further information displayed

  • a tree element multiple hotkey can be bound

The video below shows what the tree looks like with the default theme. The tree can be folded and unfolded, there are default keystrokes for these. Multiple keystrokes can be added to execute various functionalities with the leaf actually selected.

The code is pure C#, meaning high level of awesomeness.

Server

The server will be a Dotnet Core server, probably ends up being gRPC, but for building a POC it will be a REST one.

Responsibility of the Server are the following:

  • parsing the output of Junit's ConsoleLauncher and process it for the UI

  • executing build whenever the code changes in order to reflect the changes on the UI, meaning if the developer adds a new test and the code can compile the new test should be displayed as soon as possible and with zero interaction from the developer

  • can execute commands and parse its result

Test case discovery

Test case discovery happens by Junit's standalone ConsoleLauncher and the Server will process the output. The ConsoleLauncher has multiple output option and the verbose mode looks like the most feasible.

Test case discovery provides the information to display the test case tree. The following cases will demonstrate what capabilities the whole system will have.

Scenario: Tests are organised by packages

These test cases organised by package and nothing else.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.organisedbypackage

Result:

├─ JUnit Jupiter
│  ├─ P1Tests
│  │  ├─ p1Test1()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p1.P1Tests', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ P1Tests
│  ├─ P2Tests
│  │  ├─ p2Test1()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]/[method:p2Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests', methodName = 'p2Test1', methodParameterTypes = '']
│  │  ├─ p2Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]/[method:p2Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests', methodName = 'p2Test2', methodParameterTypes = '']
│  │  ├─ p2Test3()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]/[method:p2Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.organisedbypackages.p2.P2Tests', methodName = 'p2Test3', methodParameterTypes = '']
│  └─ P2Tests
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: it shows enough information to build the tree

Scenario: There are disabled test cases

These tests include one disabled test.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.disabled

Result:

├─ JUnit Jupiter
│  ├─ IncludesDisabledTests
│  │  ├─ p1Test1()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.disabled.IncludesDisabledTests', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.disabled.IncludesDisabledTests', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.disabled.IncludesDisabledTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.disabled.IncludesDisabledTests', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ IncludesDisabledTests
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: it is not marked which test is disabled. Other solution is needed to be able to acquire the information which test is enabled and disabled and provide this information to the UI.

Scenario: there are class level tags

One of the test classes here have a single tag while the other one has multiple tags.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.classleveltags

Result:

├─ JUnit Jupiter
│  ├─ ClassLevelTagsTests
│  │  ├─ p1Test1()
│  │  │       tags: [simpletag]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: [simpletag]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: [simpletag]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTagsTests', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ ClassLevelTagsTests
│  ├─ ClassLevelTags2Tests
│  │  ├─ p1Test1()
│  │  │       tags: [multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: [multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: [multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classleveltags.ClassLevelTags2Tests', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ ClassLevelTags2Tests
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: when class level tags are added they won't be displayed in the output as class level tags, rather added to every test method. From filtering point of view this is what makes sense. If the UI is going to display tags in any shape or form the processing has to deal with finding the class level tags and separate them from the method level tags.

Scenario: there are method level tags

These classes have methods having a single tag and multiple tags.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.methodleveltags

Result:

├─ JUnit Jupiter
│  ├─ MethodLevelTagTest
│  │  ├─ p1Test1()
│  │  │       tags: [p1Test1-tag]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagTest', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ MethodLevelTagTest
│  ├─ MethodLevelTagsTest
│  │  ├─ p1Test1()
│  │  │       tags: [methodTag1, methodTag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: [methodTag3, methodTag4]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.methodleveltags.MethodLevelTagsTest', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ MethodLevelTagsTest
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: tags are displayed at the methods

Scenario: there are class and method level tags

These classes have method and class level tags.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.classandmethodleveltags

Result:

├─ JUnit Jupiter
│  ├─ MethodLevelTagsTest
│  │  ├─ p1Test1()
│  │  │       tags: [doesnthaveclasstag1, onlyemethodtag]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: [doesnthaveclasstag2, onlyemethodtag1]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.MethodLevelTagsTest', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ MethodLevelTagsTest
│  ├─ ClassLevelTags2Tests
│  │  ├─ p1Test1()
│  │  │       tags: [hasclasstags, multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]/[method:p1Test1()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests', methodName = 'p1Test1', methodParameterTypes = '']
│  │  ├─ p1Test2()
│  │  │       tags: [multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]/[method:p1Test2()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests', methodName = 'p1Test2', methodParameterTypes = '']
│  │  ├─ p1Test3()
│  │  │       tags: [hasclasstags2, multitag1, multitag2]
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]/[method:p1Test3()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.classandmethodleveltags.ClassLevelTags2Tests', methodName = 'p1Test3', methodParameterTypes = '']
│  └─ ClassLevelTags2Tests
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: class level tags are added to the methods of the class and if the methods have their own tags they are added to the tree too.

Scenario: there are test suites

These classes are organised to suites.

Command:

java -jar junit-platform-console-standalone-1.10.3.jar discover -cp 'target/test-classes/' --disable-banner --details=verbose --select-package=com.andrascsanyi.junittest.suite

Result:

├─ JUnit Jupiter
│  ├─ SecondSuiteFirstTests
│  │  ├─ firstTest()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteFirstTests]/[method:firstTest()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteFirstTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.suite.SecondSuiteFirstTests', methodName = 'firstTest', methodParameterTypes = '']
│  │  ├─ secondTest()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteFirstTests]/[method:secondTest()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteFirstTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.suite.SecondSuiteFirstTests', methodName = 'secondTest', methodParameterTypes = '']
│  └─ SecondSuiteFirstTests
│  ├─ SecondSuiteSecondTests
│  │  ├─ firstTest()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteSecondTests]/[method:firstTest()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteSecondTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.suite.SecondSuiteSecondTests', methodName = 'firstTest', methodParameterTypes = '']
│  │  ├─ secondTest()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteSecondTests]/[method:secondTest()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.SecondSuiteSecondTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.suite.SecondSuiteSecondTests', methodName = 'secondTest', methodParameterTypes = '']
│  └─ SecondSuiteSecondTests
│  ├─ FirstSuiteFirstTests
│  │  ├─ firstSuite()
│  │  │       tags: []
│  │  │   uniqueId: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.FirstSuiteFirstTests]/[method:firstSuite()]
│  │  │     parent: [engine:junit-jupiter]/[class:com.andrascsanyi.junittest.suite.FirstSuiteFirstTests]
│  │  │     source: MethodSource [className = 'com.andrascsanyi.junittest.suite.FirstSuiteFirstTests', methodName = 'firstSuite', methodParameterTypes = '']
│  └─ FirstSuiteFirstTests
└─ JUnit Jupiter
├─ JUnit Vintage
│       tags: []
│   uniqueId: [engine:junit-vintage]
│     parent: []
├─ JUnit Platform Suite
│       tags: []
│   uniqueId: [engine:junit-platform-suite]
│     parent: []

Conclusion: it seems the discovery option doesn't provide any metadata suites related.

At this point we can say that the discovery provides enough information to build a test case tree.

Executing tests

The test execution happens with Junit's ConsoleLauncher.

Expectations:

  • Test case discovery

    • can return a list of tests

      • includes meta information like class name, method name, suite name and so on, so it is possible to build the test case tree
  • Test case execution

    • can run a single test

    • can run multiple test cases across multiple classes