Simple Enough Blog logo
  • Home 
  • Projets 
  • Tags 

  •  Langage
    • English
    • Français
  1.   Blogs
  1. Accueil
  2. Blogs
  3. Interfaces, Fonctions et Modules en Go : Structurer son code pour le TDD sans le complexifier

Interfaces, Fonctions et Modules en Go : Structurer son code pour le TDD sans le complexifier

Posté le 15 décembre 2025 • 5 min de lecture • 927 mots
Golang   Tdd   Architecture   Tests   Modularité   Devops   Thibault  
Golang   Tdd   Architecture   Tests   Modularité   Devops   Thibault  
Partager via
Simple Enough Blog
Lien copié dans le presse-papier

Comment utiliser les interfaces, les fonctions et les modules Go dans une approche Test-Driven Development ? Comment mocker, où mettre les interfaces? Comment isoler un package à fonctions ?Comment éviter la complexité inutile ?

Sur cette page
I. Introduction   II. Le modèle Go : simple, modulaire, mais différent   III. Modules Go et TDD : où placer les interfaces ?   Où mettre l’interface ?   Exemple d’interface définie dans bar   IV. Comment mocker un package qui expose uniquement des fonctions ?   1. Option A — Injection de fonction (simple et idiomatique)   2. Option B — Introduire un struct dans foo (pour multi-méthodes)   V. Interfaces vs Function Injection : tableau comparatif   VI. Détecter qu’une implémentation ne satisfait plus une interface   VII. Workflow TDD idiomatique en Go   VIII. Cas réels où l’injection de fonction est la meilleure solution   IX. Impact des outils modernes (IA, Cursor, etc.)   Ce qui change :   X. Conclusion   🔗 Useful Links   Documentation officielle Go   Tests & TDD en Go  
Interfaces, Fonctions et Modules en Go : Structurer son code pour le TDD sans le complexifier
Photo par Thibaut Deheurles

I. Introduction  

Go est un langage minimaliste, mais cette simplicité peut donner l’impression qu’il “manque quelque chose” lorsqu’on aborde des architectures plus poussées, du Test-Driven Development ou la nécessité de mocker certaines dépendances. Très vite, les développeurs venant de Java, C# ou Python se posent les mêmes questions :

  • Dois-je transformer toutes mes fonctions en structs + interfaces pour tester ?
  • Comment organiser mes packages pour qu’ils soient modulaires et testables ?
  • Où placer les interfaces ? Chez le fournisseur ou chez le consommateur ?
  • Comment isoler un package qui expose uniquement des fonctions ?
  • Comment détecter automatiquement un changement de signature ?

II. Le modèle Go : simple, modulaire, mais différent  

Contrairement à Java ou C#, Go ne repose pas sur l’héritage, les classes ou les gros frameworks d’injection de dépendances.

En Go :

  • un package est l’unité d’encapsulation,
  • une fonction publique suffit souvent,
  • une interface décrit un comportement minimal,
  • l’interface est généralement définie dans le consommateur,
  • les tests guident l’introduction d’abstractions.

La philosophie Go se résume en une phrase :

“Accept interfaces, return structs.”

C’est un langage où l’on introduit une abstraction uniquement lorsqu’elle devient utile et pas avant cela.


III. Modules Go et TDD : où placer les interfaces ?  

Imaginons deux packages :

foo/
foo.go

et un autre

bar/
bar.go

bar consomme foo.

Où mettre l’interface ?  

Dans le package consommateur (bar), pas dans foo.

Pourquoi ?

  • Le consommateur sait précisément ce dont il a besoin.
  • Le fournisseur n’a pas à imposer un contrat rigide.
  • On garde une interface minimaliste, plus facile à fake/mocker.
  • Le couplage est réduit.
  • Les tests deviennent plus simples.

Exemple d’interface définie dans bar  

// package bar

type Fooer interface {
    Do(ctx context.Context, input string) (string, error)
}

type Service struct {
    foo Fooer
}

func NewService(foo Fooer) *Service {
    return &Service{foo: foo}
}

Production :

f := foo.NewClient(...)
svc := bar.NewService(f)

Test :

type fakeFoo struct{}

func (f *fakeFoo) Do(ctx context.Context, in string) (string, error) {
    return "OK", nil
}

svc := bar.NewService(&fakeFoo{})

IV. Comment mocker un package qui expose uniquement des fonctions ?  

Supposons :

// package foo
func Compute(ctx context.Context, x int) (int, error) {
    ...
}

Tu veux isoler Compute dans bar.

Tu as deux modèles idiomatiques.

1. Option A — Injection de fonction (simple et idiomatique)  

// package bar

type ComputeFunc func(ctx context.Context, x int) (int, error)

type Service struct {
    compute ComputeFunc
}

func NewService() *Service {
    return &Service{
        compute: foo.Compute,
    }
}

