• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 22/11/2019

Générez une facture

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Il est temps de finaliser la génération d'une facture. Dans ce chapitre, nous allons générer une facture dans un fichier.

La méthode generate()

Nous allons ajouter une méthode  generate  à  Bill . Cette méthode va se charger de générer la facture dans un fichier.

Nous allons faire cela proprement et utiliser de nouveau une interface dédiée, nommée  Writer. Vous allez comprendre la flexibilité qu'apporte ce type de conception.

Nous avons donc :

package com.cursan.homeshop;

public interface Writer {
    /**
     * Start writing process
     */
    public void start();

    /**
     * Write on line
     * @param line
     */
    public void writeLine(String line);

    /**
     * Stop writing process
     */
    public void stop();
}

 Commençons par coder  generate (et les tests associés) avant même de coder notre  Writer.

Voici le résultat attendu :

HomeShop compagnie
1 Place Charles de Gaulle, 75008 Paris

Facture à l'attention de : 
Juste Leblanc
19 rue Germain Pilon, Paris

Mode de livraison : livraison point relais 2.99€

Produits : 
-----------------------------------------------------
TV Samsung UE49MU6292 - 599.0 - 1 unité(s)
Smart TV LED incurvée 49"

BEKO TSE 1042 F - 189.0 - 1 unité(s)
Réfrigérateur BEKO 130L - Classe A+ - blanc

Philips HD7866/61 - 79.99 - 1 unité(s)
Philips SENSEO Quadrante, Noir - 1 ou 2 tasses

Livraison : 2.99
-----------------------------------------------------
Total : 870.98

Voici l'UML :

UML de Bill avec generate
UML de Bill avec generate

Un Mock

Deux nouvelles méthodes sont présentes :  generate  et  getTotal . Nous allons commencer par les tester.

Dans le premier cours consacré à Java, j'avais utilisé une méthode un peu particulière pour tester l'affichage. Ce n'était pas la bonne façon de faire ! Il faut procéder comme nous allons le faire ici.

Pour tester  generate , nous allons créer un faux  Writer  : un  Writer  qui n'écrit rien du tout, mais qui stocke ce qu'il doit écrire. Cela sera beaucoup plus simple pour tester. On appelle cela un Mock.

On va ajouter cette nouvelle classe  directement dans la classe de test associée à  Bill  :  BillTest.

Hein o_O ? Mais tu nous as dit qu'une classe correspondait à un fichier !

En général, c'est vrai. Il y a 2 exceptions :

  • Les inner classes : ce sont des classes déclarées dans d'autres. C'est rare d'en trouver, mais parfois il y en a besoin. Vous allez certainement en rencontrer un jour ou l'autre.

  • les classes anonymes : c'est ce que nous allons utiliser. Cela consiste à faire un héritage dans le code, sans créer de nouvelle classe. On change le comportement de base de la classe sans en créer une nouvelle.

Voilà le résultat :

package com.cursan.homeshop;

import static org.junit.Assert.*;

public class BillTest {
    private String output;
    private Writer writerMock = new Writer() {
        @Override
        public void start() {
            output = "";
        }

        @Override
        public void writeLine(String line) {
            output += line + "%n";
        }

        @Override
        public void stop() {
        }
    };
}

On va pouvoir utiliser  output  pour facilement tester la génération.

Comment se fait-il que  writerMock  puisse accéder à  output  alors que ce n'est pas la même classe ?

C'est une question de scope.  writerMock  est un membre de  BillTest , il peut avoir accès aux autres membres. C'est une particularité assez avancée de Java. Si vous ne comprenez pas bien, ne vous y attardez pas.

Essayez de rédiger par vous-même des tests intelligents pour  generate  et  getTotal.

Voici ceux que je vous propose :

package com.cursan.homeshop;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class BillTest {
    private String output;
    private Writer writerMock = new Writer() {
        @Override
        public void start() {
            output = "";
        }

        @Override
        public void writeLine(String line) {
            output += line + "\n";
        }

        @Override
        public void stop() {
        }
    };
    private Product cafe = new Product("Philips HD7866/61", "Philips SENSEO Quadrante, Noir - 1 ou 2 tasses", 79.99);
    private Product tv = new Television("TV Samsung UE49MU6292", "Smart TV LED incurvée 49\"", 599, 49, "LED");
    private Fridge fridge = new Fridge("BEKO TSE 1042 F", "Réfrigérateur BEKO 130L - Classe A+ - blanc", 189, 130, false);
    private Customer customer = new Customer("Juste Leblanc", "19 rue Germain Pilon, Paris");
    private Delivery lowCostRelayDelivery = new RelayDelivery(27);

    @Test
    public void Given_2productsAndDelivery_When_generatingBill_Then_getGoodLineNumber() {
        Bill bill = new Bill(customer, lowCostRelayDelivery);
        bill.addProduct(cafe, 1);
        bill.addProduct(tv, 1);
        bill.generate(writerMock);
        int lineNumber = output.split("\n").length;
        assertEquals(20, lineNumber);
    }

    @Test
    public void Given_3productsAndDelivery_When_generatingBill_Then_getGoodTotal() {
        Bill bill = new Bill(customer, lowCostRelayDelivery);
        bill.addProduct(cafe, 1);
        bill.addProduct(tv, 1);
        bill.addProduct(fridge, 1);
        assertEquals(870.98, bill.getTotal(), 0.01);
    }
}

