📜 ⬆️ ⬇️

How to build a pyramid in the trunk or Test-Driven Development applications for Spring Boot

The Spring Framework is often cited as an example of the Cloud Native framework created for working in the cloud, developing Twelve-Factor applications , microservices, and one of the most stable, but at the same time innovative products. But in this article I would like to dwell on another strong point of Spring: is it his support for developing through testing (TDD?). In spite of TDD, I often noticed that Spring projects either ignore some best practices for testing, or invent their own bikes, or do not write tests at all because they are "slow" or "unreliable." And that's exactly how to write fast and reliable tests for applications on the Spring Framework and to develop through testing, and I will tell. So if you use Spring (or want to start), understand what tests are in general (or want to understand), or think that contextLoads is the necessary and sufficient level of integration testing - it will be interesting!


"TDD-rumenost" feature is very ambiguous, and poorly measurable, but still Spring has a lot of things that by design helps to write integration and unit tests with a minimum of effort. For example:



For a start, a small, but necessary, introduction about TDD and testing in general.


Test Driven Development


At the heart of TDD is a very simple idea - we write tests before we write code. In theory, it sounds scary, but after some time comes an understanding of the practices and techniques, and the option of writing tests afterwards causes significant discomfort. One of the key practices is iteration , i.e. make all small, focused iterations, each of which is described as a red-green-refactor .


In the red phase, we write a falling test, and it’s very important that it falls with a clear, understandable reason and description, and that the test itself is complete and passes when the code is written. The test should check the behavior , not the implementation , i.e. follow the black box approach, further explain why.


In the green phase, we write the minimum necessary code to pass the test. Sometimes it is interesting to practice and bring to asbestos (although it is better not to get involved) and when the function returns a boolean depending on the state of the system, the first “pass” may be just return true .


In the refactoring phase, which can only be started when all the tests are green , we refactor the code and bring it to the proper state. It is not necessary even for a piece of code that we wrote, therefore, it is important to start refactoring on a stable system. The "black box" approach will help to perform refactoring, changing the implementation, but not touching the behavior.


I will also talk about different aspects of TDD in the future; after all, this is the idea of ​​a series of articles, so now I’ll not particularly dwell on the details. But in advance of responding to the standard criticism of TDD, I will mention a couple of myths that I hear often.



The main goal of TDD and testing in general is to give the team confidence that the system is stable. Therefore, none of the testing practices determine how much and what tests to write. Write as you see fit, how much you need to be sure that right now you can put the code in production and it will work . There are people who consider rapid integration tests as an ultimatum “black box” necessary and sufficient, and unit tests as optional. Someone says that e2e tests are not so critical with the possibility of a quick rollback to the previous version and the availability of canary releases. How many teams - so many approaches, it is important to find your own.

One of my goals is to move away from the TDD story from the “test-developing function that adds two numbers” format and look at the real application, a kind of test practice evaporated to a minimal application, collected on real projects. As such a half-real example, I will use a small web application, which I myself invented, for an abstract factories bakery-bakery - Cake Factory . I plan to write small articles, focusing each time on a separate piece of application functionality and show, through TDD, you can design the API, the internal structure of the application and maintain constant refactoring.


A rough plan for a series of articles, as I see it at the moment, is:


  1. Walking skeleton - an application framework on which you can run the Red-Green-Refactor cycle
  2. UI Testing and Behavior Driven Design
  3. Data Access Testing (Spring Data)
  4. Authorization and Authentication Testing (Spring Security)
  5. Reactive stack (WebFlux + Project Reactor)
  6. Interaction (micro) services and contracts (Spring Cloud)
  7. Testing Message Queuing (Spring Cloud)

This introductory article will be about points 1 and 2 - I will create an application framework and a basic UI test using the BDD approach - or behavior-driven development . Each article will begin with a user story , but to save time about the "food" part, I will not speak. User story will be written in English, it will soon be clear why. All code examples can be found on GitHub, so I will not review all the code, only the important parts.


User story is a description of the features of the application in natural language, which are usually written on behalf of the user of the system.

User story 1: The user sees the welcome page.


As Alice, a new user
Cake Factory web site
So that I know when Cake Factory is about to launch

Acceptance criteria:
Scenario: a visit to the web site
Given i'm a new user
When I visit Cake Factory web site
Then I see a message 'Thanks for your interest'
And I see a message 'The web-site is coming soon ...'

It will require knowledge of what Behavior-Driven Development and Cucumber , the basics of Spring Boot Testing, are .


The first user story is quite basic, but the goal is not yet in complexity, but in the creation of walking skeleton , a minimal application to start the TDD cycle .


After creating a new project on Spring Initializr with Web and Mustache modules, first I will need a few more changes to build.gradle :



By and large, this is already the basic “skeleton” of the application, you can already write the first test.


To make it easier to navigate the code, I will briefly tell you about the technologies used.


Cucumber


Cucumber is a Behavior-Driven Development framework that helps create "executable specifications", i.e. run tests (specifications) written in natural language. The Cucumber plugin analyzes Java source code (and many other languages) and uses step definitions to run real code. Step definitions are class methods annotated by @Given , @When , @Then and other annotations.


HtmlUnit


The main project page calls HtmlUnit "GUI-less browser for Java applications." Unlike Selenium, HtmlUnit does not launch a real browser and, most importantly, does not render the page at all, working directly with the DOM. JavaScript is supported through the Mozilla Rhino engine. HtmlUnit is well suited for classic applications, but not very friendly with Single Page Apps. For a start, it will be enough, and then I will try to show that even such things as a test framework can be made implementation detail, and not the foundation of the application.


First test


Now I just need a user-story written in English. The best trigger for launching the next TDD iteration is the acceptance criteria written in such a way that they can be turned into executable specifications with a minimum of gestures.


Ideally, user stories should be written in such a way that they can simply be copied to the BDD specification and run. This is not always easy and not always possible, but it should be the goal of the product owner and the whole team, although not always achievable.

So, my first feature.


 Feature: Welcome page Scenario: a user visiting the web-site visit before the launch date Given a new user, Alice When she visits Cake Factory web-site Then she sees a message 'Thank you for your interest' And she sees a message 'The web-site is coming in December!' 

If you generate steps descriptions (the Intellij IDEA plugin helps Gherkin support) and run the test, it will of course be green - it is not testing anything yet. And here comes the important phase of work on the test - you need to write a test, as if the main code was written .


Often, those who are starting to detoxify TDD have a stupor here - it is difficult to put algorithms and logic of something that does not exist in their heads. Therefore, it is very important to have as small and focused iterations as possible, starting from the user-story and going down to the integration and unit-level. It is important to focus on one test at a time and try to get wet and ignore dependencies that are not important yet. I sometimes noticed how people easily go to the side - they create an interface or class for dependency, they immediately generate an empty test class for it, another dependency is added there, another interface is created, and so on.


If the story is "it would be necessary to refresh the status at the save game" it is very difficult to automate and formalize it. In my example, each step can be clearly laid out in a sequence of steps that can be described by code. It is clear that this is the simplest example and it shows little, but I hope that further, with increasing complexity, it will be more interesting.

Red


So, for my first feature, I created the following steps:


 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WelcomePage { private WebClient webClient; private HtmlPage page; @LocalServerPort private int port; private String baseUrl; @Before public void setUp() { webClient = new WebClient(); baseUrl = "http://localhost:" + port; } @Given("a new user, Alice") public void aNewUser() { // nothing here, every user is new by default } @When("she visits Cake Factory web-site") public void sheVisitsCakeFactoryWebSite() throws IOException { page = webClient.getPage(baseUrl); } @Then("she sees a message {string}") public void sheSeesAMessageThanksForYourInterest(String expectedMessage) { assertThat(page.getBody().asText()).contains(expectedMessage); } } 

A couple of points to pay attention to:



And the test, as expected, crashes with a 404 for http://localhost:51517 error 404 for http://localhost:51517 .


The errors that the test falls with are incredibly important, especially when it comes to units or integration tests, and these errors are part of the API. If the test fails with NullPointerException it is not too good, but the BaseUrl configuration property is not set - much better.

Green


To make the test green, I added a base controller and view with minimal HTML:


 @Controller public class IndexController { @GetMapping public String index() { return "index"; } } 

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Cake Factory</title> </head> <body> <h1>Thank you for your interest</h1> <h2>The web-site is coming in December!</h2> </body> </html> 

The test is green, the application works, although it is executed in the tradition of austere engineering design.


On a real project and in a balanced team, I would, of course, have sat down with the designer and we would have turned naked HTML into something much more beautiful. But under the article the miracle will not happen, the princess will remain a frog.

The question “what part of a design in TDD” is not so simple. One of the practices that I found useful - at first not even look at the UI at all (not even launch the application to save nerves), write a test, make it green - and then, having a stable foundation, work on the front end, constantly restarting the tests .


Refactor


In the first iteration, there is no particular refactoring, but even though I spent the last 10 minutes choosing a template for Bulma , which can be counted as refactoring!


Finally


As long as the application does not work with security, neither from the database nor the API, then the tests and TDD look pretty simple. And in general, from the testing pyramid, I touched only the very top, the UI test. But this, in part, is the secret of the lean approach - to do everything in small iterations, one component at a time. It helps to focus on tests, make them simple, and control the quality of the code. I hope that the following articles will be more interesting.


Links



PS The title of the article is not as crazy as it may seem at the beginning, I think many have already guessed. "How to build a pyramid in your boot" is a reference to the testing pyramid (I’ll tell you more about it later) and Spring Boot, where boot in British English also means "trunk".


')

Source: https://habr.com/ru/post/431306/


All Articles