Skip to content

Commit 057f507

Browse files
committed
Day 21: Changing tides.
1 parent d4e2152 commit 057f507

File tree

11 files changed

+242
-0
lines changed

11 files changed

+242
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Here are the different challenges :
6666
- [Day 18: Automatically detect Linguistic Anti-Patterns (LAP).](exercise/day18/docs/challenge.md)
6767
- [Day 19: Loosing up dead weight.](exercise/day19/docs/challenge.md)
6868
- [Day 20: No more exceptions in our domain.](exercise/day20/docs/challenge.md)
69+
- [Day 21: Refactor the tests and production code to Output-based tests.](exercise/day21/docs/challenge.md)
6970

7071
### Solutions
7172
A solution proposal will be published here every day during the `Advent Of Craft` containing `the code` and a `step by step` guide.

exercise/day21/docs/challenge.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
## Day 21: Changing tides.
2+
3+
Today, you are still fighting and while the main storm
4+
passed you are still not out of danger yet!
5+
6+
You will need to reach the nearby island quickly to recover
7+
since your ship is badly broken.
8+
9+
Today's exercise is about fixing tests and fixing code.
10+
11+
A refactoring is needed but a first step needs to be made
12+
in the tests.
13+
14+
> **Challenge of day 21: Refactor the tests and production code to Output-based tests.**
15+
16+
Before refactoring the code, here are some explanations regarding the different kind of tests as explained by Vladimir
17+
Khorikov in his book [Unit Testing Principles, Practices and Patterns.](https://www.manning.com/books/unit-testing).
18+
19+
### Different styles of tests
20+
21+
#### State-Based
22+
23+
```java
24+
class StateBasedTests {
25+
@Test
26+
void it_should_add_given_product_to_the_order() {
27+
val product = new Product("Free Guy");
28+
val sut = new Order();
29+
30+
sut.add(product);
31+
32+
// Verify the state
33+
assertThat(sut.getProducts())
34+
.hasSize(1)
35+
.allMatch(item -> item.equals(product));
36+
}
37+
38+
@AllArgsConstructor
39+
class Product {
40+
private final String name;
41+
}
42+
43+
class Order {
44+
private final List<Product> products = new ArrayList<>();
45+
46+
List<Product> getProducts() {
47+
return Collections.unmodifiableList(products);
48+
}
49+
50+
void add(Product product) {
51+
products.add(product);
52+
}
53+
}
54+
}
55+
```
56+
57+
![State-Based](img/state-based.png)
58+
59+
#### Output-Based
60+
61+
```java
62+
class OutputBasedTests {
63+
@Test
64+
void discount_of_2_products_should_be_2_percent() {
65+
val product1 = new Product("Kaamelott");
66+
val product2 = new Product("Free Guy");
67+
68+
// Call on the SUT (here PriceEngine)
69+
// No side effects -> Pure function
70+
val discount = PriceEngine.calculateDiscount(product1, product2);
71+
72+
assertThat(discount).isEqualTo(0.02);
73+
}
74+
}
75+
```
76+
77+
![Output-Based](img/output-based.png)
78+
79+
#### Communication-Based
80+
81+
```java
82+
class CommunicationBasedTests {
83+
@Test
84+
void greet_a_user_should_send_an_email_to_it() {
85+
final var email = "[email protected]";
86+
final var emailGatewayMock = mock(EmailGateway.class);
87+
// Substitute collaborators with Test Double
88+
final var sut = new Controller(emailGatewayMock);
89+
90+
sut.greetUser(email);
91+
92+
// Verify that the SUT calls those collaborators correctly
93+
verify(emailGatewayMock, times(1)).sendGreetingsEmail(email);
94+
}
95+
96+
interface EmailGateway {
97+
Try<String> sendGreetingsEmail(String email);
98+
}
99+
100+
@AllArgsConstructor
101+
class Controller {
102+
private final EmailGateway emailGateway;
103+
104+
public Try<String> greetUser(String email) {
105+
return emailGateway.sendGreetingsEmail(email);
106+
}
107+
}
108+
}
109+
```
110+
111+
![Communication-Based](img/communication-based.png)
112+
113+
![snippet of the day](snippet.png)
64.4 KB
Loading
71.6 KB
Loading
62.1 KB
Loading

exercise/day21/docs/snippet.png

389 KB
Loading

exercise/day21/pom.xml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.advent-of-craft</groupId>
9+
<artifactId>advent-of-craft2023</artifactId>
10+
<version>1.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>audit</artifactId>
14+
<version>1.0-SNAPSHOT</version>
15+
<properties>
16+
<mockito-core.version>4.8.1</mockito-core.version>
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.mockito</groupId>
22+
<artifactId>mockito-core</artifactId>
23+
<version>${mockito-core.version}</version>
24+
<scope>test</scope>
25+
</dependency>
26+
</dependencies>
27+
28+
</project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package audit;
2+
3+
import java.nio.file.Paths;
4+
import java.time.LocalDateTime;
5+
import java.time.format.DateTimeFormatter;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
9+
public class AuditManager {
10+
private final int maxEntriesPerFile;
11+
private final String directoryName;
12+
private final FileSystem fileSystem;
13+
14+
public AuditManager(int maxEntriesPerFile, String directoryName, FileSystem fileSystem) {
15+
this.maxEntriesPerFile = maxEntriesPerFile;
16+
this.directoryName = directoryName;
17+
this.fileSystem = fileSystem;
18+
}
19+
20+
public void addRecord(String visitorName, LocalDateTime timeOfVisit) {
21+
String[] filePaths = fileSystem.getFiles(directoryName);
22+
String[] sorted = sortByIndex(filePaths);
23+
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
24+
String newRecord = visitorName + ";" + timeOfVisit.format(dateTimeFormatter);
25+
26+
if (sorted.length == 0) {
27+
String newFile = Paths.get(directoryName, "audit_1.txt").toString();
28+
fileSystem.writeAllText(newFile, newRecord);
29+
return;
30+
}
31+
32+
int currentFileIndex = sorted.length - 1;
33+
String currentFilePath = sorted[currentFileIndex];
34+
List<String> lines = fileSystem.readAllLines(currentFilePath);
35+
36+
if (lines.size() < maxEntriesPerFile) {
37+
lines.add(newRecord);
38+
String newContent = String.join(System.lineSeparator(), lines);
39+
fileSystem.writeAllText(currentFilePath, newContent);
40+
} else {
41+
String newName = "audit_" + (currentFileIndex + 2) + ".txt";
42+
String newFile = Paths.get(directoryName, newName).toString();
43+
fileSystem.writeAllText(newFile, newRecord);
44+
}
45+
}
46+
47+
private String[] sortByIndex(String[] filePaths) {
48+
return Arrays.stream(filePaths)
49+
.sorted()
50+
.toArray(String[]::new);
51+
}
52+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package audit;
2+
3+
import java.util.List;
4+
5+
public interface FileSystem {
6+
String[] getFiles(String directoryName);
7+
8+
void writeAllText(String filePath, String content);
9+
10+
List<String> readAllLines(String filePath);
11+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package audit;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.mockito.Mockito;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.List;
8+
9+
import static org.mockito.Mockito.verify;
10+
import static org.mockito.Mockito.when;
11+
12+
class AuditManagerTests {
13+
@Test
14+
void addsNewVisitorToANewFileWhenEndOfLastFileIsReached() {
15+
FileSystem fileSystemMock = Mockito.mock(FileSystem.class);
16+
when(fileSystemMock.getFiles("audits"))
17+
.thenReturn(new String[]{
18+
"audits/audit_2.txt",
19+
"audits/audit_1.txt"}
20+
);
21+
when(fileSystemMock.readAllLines("audits/audit_2.txt"))
22+
.thenReturn(List.of(
23+
"Peter;2019-04-06 16:30:00",
24+
"Jane;2019-04-06 16:40:00",
25+
"Jack;2019-04-06 17:00:00"
26+
));
27+
28+
var sut = new AuditManager(3, "audits", fileSystemMock);
29+
30+
sut.addRecord("Alice", LocalDateTime.parse("2019-04-06T18:00:00"));
31+
32+
verify(fileSystemMock).writeAllText("audits/audit_3.txt", "Alice;2019-04-06 18:00:00");
33+
}
34+
}
35+
36+

0 commit comments

Comments
 (0)