-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
17 changed files
with
360 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
## Day 15: Put a code under tests. | ||
|
||
Imagine we need to adapt the code below to support a new document template | ||
- We want to be sure to not introduce regression / have a safety net | ||
|
||
### Assessing the right type of tests | ||
|
||
- Let's add some tests | ||
- We have plenty of possible combinations | ||
- We could use `ParameterizedTests` to make those assertions | ||
|
||
### Use Approval Testing instead | ||
|
||
We can use `Approval Testing` to quickly put legacy code under tests. | ||
|
||
It is also called : `Characterization Tests` OR `Golden Master` | ||
- Unit testing assertions can be difficult to use and long to write | ||
- Approval tests simplify this by taking a snapshot of the results / confirming that they have not changed at each run | ||
|
||
Let's set that up: | ||
- We add [ApprovalTests](https://github.com/approvals/approvaltests.java) dependency in our pom | ||
- Add `.gitignore` file to exclude `*.received.*` from git | ||
- Instead, let's use the power of Approval !!! | ||
- We can generate combinations and have only 1 test (golden master) using `CombinationApprovals.verifyAllCombinations` | ||
|
||
How it works: | ||
- On the first run, 2 files are created | ||
- `DocumentTests.combinationTests.received.txt`: the result of the call of the method under test | ||
- `DocumentTests.combinationTests.approved.txt`: the approved version of the result (approved manually) | ||
- The library simply compare the 2 text files, so it fails the first time you run it | ||
- It compares the actual result and an empty file | ||
|
||
- We need to approve the `received` file to make the test passes | ||
- Meaning we create the `approved` one with the result of the current production code | ||
|
||
### Refactoring time | ||
- We can even improve the test by making it totally dynamic | ||
- If we add a new Enum entry the test will fail | ||
- Forcing us to approve the new version of the test output | ||
|
||
- In just a few minutes, we have successfully covered a cryptic code with robust tests | ||
|
||
>**Tip of the day: Choosing the right type of test can help you gather better feedback on your code.** | ||
### Share your experience | ||
|
||
How does your code look like? | ||
|
||
Please let everyone know in the discord. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
## Day 15: Put a code under tests. | ||
- Imagine we need to adapt the code below to support a new document template | ||
- We want to be sure to not introduce regression / have a safety net | ||
|
||
```java | ||
public enum DocumentTemplateType { | ||
DRP("DEER", I), | ||
DPM("DEER", L), | ||
ATP("AUTP", I), | ||
ATM("AUTM", L), | ||
SPEC("SPEC", ALL), | ||
GLP("GLPP", I), | ||
GLM("GLPM", L); | ||
|
||
private final String documentType; | ||
private final RecordType recordType; | ||
|
||
DocumentTemplateType(String documentType, RecordType recordType) { | ||
this.documentType = documentType; | ||
this.recordType = recordType; | ||
} | ||
|
||
public static DocumentTemplateType fromDocumentTypeAndRecordType(String documentType, String recordType) { | ||
for (DocumentTemplateType dtt : DocumentTemplateType.values()) { | ||
if (dtt.getDocumentType().equalsIgnoreCase(documentType) | ||
&& dtt.getRecordType().equals(RecordType.valueOf(recordType))) { | ||
return dtt; | ||
} else if (dtt.getDocumentType().equalsIgnoreCase(documentType) | ||
&& dtt.getRecordType().equals(ALL)) { | ||
return dtt; | ||
} | ||
} | ||
throw new IllegalArgumentException("Invalid Document template type or record type"); | ||
} | ||
|
||
private RecordType getRecordType() { | ||
return recordType; | ||
} | ||
|
||
private String getDocumentType() { | ||
return documentType; | ||
} | ||
} | ||
``` | ||
|
||
- Let's add some tests | ||
- We have plenty of possible combinations | ||
- We could use `ParameterizedTests` to make those assertions | ||
|
||
```java | ||
@Test | ||
void given_glpp_and_individual_prospect_should_return_glpp() { | ||
final var result = DocumentTemplateType.fromDocumentTypeAndRecordType("GLPP", "INDIVIDUAL_PROSPECT"); | ||
assertThat(result).isEqualTo(DocumentTemplateType.GLPP); | ||
} | ||
|
||
@Test | ||
void given_glpp_and_legal_prospect_should_fail() { | ||
assertThrows(IllegalArgumentException.class, | ||
() -> DocumentTemplateType.fromDocumentTypeAndRecordType("GLPP", "LEGAL_PROSPECT")); | ||
} | ||
``` | ||
|
||
### Use Approval Testing instead | ||
We can use `Approval Testing` to quickly put legacy code under tests. | ||
|
||
Learn more about it [here](https://understandlegacycode.com/approval-tests/). | ||
|
||
It is also called : `Characterization Tests` OR `Golden Master` | ||
- Unit testing assertions can be difficult to use and long to write | ||
- Approval tests simplify this by taking a snapshot of the results / confirming that they have not changed at each run | ||
|
||
|
||
We add [ApprovalTests](https://github.com/approvals/approvaltests.java) dependency in our pom | ||
|
||
```xml | ||
<properties> | ||
<approvaltests.version>22.3.2</approvaltests.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.approvaltests</groupId> | ||
<artifactId>approvaltests</artifactId> | ||
<version>${approvaltests.version}</version> | ||
</dependency> | ||
</dependencies> | ||
``` | ||
|
||
- Add `.gitignore` file to exclude `*.received.*` from git | ||
|
||
```text | ||
### Approval exclusion ### | ||
*.received.* | ||
``` | ||
|
||
- Instead, let's use the power of Approval !!! | ||
|
||
 | ||
|
||
- We can generate combinations and have only 1 test (golden master) using `CombinationApprovals.verifyAllCombinations` | ||
|
||
 | ||
|
||
- We use all the possible values as inputs | ||
- It will make a cross product for the test | ||
|
||
```java | ||
@Test | ||
void combinationTests() { | ||
CombinationApprovals.verifyAllCombinations( | ||
DocumentTemplateType::fromDocumentTypeAndRecordType, | ||
new String[]{"AUTP", "AUTM", "DEERPP", "DEERPM", "SPEC", "GLPP", "GLPM"}, | ||
new String[]{"INDIVIDUAL_PROSPECT", "LEGAL_PROSPECT", "ALL"} | ||
); | ||
} | ||
``` | ||
|
||
- On the first run, 2 files are created | ||
- `DocumentTests.combinationTests.received.txt`: the result of the call of the method under test | ||
- `DocumentTests.combinationTests.approved.txt`: the approved version of the result (approved manually) | ||
|
||
 | ||
|
||
- The library simply compare the 2 text files, so it fails the first time you run it | ||
|
||
 | ||
|
||
- It compares the actual result and an empty file | ||
|
||
 | ||
|
||
- We need to approve the `received` file to make the test passes | ||
- Meaning we create the `approved` one with the result of the current production code | ||
|
||
```bash | ||
cp src/test/java/DocumentTests.combinationTests.received.txt src/test/java/DocumentTests.combinationTests.approved.txt | ||
``` | ||
|
||
### Refactoring time | ||
- We can even improve the test by making it totally dynamic | ||
- If we add a new Enum entry the test will fail | ||
- Forcing us to approve the new version of the test output | ||
|
||
```java | ||
@Test | ||
void combinationTests() { | ||
verifyAllCombinations( | ||
DocumentTemplateType::fromDocumentTypeAndRecordType, | ||
Arrays.stream(DocumentTemplateType.values()).map(Enum::name).toArray(String[]::new), | ||
Arrays.stream(RecordType.values()).map(Enum::name).toArray(String[]::new) | ||
); | ||
} | ||
``` | ||
|
||
- In just a few minutes, we have successfully covered a cryptic code with robust tests | ||
|
||
 | ||
|
||
> We are now ready for refactoring... 😉 | ||
|
||
- We refactor the production code | ||
|
||
```java | ||
private static final Map<String, DocumentTemplateType> mapping = | ||
List.of(DocumentTemplateType.values()) | ||
.toMap(v -> formatKey(v.getDocumentType(), v.getRecordType().name()), v -> v) | ||
.merge(List.of(RecordType.values()).toMap(v -> formatKey(SPEC.name(), v.name()), v -> SPEC)); | ||
|
||
private static String formatKey(String documentType, String recordType) { | ||
return documentType.toUpperCase() + "-" + recordType.toUpperCase(); | ||
} | ||
|
||
public static DocumentTemplateType fromDocumentTypeAndRecordType(String documentType, String recordType) { | ||
return mapping.get(formatKey(documentType, recordType)) | ||
.getOrElseThrow(() -> new IllegalArgumentException("Invalid Document template type or record type")); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns="http://maven.apache.org/POM/4.0.0" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>com.advent-of-craft</groupId> | ||
<artifactId>advent-of-craft2023</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>documents</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<properties> | ||
<approvaltests.version>22.3.2</approvaltests.version> | ||
<vavr.version>0.10.4</vavr.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.approvaltests</groupId> | ||
<artifactId>approvaltests</artifactId> | ||
<version>${approvaltests.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.vavr</groupId> | ||
<artifactId>vavr</artifactId> | ||
<version>${vavr.version}</version> | ||
</dependency> | ||
</dependencies> | ||
</project> |
44 changes: 44 additions & 0 deletions
44
solution/day15/src/main/java/document/DocumentTemplateType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package document; | ||
|
||
import io.vavr.collection.List; | ||
import io.vavr.collection.Map; | ||
|
||
public enum DocumentTemplateType { | ||
DEERPP("DEER", RecordType.INDIVIDUAL_PROSPECT), | ||
DEERPM("DEER", RecordType.LEGAL_PROSPECT), | ||
AUTP("AUTP", RecordType.INDIVIDUAL_PROSPECT), | ||
AUTM("AUTM", RecordType.LEGAL_PROSPECT), | ||
SPEC("SPEC", RecordType.ALL), | ||
GLPP("GLPP", RecordType.INDIVIDUAL_PROSPECT), | ||
GLPM("GLPM", RecordType.LEGAL_PROSPECT); | ||
|
||
private static final Map<String, DocumentTemplateType> mapping = | ||
List.of(DocumentTemplateType.values()) | ||
.toMap(v -> formatKey(v.getDocumentType(), v.getRecordType().name()), v -> v) | ||
.merge(List.of(RecordType.values()).toMap(v -> formatKey(SPEC.name(), v.name()), v -> SPEC)); | ||
|
||
private final String documentType; | ||
private final RecordType recordType; | ||
|
||
DocumentTemplateType(String documentType, RecordType recordType) { | ||
this.documentType = documentType; | ||
this.recordType = recordType; | ||
} | ||
|
||
private static String formatKey(String documentType, String recordType) { | ||
return documentType.toUpperCase() + "-" + recordType.toUpperCase(); | ||
} | ||
|
||
public static DocumentTemplateType fromDocumentTypeAndRecordType(String documentType, String recordType) { | ||
return mapping.get(formatKey(documentType, recordType)) | ||
.getOrElseThrow(() -> new IllegalArgumentException("Invalid Document template type or record type")); | ||
} | ||
|
||
private RecordType getRecordType() { | ||
return recordType; | ||
} | ||
|
||
private String getDocumentType() { | ||
return documentType; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package document; | ||
|
||
public enum RecordType { | ||
INDIVIDUAL_PROSPECT("IndividualPersonProspect"), | ||
LEGAL_PROSPECT("LegalEntityProspect"), | ||
ALL("All"); | ||
private final String value; | ||
|
||
RecordType(String value) { | ||
this.value = value; | ||
} | ||
|
||
public String getValue() { | ||
return value; | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
solution/day15/src/test/java/DocumentTests.combinationTests.approved.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[DEERPP, INDIVIDUAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[DEERPP, LEGAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[DEERPP, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[DEERPM, INDIVIDUAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[DEERPM, LEGAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[DEERPM, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[AUTP, INDIVIDUAL_PROSPECT] => AUTP | ||
[AUTP, LEGAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[AUTP, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[AUTM, INDIVIDUAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[AUTM, LEGAL_PROSPECT] => AUTM | ||
[AUTM, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[SPEC, INDIVIDUAL_PROSPECT] => SPEC | ||
[SPEC, LEGAL_PROSPECT] => SPEC | ||
[SPEC, ALL] => SPEC | ||
[GLPP, INDIVIDUAL_PROSPECT] => GLPP | ||
[GLPP, LEGAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[GLPP, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[GLPM, INDIVIDUAL_PROSPECT] => java.lang.IllegalArgumentException: Invalid Document template type or record type | ||
[GLPM, LEGAL_PROSPECT] => GLPM | ||
[GLPM, ALL] => java.lang.IllegalArgumentException: Invalid Document template type or record type |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import document.DocumentTemplateType; | ||
import document.RecordType; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.Arrays; | ||
|
||
import static org.approvaltests.combinations.CombinationApprovals.verifyAllCombinations; | ||
|
||
class DocumentTests { | ||
@Test | ||
void combinationTests() { | ||
verifyAllCombinations( | ||
DocumentTemplateType::fromDocumentTypeAndRecordType, | ||
Arrays.stream(DocumentTemplateType.values()).map(Enum::name).toArray(String[]::new), | ||
Arrays.stream(RecordType.values()).map(Enum::name).toArray(String[]::new) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters