Skip to content

Commit

Permalink
Merge branch 'steps/06-properties' into steps/07-architecture-tests
Browse files Browse the repository at this point in the history
# Conflicts:
#	Bouchonnois/Repository/IPartieDeChasseRepository.cs
#	Bouchonnois/Service/Terrain.cs
#	c#/Bouchonnois/Domain/IPartieDeChasseRepository.cs
#	c#/Bouchonnois/Domain/Terrain.cs
#	c#/Bouchonnois/Repository/IPartieDeChasseRepository.cs
#	c#/Bouchonnois/Service/Terrain.cs
  • Loading branch information
ythirion committed Jun 14, 2024
2 parents b82fcef + d0e2089 commit 8aa8db4
Show file tree
Hide file tree
Showing 128 changed files with 4,251 additions and 18 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/build.yml → .github/workflows/c#.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: SonarCloud
name: C# CI
on:
push:
branches:
Expand All @@ -20,11 +20,11 @@ jobs:
name: Build and analyze
runs-on: windows-latest
steps:
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 11
distribution: 'zulu' # Alternative distribution options are available.
java-version: 17
distribution: 'zulu'
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
Expand Down Expand Up @@ -55,6 +55,6 @@ jobs:
run: |
dotnet tool install --global dotnet-coverage
.\.sonar\scanner\dotnet-sonarscanner begin /k:"ythirion_refactoring-du-bouchonnois" /o:"ythirion" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml
dotnet build
dotnet-coverage collect "dotnet test" -f xml -o "coverage.xml"
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
dotnet build c#/
dotnet-coverage collect "dotnet test c#/" -f xml -o "coverage.xml"
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
34 changes: 34 additions & 0 deletions .github/workflows/java.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Java CI
on:
push:
branches:
- main
- steps/01-gather-metrics
- steps/02-treat-warnings-as-errors
- steps/03-kill-mutants
- steps/04-improve-tests-readability
- steps/05-approve-everything
- steps/06-properties
- steps/07-architecture-tests
- steps/08-use-cases
- steps/09-tell-dont-ask
- steps/10-commands
- steps/11-avoid-exceptions
- steps/12-event-sourcing
jobs:
build:
name: Build
strategy:
matrix:
platform: [ubuntu-latest]
runs-on: ${{matrix.platform}}
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'zulu'
- name: Build and Test
working-directory: ./java
run: mvn test
36 changes: 36 additions & 0 deletions .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Kotlin CI
on:
push:
branches:
- main
- steps/01-gather-metrics
- steps/02-treat-warnings-as-errors
- steps/03-kill-mutants
- steps/04-improve-tests-readability
- steps/05-approve-everything
- steps/06-properties
- steps/07-architecture-tests
- steps/08-use-cases
- steps/09-tell-dont-ask
- steps/10-commands
- steps/11-avoid-exceptions
- steps/12-event-sourcing
jobs:
build:
name: Build
strategy:
matrix:
platform: [ubuntu-latest]
runs-on: ${{matrix.platform}}
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build
build-root-directory: ./kotlin
63 changes: 62 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -354,5 +354,66 @@ target/
# Mac
.DS_Store

target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

.gradle
**/build/
!src/**/build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties

# Cache of project
.gradletasknamecache

# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath

# Verify
*.received.txt
*.received.txt
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,19 @@ Version PDF disponible [ici](example-mapping/example-mapping.pdf)

## Facilitation
### Pré-requis
Le code est disponible en `.NET 7`.
Le code est disponible en `C#` (`.NET 7`), `java`(21), `kotlin`.

Voici la liste des packages utilisés :
- `xUnit`
- `FluentAssertions`
- `Verify.xUnit`
- `FSCheck`
- `TngTech.ArchUnitNET.xUnit`
- `LanguageExt.Core`
- `FluentAssertions.LanguageExt`
Voici la liste des librairies utilisés / recommandées :