func NewServiceWithCompute(f ComputeFunc) *Service {
    return &Service{
        compute: f,
    }
}

Test :

svc := bar.NewServiceWithCompute(func(ctx context.Context, x int) (int, error) {
    return 42, nil
})
  • Idéal pour isoler une seule fonction
  • Pas besoin d’interface
  • Très léger et lisible

2. Option B — Introduire un struct dans foo (pour multi-méthodes)  

// package foo
type Computer struct{}

func NewComputer() *Computer { return &Computer{} }

func (c *Computer) Compute(ctx context.Context, x int) (int, error) {
return Compute(ctx, x)
}

Et dans bar :

type Computer interface {
    Compute(ctx context.Context, x int) (int, error)
}
  • Positif :Bon pour des contrats complexes
  • Negatif: Lourd pour une simple fonction

V. Interfaces vs Function Injection : tableau comparatif  

CritèreInterfaceFunction Injection
Quand l’utiliserComportement complexeDépendance simple
Nombre de méthodesPlusieurs1
Facilité de testTrès bonneExcellente
BoilerplateMoyenTrès faible
CouplageFaibleLégèrement plus fort
RefactoringTrès sûrPeut casser silencieusement
Style GoTrès idiomatiqueParfaitement idiomatique

Conclusion :

  • Interfaces → pour les composants riches
  • Functions → pour les dépendances ponctuelles

VI. Détecter qu’une implémentation ne satisfait plus une interface  

Si bar définit une interface Fooer, tu peux garantir la compatibilité via :

var _ bar.Fooer = (*foo.Client)(nil)

Si une méthode manque ou change → la compilation échoue.

Ce pattern est standard dans tous les gros projets Go.


VII. Workflow TDD idiomatique en Go  

Voici un workflow simple, réaliste et efficace :

  1. Commence avec des fonctions simples
    Pas besoin de premature abstraction.

  2. Observe via les tests quand l’abstraction devient nécessaire

  3. Définis l’interface dans le consommateur

  4. Injecte les dépendances (struct, interface ou fonction)

  5. Utilise un fake minimal pour les tests

  6. Vérifie la compatibilité avec :

   var _ Interface = (*Impl)(nil)

Refactorise quand les tests deviennent difficiles.

Go encourage un code clair, simple, sans sur-ingénierie.


VIII. Cas réels où l’injection de fonction est la meilleure solution  

Très souvent, les équipes Go isolent :

  • time.Now → now func() time.Time
  • rand.Int → randFunc func() int
  • http.Client.Do → Doer func(*http.Request) (*http.Response, error)
  • os.ReadFile → readFile func(string) ([]byte, error)

Pourquoi ?

Parce que c’est simple, flexible, testable, rapide — et parfaitement Go.


IX. Impact des outils modernes (IA, Cursor, etc.)  

Tu te demandais si les frameworks de mocks deviennent inutiles :

  • Oui → les fakes générés automatiquement par IA réduisent le besoin de gros frameworks.
  • Non → l’architecture reste essentielle.

Ce qui change :  

  • moins de mocks complexes
  • plus d’interfaces minimalistes
  • plus de function injection
  • plus de refactoring guidé par IA

X. Conclusion  

Go encourage la simplicité, et cela se reflète dans la manière de structurer le code pour le TDD :

  • Les fonctions publiques sont totalement légitimes.
  • Les interfaces doivent vivre dans les consommateurs, pas dans les fournisseurs.
  • L’injection de fonction est un outil puissant, trop sous-estimé.
  • Le TDD fonctionne très bien en Go — à condition d’éviter les abstractions inutiles.
  • La compilation Go permet de détecter automatiquement les incompatibilités de signature.

En appliquant ces principes, ton code Go restera :

  • testable
  • propre
  • extensible
  • idiomatique
  • lisible par n’importe quel développeur Go expérimenté.

🔗 Useful Links  

Documentation officielle Go  

  • Guide central
  • Go playground

Tests & TDD en Go  

  • Testing package
 Utiliser les constantes en TDD avec Go
Consumer-Reported Dependency Health 
  • I. Introduction  
  • II. Le modèle Go : simple, modulaire, mais différent  
  • III. Modules Go et TDD : où placer les interfaces ?  
  • Où mettre l’interface ?  
  • IV. Comment mocker un package qui expose uniquement des fonctions ?  
  • V. Interfaces vs Function Injection : tableau comparatif  
  • VI. Détecter qu’une implémentation ne satisfait plus une interface  
  • VII. Workflow TDD idiomatique en Go  
  • VIII. Cas réels où l’injection de fonction est la meilleure solution  
  • IX. Impact des outils modernes (IA, Cursor, etc.)  
  • X. Conclusion  
  • 🔗 Useful Links  
Suivez-nous

Nous travaillons avec vous !

   
Copyright © 2026 Simple Enough Blog Tous droits réservés. | Propulsé par Hinode.
Simple Enough Blog
Code copié dans le presse-papier