Découvrez la partie I «Monnaie multinationale» du livre «Test Driven Development» avec Python

Les exemples repris dans la ** partie I "Multilateral Currency" ** du livre "Test Driven Development" sont basés sur JAVA, donc Pour approfondir ma compréhension, j'ai essayé la même pratique en Python.

À propos, le code de la pratique de développement piloté par les tests est stocké dans le Github suivant. https://github.com/ttsubo/study_of_test_driven_development/tree/master/python

■ Comment procéder au développement piloté par les tests

La procédure de développement piloté par les tests préconisée dans le livre "Développement piloté par les tests" est la suivante.

Dans cette pratique également, nous suivrons cette procédure autant que possible.

■ Expérience de développement piloté par les tests via des devises multinationales

Les deux conditions suivantes doivent être respectées.

Chapitre 1: Monnaie multidevise

Tout d'abord, multipliez le montant (montant par unité monétaire) par une valeur numérique (nombre d'unités monétaires) pour obtenir le montant. Nous visons un mécanisme appelé . Plus précisément, nous allons procéder au codage pour que «5 $ * 2 = 10 $» soit valable.

(1) Commencez par écrire un petit test.

Créez un test qui confirme que «5 $ * 2 = 10 $» tient.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        five.times(2)
        self.assertEqual(10, five.amount)

(2) Préparez le code à tester

Déployez le code qui ne charge que la classe Dollar.

example/dollar.py


class Dollar:
    pass

(3) Exécutez le test et confirmez qu'il échoue.

Le test échoue car la classe «Dollar» ne peut pas être objectivée.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 6, in testMultiplication
    five = Dollar(5)
TypeError: Dollar() takes no arguments

(4) Apportez de petits changements.

Définissez un constructeur pour que vous puissiez objectiver la classe Dollar.

example/dollar.py


class Dollar:
    def __init__(self, amount):  
        self.amount = amount

(5) Exécutez à nouveau le test et confirmez qu'il échoue.

Le test échoue car la classe Dollar n'a pas de méthode times définie.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 7, in testMultiplication
    five.times(2)
AttributeError: 'Dollar' object has no attribute 'times'

(6) Effectuez à nouveau de petits changements.

Multipliez le montant par un nombre pour obtenir le montant. La méthode times le définit pour qu'il puisse l'être.

example/dollar.py


class Dollar:
    def __init__(self, amount):  
        self.amount = amount
                                                                                    
    def times(self, multiplier):                                                    
        self.amount *= multiplier

(7) Exécutez à nouveau le test et assurez-vous que tout est réussi. -> OK </ font>

Enfin, le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 2: Objets dégénérés

Dans le chapitre 1, vous obtenez le montant en multipliant le montant (montant par unité monétaire) par un nombre (nombre d'unités monétaires). En tant que mécanisme appelé , j'ai essayé de mettre en œuvre le minimum nécessaire. De plus, ici, le montant (montant par unité monétaire) est multiplié par une valeur numérique (nombre d'unités monétaires) pour obtenir le montant. Nous allons étendre le mécanisme appelé afin qu'il puisse être appelé plusieurs fois.

(1) Commencez par écrire un petit test.

Après avoir confirmé que «5 $ * 2 = 10 $» tient, créez un test qui contient également «5 $ * 3 = 15 $». On suppose que la classe Dollar est transformée en objet une fois, puis la valeur de multiplication est modifiée de différentes manières pour le test.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(10, product.amount)
        product = five.times(3)
        self.assertEqual(15, product.amount)

(2) Exécutez le test et confirmez qu'il échoue.

Le test échoue car nous n'avons pas encore implémenté la logique pour obtenir le résultat de l'exécution de la méthode times de l'objet Dollar.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/python/tests/test_money.py", line 8, in testMultiplication
    self.assertEqual(10, product.amount)
AttributeError: 'NoneType' object has no attribute 'amount'

(3) Apportez de petits changements.

Implémentez la logique pour obtenir le résultat de l'exécution de la méthode times de l'objet Dollar.

--Créez un nouvel objet Dollar lors de l'appel de la méthode times de l'objet Dollar --Enregistrer le résultat de la multiplication dans la nouvelle variable d'instance d'objet Dollar ʻamount`

example/dollar.py


class Dollar:
    def __init__(self, amount):  
        self.amount = amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.amount * multiplier)

(4) Relancez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est maintenant réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 3: Égalité pour tous

Dans le chapitre 2, vous obtenez le montant en multipliant le montant (montant par unité monétaire) par le nombre (nombre d'unités monétaires). Le mécanisme appelé `a été étendu pour pouvoir être appelé plusieurs fois. Ici, nous allons ajouter un mécanisme pour confirmer que «un $ 5 est la même valeur que l'autre 5 $. À propos, l'identité de l'objet est confirmée en utilisant la méthode spéciale «eq» de python.

(1) Commencez par écrire un petit test.

Créez un test pour vous assurer qu’un $ 5 équivaut à un autre 5 $.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        product = five.times(2)
        self.assertEqual(10, product.amount)
        product = five.times(3)
        self.assertEqual(15, product.amount)
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))

