Je suis développeur web backend et ma tâche principale est de créer des WebApi. Les tests sont une seconde nature et j’utilise le TDD.

Les tests unitaires servent énormément à la conception des programmes. Malheureusement ils ne font pas tout. Ils faut souvent les compléter par des tests d’intégrations. C’est ici qu’intervient BDD.

Souvent techniques, ces tests sont dans la plupart des cas illisibles pour les non développeurs : essayez de faire lire votre code à votre Product Owner ou à l’utilisateur final :)

Pour permettre de comprendre les fonctionnalités d’un programme pour ces personnes, il est possible d’écrire les tests avec le langage Gherkin

Langage Gherkin

Le langage gherkin vous permet d’écrire facilement et de manière lisible vos tests. Qu’ils soient unitaires, d’intégrations, d’acceptances.

La forme classique de tests écrit en gherkin :

Feature: Calculator

Scenario: “+” should add to current total
Given the current total is “5”
When I enter “7”
Then the current total should be “12”

@wip
Scenario Outline: Should support
Given the current total is “”
When I enter “”
And I enter “”
Then the current total should be “”

Examples:
| currentTotal | inputNumber | inputOperator | newTotal |
| 5            | 7           | +             | 12       |
| 5            | 7           | -             | -2       |
| 3            | 4           | *             | 12       |
| 9            | 3           | /             | 3        |

Gherkin en .Net

L’outil le plus connu pour créer des tests en Gherkin en environnement .Net est Specflow. C’est lui qui nous servira pour la suite de cet article.

Cas d’usage : tester une webapi

Afin de mieux comprendre comment faire des tests lisibles et tester une webapi je vais vous présenter comment il est possible de tester de manière lisible une webapi. Celle-ci devra faire le lien avec l’api Marvel

Afin de ne pas consommer l’api Marvel à chaque exécution nous allons faire semblant d’appeler l’api depuis notre webapi tout en lui permettant d’appeler réellement l’api Marvel en production

L’architecture de la webapi

La webapi suit une architecture de type hexagonale. Nous avons trois projets :

  • Un projet de démarrage comprenant les contrôleurs.
  • Un projet pour l’orchestration de la webapi (ou partie métier dans d’autres cas).
  • Un projet pour appeler l’api Marvel.

Fonctionnement des tests

Voici le plan de test de la webapi :

  1. Création du gherkin.
  2. Création d’un serveur pour la webapi.
  3. Mock du webservice Marvel.
  4. Appel de la fonctionnalité.
  5. Test des résultat de l’appel.

Création du gherkin

Voici un exemple de tests en Gherkin lisible par tous. Il exprime une fonctionnalité : la recherche des personnages

Feature: Je veux pouvoir avoir des informations sur mes personnages Marvel préférés

Scenario: Je veux la liste de tous les super-héros
    Given Passioné(e) de super-héros
    When Je demande la liste des super-héros
    Then Je trouve la liste souhaitée

Scenario: Je veux le détail d'un super-héros
    Given Passioné(e) de super-héros
    And Je veux en savoir plus sur <Héros>
    When Je demande le détail du super-héros
    Then Je trouve la fiche complète du personnage

    Examples:
    | Héros                            |
    | Doctor Strange (Stephen Strange) |
    | Star-Lord (Peter Quill)          |
    | Spider-Man (Peter Parker)        |

Création d’un serveur pour la webapi

Pour tester une application vous pouvez utiliser WebApplicationFactory. Avec cette classe il est possible de surcharger le fonctionnement du projet. Il est par exemple possible de remplacer le contexte d’une base de données ou la classe d’implémentation pour une interface (même celle du framework).

Nous pouvons créer la classe de base qui nous servira à appeler notre API :

public class CustomServer : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;

    public HttpClient Client { get; }

    public CustomServer(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
        Client = _factory.CreateClient();
    }
}

Celle-ci sera forunie dans les tests par l’utilisation de IClassFixture<CustomServer> dans la classe associé à notre gherkin et récupérée dans le constructeur. Pour indique à Specflow que la classe est associée à un fichier gherkin nous pouvons utiliser [Binding]

[Binding]
public class FetchMarvelCharactersSteps : IClassFixture<CustomServer>
{
    private readonly CustomServer _server;

    public FetchMarvelCharactersSteps(CustomServer server) => _server = server;

    [Given("Passioné(e) de super-héros")]
    public void GivenPassionneeDeSuperHeros()
    {
        // Do some stuff
    }

    // Suite des tests ...
}

L’appel de notre webapi se fait avec le serveur sur la route de notre webapi :