Le code de generate et getTotal

Et voilà le code attendu :

/**
 * Generate an output for the current Bill
 * @param writer object in charge of writing
 */
public void generate(Writer writer) {
    writer.start();
    writer.writeLine("HomeShop compagnie");
    writer.writeLine("1 Place Charles de Gaulle, 75008 Paris");
    writer.writeLine("");
    writer.writeLine("Facture à l'attention de : ");
    writer.writeLine(customer.getFullname());
    writer.writeLine(customer.getAddress());
    writer.writeLine("");
    writer.writeLine("Mode de livraison : " + delivery.getInfo());
    writer.writeLine("");
    writer.writeLine("Produits : ");
    writer.writeLine("-----------------------------------------------------");
    for (Map.Entry<Product, Integer> entry : products.entrySet()) {
        Product product = entry.getKey();
        Integer quantity = entry.getValue();
        writer.writeLine(product.getName() + " - " + product.getPrice() + " - " + quantity + " unité(s)");
        writer.writeLine(product.getDescription());
        writer.writeLine("");
    }
    writer.writeLine("Livraison : " + delivery.getPrice());
    writer.writeLine("-----------------------------------------------------");
    writer.writeLine("Total : " + this.getTotal());
    writer.stop();
}

/**
 * get the total price for the current bill, including products and delivery cost
 * @return total price
*/
public double getTotal() {
double total = delivery.getPrice();
    for (Map.Entry<Product, Integer> entry : products.entrySet()) {
        Product product = entry.getKey();
        Integer quantity = entry.getValue();
        total += product.getPrice() * quantity;
    }
    return total;
}

Est-ce que nous avons fini ?

Eh bien non, il nous reste à créer le  FileWriter  !

Voici l'implémentation que je vous propose :

package com.cursan.homeshop;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileWriter implements Writer {
    private String filename;
    private Path path;
    private String content;

    public FileWriter(String filename) {
        this.filename = filename;
    }

    @Override
    public void start() {
        path = Paths.get(filename);
        content = "";
    }

    @Override
    public void writeLine(String line) {
        content += line + "%n";
    }

    @Override
    public void stop() {
        try {
            Files.write(path, String.format(content).getBytes());
        } catch (IOException e) {
            System.err.println("Impossible de rédiger la facture");
        }
    }
}
package com.cursan.homeshop;

public class Main {

    public static void main(String[] args) {
        Product cafe = new Product("Philips HD7866/61", "Philips SENSEO Quadrante, Noir - 1 ou 2 tasses", 79.99);
        Product tv = new Television("TV Samsung UE49MU6292", "Smart TV LED incurvée 49\"", 599, 49, "LED");
        Fridge fridge = new Fridge("BEKO TSE 1042 F", "Réfrigérateur BEKO 130L - Classe A+ - blanc", 189, 130, false);

        Customer customer = new Customer("Juste Leblanc", "19 rue Germain Pilon, Paris");

        Bill bill = new Bill(customer, new RelayDelivery(27));
        bill.addProduct(cafe, 1);
        bill.addProduct(tv, 1);
        bill.addProduct(fridge, 1);

        bill.generate(new FileWriter("facture_leblanc"));
    }
}

Un outil bien pratique

Avec notre  Writer, nous avons beaucoup de flexibilité. On peut, par exemple, facilement créer un  Writer qui écrit dans le terminal. Cela peut être utile pour valider un fonctionnement et cela ne prend que quelques secondes.

package com.cursan.homeshop;

public class Main {

    public static void main(String[] args) {
        Product cafe = new Product("Philips HD7866/61", "Philips SENSEO Quadrante, Noir - 1 ou 2 tasses", 79.99);
        Product tv = new Television("TV Samsung UE49MU6292", "Smart TV LED incurvée 49\"", 599, 49, "LED");
        Fridge fridge = new Fridge("BEKO TSE 1042 F", "Réfrigérateur BEKO 130L - Classe A+ - blanc", 189, 130, false);

        Customer customer = new Customer("Juste Leblanc", "19 rue Germain Pilon, Paris");

        Bill bill = new Bill(customer, new RelayDelivery(27));
        bill.addProduct(cafe, 1);
        bill.addProduct(tv, 1);
        bill.addProduct(fridge, 1);

        bill.generate(new Writer() {
            @Override
            public void start() {

            }

            @Override
            public void writeLine(String line) {
                System.out.println(line);
            }

            @Override
            public void stop() {

            }
        });
    }
}
Exemple de certificat de réussite
Exemple de certificat de réussite