(2) Exécutez le test et confirmez qu'il échoue.

Pourtant, le test échoue car il n'y a pas de mécanisme dans la classe Dollar pour vérifier que` un 5 $ est équivalent à l'autre 5 $.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality FAILED              [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/python/tests/test_money.py", line 13, in testEquality
    self.assertTrue(Dollar(5) == Dollar(5))
  File "/Users/ttsubo/.pyenv/versions/3.8.0/lib/python3.8/unittest/case.py", line 765, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true

(3) Apportez de petits changements.

Profitez de la méthode spéciale __eq__ pour définir` un $ 5 afin que vous puissiez voir qu'il est équivalent à l'autre $ 5.

example/dollar.py


class Dollar:
    def __init__(self, amount):  
        self.amount = amount
    
    def __eq__(self, other):
        return self.amount == other.amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.amount * multiplier)

(4) Relancez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est maintenant réussi.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED              [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 4: Test intentionnel (confidentialité)

Dans le chapitre 3, nous avons ajouté un mécanisme pour nous assurer que «un $ 5 vaut la même chose que l'autre 5 $». Ici, nous allons refactoriser les parties qui se chevauchent de la description du test jusqu'à présent avec une vue claire. De plus, changez la variable d'instance ʻamount de l'objet Dollar` en membre privé.

(1) Test: Refactoriser MoneyTest: testMultiplication.

La méthode times de la classe Dollar renvoie un objet Dollar qui contient son montant multiplié par l'argument multiplicateur dans la variable d'instance de l'objet Dollar ʻamount. Dans la description du test que j'ai écrite jusqu'à présent, il est difficile de dire que l'objet Dollar` est retourné, donc je vais le refactoriser pour améliorer la lisibilité.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))

(2) Exécutez le test et assurez-vous que tout est réussi.

Les tests sont toujours réussis après la refactorisation.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED              [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

(3) Apportez de petits changements.

Remplacez la variable d'instance «montant» de l'objet «Dollar» par un membre privé.

example/dollar.py


class Dollar:
    def __init__(self, amount):  
        self.__amount = amount
    
    def __eq__(self, other):
        return self.__amount == other.__amount
                                                                                    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)

(4) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED              [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 5: Quand on ose briser le principe (franc-lyrique)

Jusqu'au chapitre 4, en tant que l'une des multi-devises, multipliez le montant (montant par unité monétaire) lié à la ** devise du dollar ** par une valeur numérique (nombre d'unités monétaires) pour obtenir le montant. Nous avons réalisé le mécanisme de . Ici, comme une autre monnaie, la ** monnaie franc ** réalisera le même mécanisme.

(1) Commencez par écrire un petit test.

Ajoutez un test afin que vous puissiez voir le même test pour la devise dollar dans la devise franc.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))

(2) Préparez le code à tester

Déployez le code qui ne charge que la classe Franc.

example/franc.py


class Franc:
    pass

(3) Exécutez le test et confirmez qu'il échoue.

Le test échoue car la classe Franc ne peut pas être objectivée.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
___________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 16, in testFrancMultiplication
    five = Franc(5)
TypeError: Franc() takes no arguments

(4) Apportez de petits changements.

Nous définirons la classe «Franc» en nous référant à ce que nous avons fait au chapitre 1.

example/franc.py


class Franc:
    def __init__(self, amount):  
        self.__amount = amount
    
    def __eq__(self, other):
        return self.__amount == other.__amount
                                                                                    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)

(4) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 6: Si vous constatez un manque de tests (Equality for All, Redux)

Dans le chapitre 5, vous obtenez le montant en multipliant le montant (montant par unité monétaire) lié à la devise franc par un nombre (nombre d'unités monétaires). Nous avons réalisé le mécanisme de , mais la méthode correspondait en copiant tout le mécanisme de la devise dollar jusqu'à présent, donc beaucoup de parties se chevauchaient. Pour éliminer les doubles emplois, tout d'abord, «les 5 dollars sur lesquels nous avons travaillé dans le chapitre 3 ont la même valeur que les 5 autres dollars» et les «5 francs ont la même valeur que les 5 autres francs. Démarrez la partie `.

(1) Commencez par écrire un petit test.

Test de devise du dollar «Ajoutez un test pour qu'un 5 $ soit équivalent à l'autre 5 $» en franc.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
        self.assertTrue(Franc(5) == Franc(5))
        self.assertFalse(Franc(5) == Franc(6))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))

(2) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

La partie test ajoutée est également réussie sans aucun problème.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

(3) Refactoriser et supprimer les doublons.

Ici, nous allons effectuer le refactoring suivant.

--Définissez une nouvelle classe parente Money --Eliminer la duplication de la méthode spéciale __eq__ définie dans les classes Dollar et Franc, et la rendre courante dans la classe Money.

example/money.py