| .NET | Java | Kotlin |
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| [`xUnit`](https://xunit.net/) | [`junit`](https://junit.org/junit5/) | [`Kotest`](https://kotest.io/) |
| [`FluentAssertions`](https://fluentassertions.com/) | [`assertJ`](https://joel-costigliola.github.io/assertj/) | Native `kotest` asertions |
| [`Verify.xUnit`](https://github.com/VerifyTests/Verify) | [`approvalTests`](https://github.com/approvals/approvaltests.java) | [`approvalTests`](https://github.com/approvals/approvaltests.java) |
| [`FSCheck`](https://fscheck.github.io/FsCheck/) | [`vavr-test`](https://github.com/vavr-io/vavr-test) | [`kotest-property`](https://kotest.io/docs/proptest/property-based-testing.html) |
| [`TngTech.ArchUnitNET.xUnit`](https://archunitnet.readthedocs.io/en/latest/) | [`archunit`](https://www.archunit.org/) | [`archunit`](https://www.archunit.org/) |
| [`LanguageExt.Core`](https://github.com/louthy/language-ext) | [`vavr`](https://www.vavr.io/) | [`arrow-kt`](https://arrow-kt.io/) |
| [`FluentAssertions.LanguageExt`](https://www.nuget.org/packages/FluentAssertions.LanguageExt) | [`assertj-vavr`](https://github.com/assertj/assertj-vavr) | [`kotest-extensions-arrow`](https://github.com/kotest/kotest-extensions-arrow#kotest-extensions-arrow) |

Afin d'améliorer le code on te propose de suivre les étapes ci-dessous :

Expand All @@ -56,7 +59,7 @@ Afin d'améliorer le code on te propose de suivre les étapes ci-dessous :
- [12. Event Sourcing](facilitation/12.event-sourcing.md)

Pour chaque étape :
- une proposition de solution "étape par étape" est proposée
- une proposition de solution "étape par étape" est proposée (en `C#` uniquement)
- il existe 1 branche / étape

![Branches](img/branches.webp)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
186 changes: 186 additions & 0 deletions java/docs/03.kill-mutants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Let's kill some mutants

Prendre quelques instants pour découvrir la
page [`Mutation Testing`](https://xtrem-tdd.netlify.app/flavours/mutation-testing/).

Durant cette étape:

- Lancer [`pitest`](https://pitest.org/)
- Analyser les mutants survivants
- `Tuer` autant de mutants que possible (atteindre un score de mutation d'au moins 90%)

![Step 3 - Let's kill some mutants](../../img/step3.webp)

## Point de départ

![Rapport de départ](img/pit-report.png)

Malgré notre fort `coverage` le score de mutation est très faible: `11%` sur le `domain`...

## `Domain`
De nombreux morceaux de code sont marqués comme `NO_COVERAGE` dûs à l'usage massif de `lombok`...

![@Data sur Chasseur](img/chasseur-data.png)

- On supprime `@Data` qui amène trop de fonctionnalité par rapport à notre besoin

```java
@Getter
@Setter
public class Chasseur {
private String nom;
private int ballesRestantes;
private int nbGalinettes;
}
```

- Cela va réduire drastiquement le nombre de mutations possibles

![Fix sur Chasseur](img/chasseur-fixed.png)

> On fait de même sur le reste de notre `domain`
## `Service`
On s'intéresse désormais au package `service`

![Package service](img/pit-report-service-level.png)

- On commence par travailler sur les annotation sur `Terrain`
- On a besoin de rajouter le constructeur contenant le `nom` en argument

```java
@Getter
@Setter
public class Terrain {
private final String nom;
private int nbGalinettes;

public Terrain(String nom) {
this.nom = nom;
}
}
```

- Maintenant, on peut s'attaquer au `PartieDeChasseService`

![setId](img/mutant-setId.png)

- On va tuer ce mutant en renforçant notre test
- vérifier que le call au `setId` se fait bien

```java
@Test
void avec_plusieurs_chasseurs() throws ImpossibleDeDémarrerUnePartieSansGalinettes, ImpossibleDeDémarrerUnePartieAvecUnChasseurSansBalle, ImpossibleDeDémarrerUnePartieSansChasseur {
var repository = new PartieDeChasseRepositoryForTests();
var service = new PartieDeChasseService(repository, LocalDateTime::now);
var chasseurs = new ArrayList<Tuple2<String, Integer>>() {{
add(new Tuple2<>("Dédé", 20));
add(new Tuple2<>("Bernard", 8));
add(new Tuple2<>("Robert", 12));
}};
var terrainDeChasse = new Tuple2<>("Pitibon sur Sauldre", 3);

var id = service.démarrer(
terrainDeChasse,
chasseurs
);

var savedPartieDeChasse = repository.getSavedPartieDeChasse();
// On vérifier que l'id généré n'est pas null
assertThat(savedPartieDeChasse.getId()).isNotNull().isEqualTo(id);
assertThat(savedPartieDeChasse.getStatus()).isEqualTo(PartieStatus.EN_COURS);
assertThat(savedPartieDeChasse.getTerrain().getNom()).isEqualTo("Pitibon sur Sauldre");
assertThat(savedPartieDeChasse.getTerrain().getNbGalinettes()).isEqualTo(3);
assertThat(savedPartieDeChasse.getChasseurs()).hasSize(3);
assertThat(savedPartieDeChasse.getChasseurs().get(0).getNom()).isEqualTo("Dédé");
assertThat(savedPartieDeChasse.getChasseurs().get(0).getBallesRestantes()).isEqualTo(20);
assertThat(savedPartieDeChasse.getChasseurs().get(0).getNbGalinettes()).isZero();
assertThat(savedPartieDeChasse.getChasseurs().get(1).getNom()).isEqualTo("Bernard");
assertThat(savedPartieDeChasse.getChasseurs().get(1).getBallesRestantes()).isEqualTo(8);
assertThat(savedPartieDeChasse.getChasseurs().get(1).getNbGalinettes()).isZero();
assertThat(savedPartieDeChasse.getChasseurs().get(2).getNom()).isEqualTo("Robert");
assertThat(savedPartieDeChasse.getChasseurs().get(2).getBallesRestantes()).isEqualTo(12);
assertThat(savedPartieDeChasse.getChasseurs().get(2).getNbGalinettes()).isZero();
}
```

- Si on jette un oeil aux autres mutants, on se rend compte que `pitest` arrive à retirer l'appel au `save` du repository sans que les tests le détectent

![Autres mutants](img/partiedechasseservice-mutations.png)

- On change les tests pour faire cette vérification

```java
@Test
void echoue_avec_un_chasseur_nayant_plus_de_balles() {
var id = UUID.randomUUID();
var repository = new PartieDeChasseRepositoryForTests();

var partieDeChasse = new PartieDeChasse() {
{
setId(id);
setChasseurs(new ArrayList<>() {{
add(new Chasseur() {{
setNom("Dédé");
setBallesRestantes(20);
}});
add(new Chasseur() {{
setNom("Bernard");
setBallesRestantes(0);
}});
add(new Chasseur() {{
setNom("Robert");
setBallesRestantes(12);
}});
}});

setTerrain(new Terrain("Pitibon sur Sauldre") {{
setNbGalinettes(3);
}});
setStatus(PartieStatus.EN_COURS);
setEvents(new ArrayList<>());
}
};
repository.add(partieDeChasse);
var service = new PartieDeChasseService(repository, LocalDateTime::now);

assertThatThrownBy(() -> service.tirerSurUneGalinette(id, "Bernard"))
.isInstanceOf(TasPlusDeBallesMonVieuxChasseALaMain.class);

assertPartieDeChasseHasBeenSaved(repository, partieDeChasse);
}
```

### Amélioration du coverage
- Via ce rapport, on se rend compte qu'il y a une branche qui n'est pas couverte

![Test manquant](img/test-manquant.png)

- Ajoutons le test manquant

```java
@Test
void echoue_car_partie_nexiste_pas() {
var id = UUID.randomUUID();
var repository = new PartieDeChasseRepositoryForTests();
var service = new PartieDeChasseService(repository, LocalDateTime::now);

assertThatThrownBy(() -> service.consulterStatus(id))
.isInstanceOf(LaPartieDeChasseNexistePas.class);
assertThat(repository.getSavedPartieDeChasse()).isNull();
}
```

> Bravo, tous les mutants sont morts
![Rapport final](img/rapport-final.png)

## Reflect

Pour créer de bons tests, il est important de `toujours se concentrer sur l'écriture de bonnes assertions` et encore
mieux développer en utilisant T.D.D.

Lorsqu'on écrit des tests (a priori ou posteriori), il est important d'avoir en tête certains principes tels que
les [Test Desiderata](https://kentbeck.github.io/TestDesiderata/).

![Let's kill some mutants](../../facilitation/steps/img/03.kill-mutants/kill-mutants.webp)
Binary file added java/docs/img/add-ctor-terrain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/chasseur-data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/chasseur-fixed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/mutant-setId.png
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.
Binary file added java/docs/img/pit-report-service-level.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/pit-report.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/rapport-final.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added java/docs/img/test-manquant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8aa8db4

Please sign in to comment.