TDD: Unit tests

djmil 2023-07-18 19:27:09 +02:00
parent 7ea1a78b5b
commit c0971deee8
5 changed files with 169 additions and 126 deletions

161
Home.md

@ -1,111 +1,50 @@
--- ---
gitea: none gitea: none
include_toc: true include_toc: true
--- ---
This repo is my first attempt to learn `SpringBoot` following [this](https://spring.academy/courses/building-a-rest-api-with-spring-boot/lessons/introduction) tutorial. The setup is Visual Code IDE alongside with [SpringBoot](https://code.visualstudio.com/docs/java/java-spring-boot) plugin. This repo is my first attempt to learn `SpringBoot` following [this](https://spring.academy/courses/building-a-rest-api-with-spring-boot/lessons/introduction) tutorial. The setup is Visual Code IDE alongside with [SpringBoot](https://code.visualstudio.com/docs/java/java-spring-boot) plugin.
# Spring Initializr # Spring Initializr
In VsCode press `cmd+shif+p` and type `Spring Initilizr`. Choose: In VsCode press `cmd+shif+p` and type `Spring Initilizr`. Choose:
- Gradle Project - Gradle Project
- _SpringBoot version:_ latest (3.1.1) - _SpringBoot version:_ latest (3.1.1)
- _project language:_ Java - _project language:_ Java
- _group id:_ djmil - _group id:_ djmil
- _artifact id (aka project name):_ cashcard - _artifact id (aka project name):_ cashcard
- _packaging type:_ jar - _packaging type:_ jar
- _java version:_ 17 - _java version:_ 17
- _dependencies:_ - _dependencies:_
- SpringWeb - SpringWeb
Essentially, this will generate a default minimalistic jet functional SpringBoot project. Entry point, aka `main()` can be found in [src/main/java/djmil/cashcard/CashcardApplication.java](http://192.168.8.55:3000/HQLAx/FamilyCashCard/src/branch/main/src/main/java/djmil/cashcard/CashcardApplication.java). To run the application - press `ctrl+F5` or Play button in the top right corner of an editor. Essentially, this will generate a default minimalistic jet functional SpringBoot project. Entry point, aka `main()` can be found in [src/main/java/djmil/cashcard/CashcardApplication.java](http://192.168.8.55:3000/HQLAx/FamilyCashCard/src/branch/main/src/main/java/djmil/cashcard/CashcardApplication.java). To run the application - press `ctrl+F5` or Play button in the top right corner of an editor.
# TDD # TDD
## myFirstTest Different tests can be written at different levels of the system. At each level, there is a balance between the speed of execution, the “cost” to maintain the test, and the confidence it brings to system correctness. This hierarchy is often represented as a “testing pyramid”.
Let's start with the simplest thing you can imagine: a single test method with a single statement. Create [src/test/java/example/cashcard/CashCardJsonTest.java](http://192.168.8.55:3000/HQLAx/FamilyCashCard/src/commit/5ff71154302523ab5ebd0a291e3f5819aed8fdb9/src/test/java/djmil/cashcard/CashCardJsonTest.java): ![Testing pyramid](https://blog.missiondata.com/wp-content/uploads/MD_TestingPyramid2x-1560x1045.png "Testing pyramid")
``` java ## Unit Tests
package example.cashcard; A [[Unit Tests]] exercises a small “unit” of the system that is isolated from the rest of the system. They should be simple and speedy. You want a high ratio of Unit Tests in your testing pyramid as theyre key to designing highly cohesive, loosely coupled software.
import org.junit.jupiter.api.Test; ## Integration Tests
import static org.assertj.core.api.Assertions.assertThat; Integration Tests exercise a subset of the system and may exercise groups of units in one test. They are more complicated to write and maintain, and run slower than unit tests.
public class CashCardJsonTest { ## End-to-End Tests
An End-to-End Test exercises the system using the same interface that a user would, such as a web browser. While extremely thorough, End-to-End Tests can be very slow and fragile because they use simulated user interactions in potentially complicated UIs. Implement the smallest number of these tests.
@Test
public void myFirstTest() { ## TDD Cycle
assertThat(1).isEqualTo(42);
} Software development teams love to move fast. So how do you go fast forever? By continuously improving and simplifying your code this is called **refactoring**. One of the only ways you can safely refactor is when you have a trustworthy test suite. Thus, the best time to refactor the code you're currently focusing on is during the TDD cycle. This is called the Red, Green, Refactor development loop:
}
``` 1. **Red:** Write a failing test for the desired functionality.
2. **Green:** Implement the simplest thing that can work to make the test pass.
The `@Test` annotation is part of the JUnit library, and the `assertThat` method is part of the AssertJ library. Both of these libraries are imported after the package statement. 3. **Refactor:** Look for opportunities to simplify, reduce duplication, or otherwise improve the code without changing any behavior—to _refactor._
4. Repeat!
A common convention (but not a requirement) is to always use the Test suffix for test classes. Weve done that here. The full class name CashCardJsonTest.java gives you a clue about the nature of the test we're about to write.
In true Test-First fashion, we've written a failing test first. It's important to have a failing test first so you can have high confidence that whatever you did to fix the test actually worked.
Toggle terminal with `ctrl+tilda` and type
```bash
./gradlew test
```
## Testing the CashCard Data Contract
```java
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.beans.factory.annotation.Autowired;
@JsonTest
public class CashCardJsonTest {
@Autowired
private JacksonTester<CashCard> json;
```
Marking CashCardJsonTest with `@JsonTest` annotation makes it a test class which uses the Jackson framework (which is included as part of Spring). This provides extensive JSON testing and parsing support. It also establishes all the related behavior to test JSON objects.
### @Autowired
`@Autowired` is an annotation that directs Spring to create an object of the requested type. `JacksonTester` is a convenience wrapper to the Jackson JSON parsing library. It handles serialization and deserialization of JSON objects.
To create a CashCard class and the constructor thats used in the cashCardSerializationTest() test, create the file `src/main/java/example/cashcard/CashCard.java` with the following contents (notice that this file is under in the src/main directory, not the src/test directory):
```java
package example.cashcard;
public record CashCard(Long id, Double amount) {
}
```
### The contract file
`src/test/resources/example/cashcard/expected.json`
```json
{
"id": 99,
"amount": 123.45
}
```
### The test
```java
@Test
public void cashCardSerializationTest() throws IOException {
CashCard cashCard = new CashCard(99L, 123.45);
assertThat(json.write(cashCard)).isStrictlyEqualToJson("expected.json");
assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.id");
assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.id")
.isEqualTo(99);
assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.amount");
assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.amount")
.isEqualTo(123.45);
}
```

119
Unit Tests.md Normal file

@ -0,0 +1,119 @@
---
gitea: none
include_toc: true
---
# My first unit test
Let's start with the simplest thing you can imagine: a single test method with a single statement. Create [src/test/java/example/cashcard/CashCardJsonTest.java](http://192.168.8.55:3000/HQLAx/FamilyCashCard/src/commit/5ff71154302523ab5ebd0a291e3f5819aed8fdb9/src/test/java/djmil/cashcard/CashCardJsonTest.java):
``` java
package djmil.cashcard;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CashCardJsonTest {
@Test
public void myFirstTest() {
assertThat(1).isEqualTo(42);
}
}
```
The `@Test` annotation is part of the JUnit library, and the `assertThat` method is part of the AssertJ library. Both of these libraries are imported after the package statement.
A common convention (but not a requirement) is to always use the Test suffix for test classes. Weve done that here. The full class name CashCardJsonTest.java gives you a clue about the nature of the test we're about to write.
In true Test-First fashion, we've written a failing test first. It's important to have a failing test first so you can have high confidence that whatever you did to fix the test actually worked.
Toggle terminal with `ctrl+tilda` and type
```bash
./gradlew test
```
# Testing the CashCard Data Contract
```java
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.beans.factory.annotation.Autowired;
@JsonTest
public class CashCardJsonTest {
@Autowired
private JacksonTester<CashCard> json;
```
Marking CashCardJsonTest with `@JsonTest` annotation makes it a test class which uses the Jackson framework (which is included as part of Spring). This provides extensive JSON testing and parsing support. It also establishes all the related behavior to test JSON objects.
### @Autowired
`@Autowired` is an annotation that directs Spring to create an object of the requested type. `JacksonTester` is a convenience wrapper to the Jackson JSON parsing library. It handles serialization and deserialization of JSON objects.
To create a CashCard class and the constructor thats used in the `cashCardSerializationTest()` test, create the file `src/main/java/djmil/cashcard/CashCard.java` with the following contents (notice that this file is under in the `src/main` directory, not the `src/test` directory):
```java
package djmil.cashcard;
public record CashCard(Long id, Double amount) {
}
```
### The contract file
`src/test/resources/djmil/cashcard/expected.json`
```json
{
"id": 99,
"amount": 123.45
}
```
**NOTE** Resources
Pay attention to the path `djmil/cashcard/` is essentially a *package name*. It is shared between different aspects of the project:
- src/main/java - code
- src/tests/java - tests
- src/tests/resources - static resources for testing.
Essentially `gradle` is responsible to map different parts of source code onto final package to be accessible for java via *classpath*.
### The test
```java
@Test
public void cashCardSerializationTest() throws IOException {
CashCard cashCard = new CashCard(99L, 123.45);
assertThat(json.write(cashCard)).isStrictlyEqualToJson("expected.json");
assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.id");
assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.id")
.isEqualTo(99);
assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.amount")
assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.amount")
.isEqualTo(123.45);
}
```
`.isStrictlyEqualToJson("expected.json");` will try to load static file from `FamilyCashCard/build/resources/test/djmil/cashcard` directory.
# Testing Deserialization
```java
@Test
public void cashCardDeserializationTest() throws IOException {
String expected = """
{
"id":1000,
"amount":67.89
}
""";
assertThat(json.parse(expected)).isEqualTo(new CashCard(1000L, 67.89));
assertThat(json.parseObject(expected).id()).isEqualTo(1000);
assertThat(json.parseObject(expected).amount()).isEqualTo(67.89);
}
```

@ -1,11 +0,0 @@
---
gitea: none
include_toc: true
---
This is a test for special page called '_sidebar'
# [[Home]]
# subfolder
## Note1
# [[testPage]]

@ -1,3 +0,0 @@
This note inside sub folder was created by Obsidian. I hope that GiteWiki engine would be able to handle this.
A link to the [[Home]] page

@ -1 +0,0 @@
This wiki page is on the same level as [[Home]] page