class Money:
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return self.__amount == other.__amount

example/dollar.py


from example.money import Money

class Dollar(Money):
    def __init__(self, amount):  
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)

example/franc.py


from example.money import Money

class Franc(Money):
    def __init__(self, amount):  
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)

(4) Relancez le test et assurez-vous que tout est réussi. -> NG </ font>

De manière inattendue, le test a été effacé. </ font>

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication FAILED                [100%]
================================== FAILURES ===================================
______________________________ MoneyTest.testEquality _________________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

  File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Dollar' object has no attribute '_Money__amount'

________________________ MoneyTest.testFrancMultiplication ____________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

  File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Franc' object has no attribute '_Money__amount'

__________________________ MoneyTest.testMultiplication ______________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):

...(snip)

File "/Users/ttsubo/source/study_of_test_driven_development/example/money.py", line 6, in __eq__
    return self.__amount == other.__amount
AttributeError: 'Dollar' object has no attribute '_Money__amount'

(5) Refactoriser pour traiter la cause de l'échec du test.

La raison pour laquelle le test a échoué était le moment de l'objectivation de «Dollar» et «Franc», et il était nécessaire de sauvegarder la valeur dans la variable d'instance «montant» de la classe parente «Money», donc corrigez le code.

example/dollar.py


from example.money import Money

class Dollar(Money):
    def __init__(self, amount):  
        super(Dollar, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)

example/franc.py


from example.money import Money

class Franc(Money):
    def __init__(self, amount):  
        super(Franc, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Franc(self.__amount * multiplier)

(6) Exécutez à nouveau le test et assurez-vous que tout est réussi. -> OK </ font>

Cette fois, le test a réussi. C'est à cause du développement piloté par les tests que vous pouvez immédiatement remarquer même une petite erreur de refactoring. </ font>

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 7: Traduire les doutes en tests (pommes et oranges)

Jusqu'à présent, nous avons soutenu la «devise dollar» et la «devise franc» en tant que devises multiples. La question est maintenant: "Et si vous compariez la ** devise du dollar ** à la ** devise du franc **?"

(1) Commencez par écrire un petit test.

Ajoutez un test pour vous assurer que la ** devise du dollar ** et la ** devise du franc ** ne sont pas égales.

tests/test_money.py


from testtools import TestCase
from example.dollar import Dollar
from example.franc import Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Dollar(5)
        self.assertEqual(Dollar(10), five.times(2))
        self.assertEqual(Dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Dollar(5) == Dollar(5))
        self.assertFalse(Dollar(5) == Dollar(6))
        self.assertTrue(Franc(5) == Franc(5))
        self.assertFalse(Franc(5) == Franc(6))
        self.assertFalse(Franc(5) == Dollar(5))

    def testFrancMultiplication(self):
        five = Franc(5)
        self.assertEqual(Franc(10), five.times(2))
        self.assertEqual(Franc(15), five.times(3))

(2) Exécutez le test. -> NG </ font>

Le test échouera. En d'autres termes, le résultat est que le dollar et le franc sont égaux.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
================================== FAILURES ===================================
______________________________ MoneyTest.testEquality _________________________
NOTE: Incompatible Exception Representation, displaying natively:

testtools.testresult.real._StringException: Traceback (most recent call last):
  File "/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py", line 16, in testEquality
    self.assertFalse(Franc(5) == Dollar(5))
  File "/Users/ttsubo/.pyenv/versions/3.8.0/lib/python3.8/unittest/case.py", line 759, in assertFalse
    raise self.failureException(msg)
AssertionError: True is not false

(3) Traitez la cause de l'échec du test.

Puisqu'il était nécessaire de comparer l'objet «Dollar» et l'objet «Franc» pour effectuer la comparaison d'équivalence, modifiez la partie logique de jugement de la méthode spéciale «eq» de la classe «Money».

example/money.py


class Money:
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

(4) Relancez le test et assurez-vous que tout est réussi. -> OK </ font>

Cette fois, le test a réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 8: Implémentation de Hide (objets Makin)

Dans les chapitres 6 et 7, nous avons remanié la comparaison d'équivalence. À partir de maintenant, pendant un certain temps, multipliez le montant (montant par unité monétaire) par un nombre (nombre d'unités monétaires) pour obtenir le montant. Tente d'éliminer les doublons dans le mécanisme .

(1) Commencez par rédiger un petit test.

Ici, on suppose que la refactorisation suivante sera effectuée comme première étape pour éliminer la partie dupliquée de la méthode times définie dans les classes Dollar et Franc et pour la rendre commune dans la classe Money. Et corrigez le test.

--Définissez la méthode de classe dollar dans la classe Money pour créer un objet Dollar --Définissez la méthode de classe franc dans la classe Money pour créer un objet Franc

tests/test_money.py


from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))

(2) Refactor.

Ici, le refactoring suivant est effectué comme première étape pour éliminer la partie dupliquée de la méthode times définie dans les classes Dollar et Franc et pour la rendre commune dans la classe Money.

