Code :
```python
def main_v1() -> int:
result: dict = compute_stuff()
# Appel à une fonction builtin, avec un chemin hard-codé :
# difficile à tester
with open("/path/to/file", "w") as file:
json.dump(result, file)
return 0
```
Refacto :
```python
def write_to_json(content: dict, path: Path) -> None:
with open(path, "w") as file:
json.dump(content, file)
def main_v2(result_path: Path) -> int:
result: dict = compute_stuff()
write_to_json(result, path)
```
Test avec un mock :
```python
def test_main_with_mock():
with patch("write_to_json") as mock_write:
result_file = Path("/path/to/result.json")
main_v2(result_file)
mock_write.assert_called_with(result_file)
```
Test avec un fichier temporaire :
```python
# Automatically get a tmp directory from the test framework
def test_main_with_tmp_dir_from_test_framework(tmp_path: Path):
result_file = tmp_path / "result.json"
main_v2(result_file)
assert result_file.isfile()
```
-v-
## Exemple : base de données
Code :
```python
class Manager:
def __init__(self, host):
self._db = DatabaseConnection(host)
def save_daily_report(number: int) -> None:
self._db.add_daily_number(number)
print("sauvegarde ok!")
def retrieve_report() -> int:
return self._db.get_daily_number()
```
Test avec Mock :
```python
def test_manager_with_mock():
with patch("DatabaseConnection") as mock_db:
manager = Manager() # Given
manager.save_daily_report(10) # When
assert mock_db.add_daily_number.assert_called_with(10) # Then
```
Test avec Fake :
```python
class FakeDatabase:
def __init__(self):
self.number = 0
def add_daily_number(self, number: int) -> None:
self.number = number
def get_daily_number(self) -> int:
return self.number
def test_manager_with_fake():
with patch("DatabaseConnection", new=FakeDatabase) as fake_db:
manager = Manager() # Given
manager.save_daily_report(10) # When
assert fake_db.number == 10 # Then
```
-v-
## Exemple : base de données 2
Code :
```python
class Manager:
def __init__(self, host):
self._db = DatabaseConnection(host)
def save_daily_report(number: int) -> None:
self._db.add_daily_number(number)
print("sauvegarde ok!")
def retrieve_report() -> int:
return self._db.get_daily_number()
```
Test avec container :
```shell
docker run --port 8000 my-awesome-db-image:latest
```
```python
def test_manager_with_container():
manager = Manager("localhost:8000") # Given
manager.save_daily_report(10) # When
assert manager.retrieve_report() == 10 # Then
```
Test avec une vraie base de données, en read-only:
```python
def test_manager_with_container():
manager = Manager()
assert manager.retrieve_report() >= 0
```
-v-
## Techniques
Le minimum à maîtriser selon nous :
* la mise en place de son environnement de test, en local et en CI
* peut être très simple avec certains projet, moins pour d'autres
* le framework de test standard de son langage
* vous n'êtes pas seuls avec vos problèmes de tests 😇
* les techniques d'isolation des effets de bord : fakes, mocks, TestContainers...
* les techniques de lisibilité et de factorisation des tests
Notes:
* ce qu'on considère le minimum à maîtriser pour tester
* test harness
* TestContainers
* framework
* mocking et fakes versus simulateurs/émulateurs, oracles
* qu'est-ce je gagne et je perds si je mocke ? gain = vitesse d'exec + facile à mettre en oeuvre (autospec), perte = maintenabilité et réalisme
* DB in-memory
-v-
## Techniques avancées
* IA pour les tests
* couverture de test
* snapshot testing
* fuzzing
* tests d'architecture
* tests de performance et de charge
* Page Object Model (POM) pour les tests d'UI
* Métriques de perf et capacités DORA (équipe DevOps Research and Assessment de Google)
* ...
Juste le sommet de l'iceberg !
(le reste en annexe et dans les sources des slides)
Notes:
* pour aller + loin (et qui mérite chacun son 45 minutes ou +) pour développer culture et savoir-faire
-v-
## Bonus
- Les tests peuvent être intéressants, avec de belles solutions techniques à mettre en place
- Les tests nous font mieux maitriser le langage et ses libs
- Les tests nous font mieux comprendre le périmètre du code et ses interactions
- Les tests deviennent de plus en plus faciles à faire, on acquiert des automatismes
---
# Conclusion
* Le test est indispensable, l'automatisation (partielle) aussi
* Le test n'est pas simple, il faut l'apprendre et acquérir de l'expertise
* Il faut l'initier, construire du consensus quand on n'est que dev
* Le test fait partie intégrante de l'ingénierie logicielle
Notes:
* expertise indispensable, il faut s'y mettre, dans un environnement semi-hostile (vocab, équipe, rythme, outillage, ...) -> CI, run local. C'est une partie de l'ingénierie
---
# Nos recommandations
* Titus Winters, Tom Manschrek et Hyrum Wright - Software Engineering at Google ([online](https://abseil.io/resources/swe-book))
* Michael feathers - Working effectively with legacy code
* [BiteCode - Testing with Python (part 4): why and what to test?](https://www.bitecode.dev/p/testing-with-python-part-4-why-and)
* [Dwayne Richard Hipp - How SQLite Is Tested](https://www.sqlite.org/testing.html)
* [Adam Bender - SMURF: Beyond the Test Pyramid](https://testing.googleblog.com/2024/10/smurf-beyond-test-pyramid.html)
* [Miško Hevery - Writing Testable Code](https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html)
* [Jeremy Sorent - J'écris de tests sans pleurer maintenant](https://www.youtube.com/watch?v=2S9TxoTE8BA)
* [Gary Bernhardt - Boundaries](https://www.destroyallsoftware.com/talks/boundaries)
* ... et 27 autres en annexe !
Notes:
* Avis de Julien, pourquoi recommander :
* Jeremy Sorent - J'écris de tests sans pleurer maintenant
* un talk assez similaire à celui-ci dans l'intention, mais recentré sur le design du code
* Michael feathers - Working effectively with legacy code
* ça parle beaucoup beaucoup de test, et surtout de comment reprendre le contrôle une fois que ça a dérapé !
* Dwayne Richard Hipp - How SQLite Is Tested
* un exemple de comment n'avoir quasi aucun bug pour un des logiciels les plus utilisé au monde
* Adam Bender - SMURF: Beyond the Test Pyramid
* un exemple par Google de détricoter la pyramide des tests dans une vision complémentaire des tests selon leurs propriétés techniques
* Miško Hevery - Writing Testable Code
* un ensemble de bons conseils pour rendre son code testable, dont le premier point ("Mixing object graph construction with application logic") est trop méconnu
* BiteCode - Testing with Python (part 4): why and what to test?
* pas si spécifique à Python, toute la série d'articles vaut le détour, mais cet épisode s'attarde sur, sans le nommer ainsi, la stratégie de test
* Gary Bernhardt - Boundaries
* comment découper son application pour faciliter sa testabilité (notion de "context domain" du DDD)
* Titus Winters, Tom Manschrek et Hyrum Wright - Software Engineering at Google
* de très bonne qualité, et parle significativement de test
---
# Crédits photos
* [mème de source inconnue, sur yaplakal.com](https://s00.yaplakal.com/pics/pics_preview/4/4/7/10845744.jpg)
* [image de voie d'escalade, sur Wikimedia](https://commons.wikimedia.org/wiki/File:Top_Rope_Climbing_5.jpg)
* [mèmes de "test en prod", via Google Images](https://www.google.com/search?udm=2&q=test+en+prod)
---
# Remerciements
* Damien Roulier
* Eric Papazian
* Mathieu Mattringe
* Rachel Da Silva
* Francky Flamant
* Fanny Velsin
* Victor Lambret
---
# Questions