TLDR; JUnit is an execution framework. The principles of TDD are about writing code, seeing it fail, writing code to make it pass, seeing it pass and making it better. All applies when writing automated execution code for Testing
I was asked a question from someone learning how to automate with Java. I’ve rewritten and paraphrased the question below.
I see that JUnit is a way for a developer to write Unit tests because I want the outcome of the code to work as per assertions. JUnit is for Unit Testing. And Unit tests are written by developers. When will a tester use JUnit?
I think it is a good question, because JUnit is often presented as a “Unit Testing Framework” and “TDD” is for creating application code. How could any of that apply to automated execution code written to support Testing?
Much of the automated execution code we write is not TDD in a traditional sense.
There are parts of the abstraction code that are suitable for using TDD on.
- writing support classes to create payloads for REST API calls
- writing libraries classes that parse or scrape data
- writing custom data generation routines
- writing random data selection classes
- Domain objects e.g. representing logical entities in the application like User, Account, etc.
Any code that does not directly interact with the application under test is probably a good candidate for some form of TDD or at least for Unit Testing.
What we want to avoid is writing Unit Tests for code that interacts with the application under test. This invariably involves too much mocking and too much maintenance. e.g. I’ve seen people Unit Test Page Objects, which often means mocking out WebDriver, and mocking out the page being modelled in the Page Object.
Also… many of the @Test methods we write involve a form of TDD, at least when I write them, and the approach I try to teach in Java For Testers.
I encourage writing the code we want to see. At that point the abstractions don’t exist, so we have a failing test. Then we write the code and it may still not pass because the application isn’t running. Then we run the test against the application and the feature may not have been complete yet, so the test fails. Or perhaps we are testing something that exists, and the test passes. So we debug the test to see it interacting with the application slowly to convince ourselves it works. Then… we change the data in the test so that we can see if ‘fail’ under different circumstances so we know it is more robust and less prone to false positives.
This isn’t strictly TDD, because we are not driving the development of the application.
But we are adopting the mindset of:
- using tests to drive the creation of code (in our case automated execution and associated abstraction layers and support classes)
- making sure we see the test fail so that we can have more faith that it does not mislead when there is an underlying error in the application
- making sure we see it pass and investigate that we have the coverage we expect from the test
We also have the Refactor step in the TDD process of Red, Green, Refactor.
- Once we have a set of code, that executes without error.
- Refactor it to abstraction layers, domain objects and support classes.
- Do this by writing the code you want to see (which doesn’t exist yet),
- then refactor the existing code to implement the code you want to see.
I have some examples of this in https://github.com/eviltester/automationAbstractions
One common misconception is that JUnit is exclusively a Unit Testing Tool.
JUnit is used for Unit Testing, because it is a ‘code execution framework with built in assertions and supporting features for data driven iteration’.
- code execution framework, meaning that we don’t have to ‘build’ an application and the @Test code has access to any of the code instantiated as it executes, e.g. classes we write. When used for Unit Testing, these are the classes in the application itself. When used for Integration Testing we use the classes we’ve written as Abstraction Layers, Domain Objects, and the libraries we have imported to support our execution (WebDriver, REST Assured etc.). But the ‘thing’ we are executing against is a separate application, rather than an object in memory.
- built in assertions, meaning we write code that ‘fails’ if the condition we expect to be present is not met during the execution
- features for data driven iteration, meaning we can separate variant data from the invariant path encoded in the @Test and reuse the @Test rather than create a ‘loop’ that reads data and feeds it into a set of sequential code
JUnit, TestNG, and even Cucumber, can be viewed as execution frameworks which we harness to automate code and applications effectively. We choose what is most appropriate for the process, tooling, abstraction layer, automation approach, and experience we have. The tooling does not dictate what we do with it. We choose how to apply it most effectively.
I think it is worth have a look through Github at some Automated Execution projects to see the different ways that people use the execution frameworks.
e.g. I have a few:
There will more more if you hunted through my repos list.
And if you look through Github repos by some of the people I’m following on Github, I’m sure you’ll find plenty of additional examples:
Also search Github for “webdriver extension:java filename:*Test*” i.e. find webdriver in any java file with Test in the filename
And you’ll find a lot of code to study.
It may not always be intended as a ‘good example’ but we learn coding by reading code.
Can we use JUnit and TDD for Testing?
Yes we can. The associated tooling and approaches are not limited to subset of coding. We don’t just code applications, which is the traditional application of JUnit and TDD. We code support tools, test tools, automated execution, build scripts, release processes, adhoc utilities, etc.
JUnit and TDD can be used whenever someone writes any type of code.