--Définissez la méthode de classe dollar dans la classe Money pour créer un objet Dollar --Définissez la méthode de classe franc dans la classe Money pour créer un objet Franc

example/money.py


from abc import ABCMeta, abstractmethod
from example.dollar import Dollar
from example.franc import Franc

class Money(metaclass=ABCMeta):
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount)

    @classmethod
    def franc(cls, amount):
        return Franc(amount)

(3) Exécutez le test. -> NG </ font>

Le test n'a pas pu démarrer et une erreur s'est produite. Il semble que la cause soit l'importation circulaire de modules Python.

$ pytest -v

...(snip)

tests/test_money.py::MoneyTest::testEquality FAILED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication FAILED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]
=================================== ERRORs ====================================
ImportError while importing test module '/Users/ttsubo/source/study_of_test_driven_development/tests/test_money.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_money.py:2: in <module>
    from example.money import Money
example/money.py:2: in <module>
    from example.dollar import Dollar
example/dollar.py:1: in <module>
    from example.money import Money
E   ImportError: cannot import name 'Money' from partially initialized module 'example.money' (most likely due to a circular import) (/Users/ttsubo/source/study_of_test_driven_development/example/money.py)

(4) Traitez la cause de l'échec du test.

En raison de l'importation circulaire des modules Python, modifiez les classes Dollar et Franc à définir dans money.py.

example/money.py


from abc import ABCMeta, abstractmethod

class Money(metaclass=ABCMeta):
    def __init__(self, amount):  
        self.__amount = amount

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount)

    @classmethod
    def franc(cls, amount):
        return Franc(amount)


class Dollar(Money):
    def __init__(self, amount):  
        super(Dollar, self).__init__(amount)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Dollar(self.__amount * multiplier)


class Franc(Money):
    def __init__(self, amount):
        super(Franc, self).__init__(amount)
        self.__amount = amount

    def times(self, multiplier):
        return Franc(self.__amount * multiplier)

(5) Exécutez à nouveau le test et assurez-vous que tout est réussi. -> OK </ font>

Cette fois, le test a réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 9: Ajustement de la longueur de foulée (fois où nous vivons)

En continuant du chapitre précédent, multipliez le montant (montant par unité monétaire) par une valeur numérique (nombre d'unités monétaires) pour obtenir le montant. Tente d'éliminer les doublons dans le mécanisme .

(1) Commencez par rédiger un petit test.

Ici, les objets Dollar etFranc sont utilisés comme première étape pour éliminer la duplication de la méthode times définie dans les classes Dollar et Franc et pour les rendre communes dans la classe Money. Ajoutez le test testCurrency, en supposant que vous appliquez le concept de " currency " à des fins de distinction.

--Définissez la variable d'instance «__currency» sur «USD» lors de la création de l'objet «Dollar». --Définissez la méthode currency afin de pouvoir référencer le membre privé __currency de l'objet Dollar --Définissez «CHF» dans la variable d'instance «__currency» lors de la création de l'objet «Franc». --Définissez la méthode currency afin de pouvoir référencer le membre privé __currency de l'objet Franc

tests/test_money.py


from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))
    
    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

(2) Refactor.

Ici, les objets Dollar etFranc sont utilisés comme première étape pour éliminer la duplication de la méthode times définie dans les classes Dollar et Franc et pour les rendre communes dans la classe Money. Nous appliquons le concept de «monnaie», qui vise à faire une distinction.

--Définissez la variable d'instance «__currency» sur «USD» lors de la création de l'objet «Dollar». --Définissez la méthode currency afin de pouvoir référencer le membre privé __currency de l'objet Dollar --Définissez «CHF» dans la variable d'instance «__currency» lors de la création de l'objet «Franc». --Définissez la méthode currency afin de pouvoir référencer le membre privé __currency de l'objet Franc

example/money.py


from abc import ABCMeta, abstractmethod

class Money(metaclass=ABCMeta):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.__class__.__name__ == other.__class__.__name__)

    @abstractmethod
    def times(self, multiplier):
        pass

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Franc(amount, "CHF")


class Dollar(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)
        self.__amount = amount
    
    def times(self, multiplier):                                                    
        return Money.dollar(self.__amount * multiplier)


class Franc(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)
        self.__amount = amount

    def times(self, multiplier):
        return Money.franc(self.__amount * multiplier)

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Comme prévu, le test a réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 50%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 75%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 10: Posez le test (périodes intéressantes)

Multipliez le montant (montant par unité monétaire) que vous avez commencé jusqu'à présent par une valeur numérique (nombre d'unités monétaires) pour obtenir le montant. Terminez la tentative d'éliminer la partie dupliquée du mécanisme appelée `.

(1) Commencez par rédiger un petit test.