[Binding]
public class FetchMarvelCharactersSteps : IClassFixture<CustomServer>
{
...
    [When("Je demande la liste des super-héros")]
    public void ListeDesSuperHeros()
    {
        var response = await _server.Client.GetAsync(new Uri("/api/characters", UriKind.Relative));

        ...
    }
...

Mock du webservice Marvel

L’appel de l’api REST de Marvel se fait avec l’utilisation d’un HttpClient:

var charactersResponse = await _httpClient.GetAsync(new Uri($"/v1/public/characters?apikey={apiKey}", UriKind.Relative))

C’est cet appel que nous allons mocker. Le fait de mocker cet appel nous permet d’avoir un test complet de la webapi de son appel à l’appel de l’api Marvel.

Pour cela rien de bien compliqué quand on l’a fait une première fois:

Dans la classe CustomServer nous allons utiliser le package RichardSzalay.MockHttp.

using Moq;
using RichardSzalay.MockHttp;
...
    private Mock<IHttpClientFactory> ClientFactory { get; } = new Mock<IHttpClientFactory>();
    private MockHttpMessageHandler MessageHandler { get; } = new MockHttpMessageHandler();

    public CustomServer(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;

        ClientFactory
            .Setup(m => m.CreateClient(It.IsAny<string>()))
            .Returns(CreateWebServiceClient);

        Client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.RemoveAll<IHttpClientFactory>();
                services.AddScoped(_ => ClientFactory.Object);
            });
        }).CreateClient();
    }

    public void SetupRouteResponse(
        HttpMethod method,
        string route,
        HttpResponseMessage response) =>
        MessageHandler
            .When(method, route)
            .Respond(() => Task.FromResult(response));

    public void SetupRouteResponse(
        HttpMethod method,
        string route,
        Func<HttpRequestMessage, Task<HttpResponseMessage>> requestHandler) =>
        MessageHandler
            .When(method, route)
            .Respond(requestHandler);

    public void SetupRouteResponse(
        HttpMethod method,
        string route,
        HttpResponseMessage response,
        params KeyValuePair<string, string>[] query) =>
        MessageHandler
            .When(method, route)
            .WithQueryString(query)
            .Respond(() => Task.FromResult(response));

    public void SetupRouteResponse(
        HttpMethod method,
        string route,
        Func<HttpRequestMessage, Task<HttpResponseMessage>> requestHandler,
        params KeyValuePair<string, string>[] query) =>
        MessageHandler
            .When(method, route)
            .WithQueryString(query)
            .Respond(requestHandler);

    private HttpClient CreateWebServiceClient()
    {
        var client = MessageHandler.ToHttpClient();
        client.BaseAddress = new Uri("https://marvel.mocked.com");
        return client;
    }

    ...

Nous remplaçons le IHttpClientFactory par le mock crée dans la configuration des services. Le fait de passer une lambda dans le AddScoped permet de configurer les routes à postériori suivant le besoin.

Ci-dessus vous pouvez voir 4 méthodes pour mocker la réponse du webservice Marvel. Il y a deux versions sans queryString et deux avec. Pour rappel le query string c’est tout ce qui est après le ? dans : https://url/api/actions?option1=value1&option2=value2

Si je veux configurer uniquement la réponse j’utilise les méthodes qui prennent un HttpResponseMessage et si je veux tester la requête envoyée je lui passe une méthode qui fera le contrôle et renverra la réponse configurée.

Exemple de configuration :

[Binding]
public class FetchMarvelCharactersSteps : IClassFixture<CustomServer>
{
    private readonly CustomServer _server;

    ...
    _server.SetupRouteResponse(
        HttpMethod.Get,
        $"v1/public/characters/{characterId}",
        new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("{ \"code\": \"1234\", ...}", Encoding.UTF8, "application/json")
        }
        new KeyValuePair<string, string>("apiKey", fakeApiKey)
    )
    ...
}

Il ne vous reste plus qu’à appeler votre WebApi et vous pouvez tester toute la logique du début à la fin.

Conclusion

L’association de plusieurs types de tests vous aide à créer le bon programme qui fait ce qu’on lui demande. Il ne faut pas hésiter à les mixer. Il en existe d’autre sorte (tests de propriétés, tests de bout en bout).

Pour le gherkin je vous conseille fortement soit de déléguer l’écriture à une personne qui parlera de manière fonctionnelle ou de vous faire accompagner par cette personne.

Le gherkin est indépendant du langage et pourra être réutilisé pour une autre technologie : java, js, python, …

De plus une documentation fonctionnelle pourra en être faite, avec Pickles par exemple, et présenté à tout intervenant et permettre de comprendre ce que fait le programme.

Liens