Multipliez le montant (montant par unité monétaire) par le nombre (nombre d'unités monétaires) pour obtenir le montant. Ajoutez le test testDifferentClassEquality, en supposant que le refactoring pour éliminer les doublons dans le mécanisme est terminé.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Franc

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertTrue(Money.franc(5) == Money.franc(5))
        self.assertFalse(Money.franc(5) == Money.franc(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testFrancMultiplication(self):
        five = Money.franc(5)
        self.assertEqual(Money.franc(10), five.times(2))
        self.assertEqual(Money.franc(15), five.times(3))
    
    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testDifferentClassEquality(self):
        self.assertTrue(Money(10, "CHF") == Franc(10, "CHF"))

(2) Refactor.

Effectuez la refactorisation suivante:

--Supprimer la méthode times définie dans la classe Dollar, Franc --Définir comme une méthode times courante dans la classe Money

  • Revoir la méthode de comparaison d'équivalence des objets Money et modifier la méthode spéciale __eq__ --Modifier la classe Money qui a été traitée comme une classe abstraite, en supposant la création d'un objetMoney

example/money.py


class Money():
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Dollar(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Franc(amount, "CHF")


class Dollar(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)


class Franc(Money):
    def __init__(self, amount, currency):
        super().__init__(amount, currency)

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Comme prévu, le test a réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testDifferentClassEquality PASSED        [ 40%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 60%]
tests/test_money.py::MoneyTest::testFrancMultiplication PASSED           [ 80%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 11: La racine de tout mal

Les classes «Dollar» et «Franc» peuvent également être éliminées, complétant ainsi le refactoring que nous avons effectué jusqu'à présent.

(1) Commencez par rédiger un petit test.

`Multipliez le montant (montant par unité monétaire) par le nombre (nombre d'unités monétaires) pour obtenir le montant. En supposant que le refactoring du mécanisme appelé ʻest terminé, le test lui-même est également refactorisé.

tests/test_money.py


from testtools import TestCase
from example.money import Money

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))
    
    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

(2) Refactor.

Vous n'avez pas besoin de définir les classes «Dollar», «Franc» car vous pouvez déjà faire la distinction entre «** devise dollar » et « devise franc **» dans l'objet «Argent». Sera. Pour terminer le refactoring, supprimez les classes Dollar, Franc.

example/money.py


class Money():
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Comme prévu, le test a réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 66%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [100%]

Chapitre 12: Conception et métaphore (ajout, enfin)

À partir de là, «ajoutez deux montants différents de devises pour obtenir le montant converti en fonction du taux de change entre les devises. Nous réaliserons un mécanisme qui satisfait aux exigences de `. De plus, il semble que cette exigence puisse être décomposée en deux «ToDo» suivants.

  • $5 + $5 = $10 --$ 5 + 10 CHF = 10 $ (lorsque le taux est 2: 1)

Tout d'abord, ajoutez les deux montants pour obtenir le montant. En tant que mécanisme de , nous procédons au codage de sorte que $ 5 + $ 5 = $ 10` soit valable.

(1) Commencez par rédiger un petit test.

ʻAjoutez deux montants pour obtenir le montant. Ajoutez le test testSimpleAddition pour voir comment` fonctionne. De plus, nous garderons à l'esprit les deux concepts suivants pour les futurs designs.

  • Le concept d '«objet bancaire» basé sur l'idée qu'il incombe à la banque de convertir la devise
  • Le concept de variable «réduite» pour stocker le résultat de la conversion »obtenu en appliquant le« taux de change »

tests/test_money.py


from testtools import TestCase
from example.money import Money
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)

(2) Apportez de petits changements.

L'implémentation abordée dans ce chapitre est assez déroutante, c'est donc un changement assez important, mais cela ressemble à ceci.

―― ʻAjoutez deux montants pour obtenir le montant. Pour implémenter le mécanisme, ajoutez une méthode «plus» à la classe «Money».

  • Un objet Money est créé en héritant de la classe abstraite ʻExpression`
  • La méthode reduction de la classe Bank est provisoirement implémentée ici.

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Money(self.__amount + addend.__amount, self.__currency)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

example/bank.py


from example.money import Money

class Bank():
    def reduce(self, source , toCurrency):
        return Money.dollar(10)

example/expression.py


from abc import ABCMeta

class Expression(metaclass=ABCMeta):
    pass

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 75%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

Chapitre 13: Faites-le

Continuez à ajouter deux montants pour obtenir le montant. En tant que mécanisme de , continuez avec le codage de sorte que $ 5 + $ 5 = $ 10tient. La dernière fois, nous allons démarrer le comportement de la méthodereduction de la classe Bank` où elle a été définie comme une implémentation temporaire. De plus, étant donné que les efforts ici sont susceptibles d'être assez volumineux, nous allons procéder aux étapes suivantes.

--ETAPE1: Ecrire le traitement de $ 5 + $ 5 --STEP2: Matérialiser l'implémentation temporaire de la méthode reduction de la classe Bank --STEP3: matérialiser l'implémentation temporaire de la méthode reduction de la classe Bank (suite)

ÉTAPE 1: Écrivez le traitement de $ 5 + $ 5

Comme première étape pour réaliser «5 $ + 5 $ = 10 $», le traitement de la partie de «5 $ + 5 $» est matérialisé.

(1) Commencez par rédiger un petit test.

Ajoutez le test testPlusReturnsSum.

tests/test_money.py


from testtools import TestCase
from example.money import Money
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)

    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Définir une nouvelle classe Sum --Dans l'objet Sum, enregistrez l'état de deux montants:" augend "et" addend ".

  • Modifiez la méthode «plus» de la classe «Money» pour renvoyer l'objet «Sum».

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 40%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 60%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 80%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

ÉTAPE2: matérialiser l'implémentation temporaire de la méthode reduction de la classe Bank

Nous matérialiserons la méthode «reduction» de la classe «Bank». Ici, nous allons cibler l'objet Sum.

(1) Commencez par rédiger un petit test.

Ajoutez le test testReduceSum.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)

    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

  • La méthode reduction de la classe Bank vous permet de renvoyer le résultat du traitement de la méthode reduction de l'objet Sum.
  • Dans la méthode reduction de la classe Sum, ajoutezdeux montants pour obtenir le montant. Définir le mécanisme de --Définissez la méthode ʻamount dans la classe Moneyafin que le membre privé__amount` puisse être référencé de l'extérieur.

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)

example/bank.py


class Bank():
    def reduce(self, source , toCurrency):
        return source.reduce(toCurrency)

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 16%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 50%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 66%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 83%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

ÉTAPE 3: matérialiser l'implémentation temporaire de la méthode reduction de la classe Bank (suite)

Nous matérialiserons la méthode «reduction» de la classe «Bank». Ici, nous allons cibler l'objet Money.

(1) Commencez par rédiger un petit test.

Ajoutez le test testReduceMoney.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

  • La méthode reduction de la classe Bank vous permet de renvoyer le résultat du traitement de la méthode reduction de l'objet Money. --Définir la méthode reduction de la classe Money (implémentation provisoire) --Définissez la méthode abstraite «réduire» dans la classe abstraite «Expression» pour forcer la définition de méthode «réduire» dans la classe «Money» et la classe «Sum».

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, toCurrency):
        return self

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)

example/expression.py


from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def reduce(self, toCurrency):
        pass

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 14%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 28%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 42%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 57%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 71%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 85%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

Chapitre 14: Tests d'apprentissage et de régression (changement)

Ici, nous allons réaliser le processus de conversion de 2 francs en 1 dollar.

  • ÉTAPE 1: Traitement pour convertir 2 francs en 1 dollar (le taux de change est provisoirement mis en œuvre)
  • ETAPE2: Traitement pour convertir 2 francs en 1 dollar (mise en place du tableau des taux de change)

ÉTAPE 1: Traitement pour convertir 2 francs en 1 dollar (le taux de change est provisoirement mis en œuvre)

Nous réaliserons le processus de conversion de 2 francs en 1 dollar. Cependant, on suppose que le taux de change-> USD: CHF = 2: 1.

(1) Commencez par rédiger un petit test.

Ajoutez le test testReduceMoneyDifferentCurrency.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Add_rateméthode à la classeBank` (implémentation temporaire)

  • Lors de l'appel de la méthode reduction de l'objet Money à partir de la méthode reduction de la classe Bank, transmettez votre propre objet Bank.
  • Lors de l'appel de la méthode reduction de l'objet Sum depuis la méthode reduction de la classe Bank, transmettez votre propre objet Bank. --Définissez la méthode «taux» de la classe «Banque» afin que le taux de change puisse être obtenu à titre provisoire (taux de change-> USD: CHF = 2: 1)

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.amount() + self.addend.amount()
        return Money(amount, toCurrency)

example/bank.py


class Bank():
    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        pass

    def rate(self, fromCurrency, toCurrency):
        return 2 if (fromCurrency == "CHF" and toCurrency == "USD") else 1

example/expression.py


from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 12%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 25%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 37%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 50%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 62%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 75%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 87%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

ÉTAPE 2: Traitement pour convertir 2 francs en 1 dollar (mise en place du tableau des taux de change)

Sur la base du tableau des taux de change, nous réaliserons le processus de conversion de 2 francs en 1 dollar.

(1) Commencez par rédiger un petit test.

Ajoutez le test testIdentityRate.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Autoriser le tableau des taux de change à être maintenu dans la classe Banque

  • Différents taux peuvent être ajoutés à la table des taux de change par la méthode ʻadd_rate de l'objet Bank`.
  • Vous pouvez faire référence au taux requis par la méthode «taux» de l'objet «Banque».

example/bank.py


class Bank():
    def __init__(self):
        self._rates = {}

    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        target_rate = "{0}:{1}".format(fromCurrency, toCurrency)
        self._rates[target_rate] = rate

    def rate(self, fromCurrency, toCurrency):
        target_rate = "{0}:{1}".format(fromCurrency, toCurrency)
        if fromCurrency == toCurrency:
            return 1
        return self._rates.get(target_rate)

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 11%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 22%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 44%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 55%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 66%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 77%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 88%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

Chapitre 15: Test et compilateur (devises mixtes)

Jusqu'à présent, «ajoutez deux montants de devise différents et obtenez le montant converti en fonction du taux de change entre les devises. Nous avons cherché à réaliser un mécanisme qui satisfait aux exigences de «. Ici, nous allons commencer la tâche de «5 $ + 10 CHF = 10 $ (lorsque le taux est 2: 1)».

(1) Commencez par rédiger un petit test.

Ajoutez le test testMixedAddition.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Modifier la méthode de dérivation ʻamount dans la méthode reductionde l'objetSum --Définissez la méthodeplus dans la classe Sum` --Définissez la méthode abstraite «plus» dans la classe abstraite «Expression» et forcez la définition de la méthode «plus» dans la classe «Money» et la classe «Sum».

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount() + \
            self.addend.reduce(bank, toCurrency).amount()
        return Money(amount, toCurrency)

    def plus(self, addend):
        pass

example/expression.py


from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def plus(self, addend):
        pass

    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [ 10%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 20%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 30%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 40%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 50%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 60%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 70%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 80%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 90%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [100%]

Chapitre 16: Test pour les futurs lecteurs (abstraction, enfin)

Enfin, «ajoutez les deux devises différentes et obtenez le montant converti en fonction du taux de change entre les devises. Nous achèverons la réalisation d'un mécanisme qui satisfait aux exigences de `.

--STEP1: Complétez la méthode plus de la classe Sum --STEP2: Complétez la méthode times de la classe Sum

ÉTAPE 1: Complétez la méthode plus de la classe Sum

Complétez la méthode «plus» de la classe «Sum».

(1) Commencez par rédiger un petit test.

Ajoutez le test testSumPlusMoney.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)

    def testSumPlusMoney(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).plus(fiveBucks)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(15), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Retourne l'objet Sum lorsque la méthode plus de l'objet Sum est appelée. --Définissez la méthode abstraite «plus» dans la classe abstraite «Expression» et forcez la définition de la méthode «plus» dans la classe «Money» et la classe «Sum».

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.amount = amount
        self._currency = currency

    def __eq__(self, other):
        return (self.amount == other.amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.amount * multiplier, self._currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.currency(), toCurrency)
        return Money(self.amount / rate, toCurrency)

    def currency(self):
        return self._currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount + \
            self.addend.reduce(bank, toCurrency).amount
        return Money(amount, toCurrency)

    def plus(self, addend):
        return Sum(self, addend)

example/bank.py


class Bank():
    def __init__(self):
        self._rates = {}

    def reduce(self, source , toCurrency):
        return source.reduce(self, toCurrency)

    def add_rate(self, fromCurrency, toCurrency, rate):
        self._rates[(fromCurrency, toCurrency)] = rate

    def rate(self, fromCurrency, toCurrency):
        if fromCurrency == toCurrency:
            return 1
        return self._rates.get((fromCurrency, toCurrency))

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Le test est réussi.

$ pytest -v

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [  9%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 18%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 27%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 36%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 45%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 54%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 63%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 72%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 81%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [ 90%]
tests/test_money.py::MoneyTest::testSumPlusMoney PASSED                  [100%]

ÉTAPE 2: Complétez la méthode times de la classe Sum

Complétez la méthode «times» de la classe «Sum».

(1) Commencez par rédiger un petit test.

Ajoutez le test testSumTimes.

tests/test_money.py


from testtools import TestCase
from example.money import Money, Sum
from example.bank import Bank

class MoneyTest(TestCase):
    def testMultiplication(self):
        five = Money.dollar(5)
        self.assertEqual(Money.dollar(10), five.times(2))
        self.assertEqual(Money.dollar(15), five.times(3))

    def testEquality(self):
        self.assertTrue(Money.dollar(5) == Money.dollar(5))
        self.assertFalse(Money.dollar(5) == Money.dollar(6))
        self.assertFalse(Money.franc(5) == Money.dollar(5))

    def testCurrency(self):
        self.assertEqual("USD", Money.dollar(1).currency())
        self.assertEqual("CHF", Money.franc(1).currency())

    def testSimpleAddition(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        bank = Bank()
        reduced = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(10), reduced)
    
    def testPlusReturnsSum(self):
        five = Money.dollar(5)
        _sum = five.plus(five)
        self.assertEqual(five, _sum.augend)
        self.assertEqual(five, _sum.addend)

    def testReduceSum(self):
        _sum = Sum(Money.dollar(3), Money.dollar(4))
        bank = Bank()
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(7), result)

    def testReduceMoney(self):
        bank = Bank()
        result = bank.reduce(Money.dollar(1), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testReduceMoneyDifferentCurrency(self):
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(Money.franc(2), "USD")
        self.assertEqual(Money.dollar(1), result)

    def testIdentityRate(self):
        self.assertEqual(1, Bank().rate("USD", "USD"))

    def testMixedAddition(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        result = bank.reduce(fiveBucks.plus(tenFrancs), "USD")
        self.assertEqual(Money.dollar(10), result)

    def testSumPlusMoney(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).plus(fiveBucks)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(15), result)

    def testSumTimes(self):
        fiveBucks = Money.dollar(5)
        tenFrancs = Money.franc(10)
        bank = Bank()
        bank.add_rate("CHF", "USD", 2)
        _sum = Sum(fiveBucks, tenFrancs).times(2)
        result = bank.reduce(_sum, "USD")
        self.assertEqual(Money.dollar(20), result)

(2) Apportez de petits changements.

Apportez les modifications suivantes:

--Retourne l'objet Sum lorsque la méthode times de l'objet Sum est appelée --Définissez la méthode abstraite times dans la classe abstraite ʻExpression pour forcer la définition de méthode times dans la classe Money et la classe Sum`.

example/money.py


from example.expression import Expression

class Money(Expression):
    def __init__(self, amount, currency):  
        self.__amount = amount
        self.__currency = currency

    def __eq__(self, other):
        return (self.__amount == other.__amount
                and self.currency() == other.currency())

    def times(self, multiplier):
        return Money(self.__amount * multiplier, self.__currency)

    def plus(self, addend):
        return Sum(self, addend)

    def reduce(self, bank, toCurrency):
        rate = bank.rate(self.__currency, toCurrency)
        return Money(self.__amount / rate, toCurrency)

    def amount(self):
        return self.__amount

    def currency(self):
        return self.__currency

    @classmethod
    def dollar(cls, amount):
        return Money(amount, "USD")

    @classmethod
    def franc(cls, amount):
        return Money(amount, "CHF")

class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def reduce(self, bank, toCurrency):
        amount = self.augend.reduce(bank, toCurrency).amount() + \
            self.addend.reduce(bank, toCurrency).amount()
        return Money(amount, toCurrency)

    def plus(self, addend):
        return Sum(self, addend)

    def times(self, multiplier):
        return Sum(self.augend.times(multiplier), self.addend.times(multiplier))

example/expression.py


from abc import ABCMeta, abstractmethod

class Expression(metaclass=ABCMeta):
    @abstractmethod
    def plus(self, addend):
        pass

    @abstractmethod
    def reduce(self, bank, toCurrency):
        pass

    @abstractmethod
    def times(self, multiplier):
        pass

(3) Exécutez le test et assurez-vous que tout est réussi. -> OK </ font>

Les tests ont été couronnés de succès et la couverture est généralement bonne.

$ pytest -v --cov=example

...(snip)
tests/test_money.py::MoneyTest::testCurrency PASSED                      [  8%]
tests/test_money.py::MoneyTest::testEquality PASSED                      [ 16%]
tests/test_money.py::MoneyTest::testIdentityRate PASSED                  [ 25%]
tests/test_money.py::MoneyTest::testMixedAddition PASSED                 [ 33%]
tests/test_money.py::MoneyTest::testMultiplication PASSED                [ 41%]
tests/test_money.py::MoneyTest::testPlusReturnsSum PASSED                [ 50%]
tests/test_money.py::MoneyTest::testReduceMoney PASSED                   [ 58%]
tests/test_money.py::MoneyTest::testReduceMoneyDifferentCurrency PASSED  [ 66%]
tests/test_money.py::MoneyTest::testReduceSum PASSED                     [ 75%]
tests/test_money.py::MoneyTest::testSimpleAddition PASSED                [ 83%]
tests/test_money.py::MoneyTest::testSumPlusMoney PASSED                  [ 91%]
tests/test_money.py::MoneyTest::testSumTimes PASSED                      [100%]

...(snip)
---------- coverage: platform darwin, python 3.8.0-final-0 -----------
Name                    Stmts   Miss  Cover
-------------------------------------------
example/__init__.py         0      0   100%
example/bank.py            13      0   100%
example/expression.py      11      3    73%
example/money.py           35      0   100%
-------------------------------------------
TOTAL                      59      3    95%

...(snip)

Avec ce qui précède, j'ai essayé de faire l'expérience du développement de la version Python de la «monnaie multinationale» en utilisant ** le développement piloté par les tests **.

■ À la fin ...

Vous pouvez également découvrir le développement piloté par les tests en Python dans la partie II «xUnit» du livre «Test-Driven Development». En fait, quand je l'ai vécu, j'ai remarqué que le ** développement piloté par les tests ** était assez différent du test que j'avais imaginé, et je l'ai complètement mal compris. Ceci est une citation du chapitre 32 du livre "Learn TDD"! !!

Ironiquement, le TDD n'est pas une technique de test (proposition de Cunningham). Le TDD est une technique analytique, une technique de conception, et en fait une technique qui structure toutes les activités de développement.

De plus, l'annexe C, «Commentaire du traducteur: le présent du développement piloté par les tests», m'a aidé à comprendre TDD / BDD et a été une excellente expérience d'apprentissage.

■ URL de référence

Recommended Posts