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
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.
Les deux conditions suivantes doivent être respectées.
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.
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)
Déployez le code qui ne charge que la classe Dollar
.
example/dollar.py
class Dollar:
pass
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
Définissez un constructeur pour que vous puissiez objectiver la classe Dollar
.
example/dollar.py
class Dollar:
def __init__(self, amount):
self.amount = amount
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'
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
Enfin, le test est réussi.
$ pytest -v
...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED [100%]
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.
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)
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'
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)
Le test est maintenant réussi.
$ pytest -v
...(snip)
tests/test_money.py::MoneyTest::testMultiplication PASSED [100%]
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.
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))
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
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)
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%]
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é.
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))
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%]
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)
Le test est réussi.
$ pytest -v
...(snip)
tests/test_money.py::MoneyTest::testEquality PASSED [ 50%]
tests/test_money.py::MoneyTest::testMultiplication PASSED [100%]
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.
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))
Déployez le code qui ne charge que la classe Franc
.
example/franc.py
class Franc:
pass
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
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)
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%]
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 `.
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))
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%]
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)
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'
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)
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%]
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 **?"
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))
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
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__)
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%]
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
.
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))
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)
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)
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)
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%]
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
.
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())
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)
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%]
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 `.
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"))
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
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)
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%]
Les classes «Dollar» et «Franc» peuvent également être éliminées, complétant ainsi le refactoring que nous avons effectué jusqu'à présent.
`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())
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")
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%]
À 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.
ʻ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.
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)
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».
Money
est créé en héritant de la classe abstraite ʻExpression`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
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%]
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éthode
reduction 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)
$ 5 + $ 5
Comme première étape pour réaliser «5 $ + 5 $ = 10 $», le traitement de la partie de «5 $ + 5 $» est matérialisé.
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)
Apportez les modifications suivantes:
--Définir une nouvelle classe Sum
--Dans l'objet Sum
, enregistrez l'état de deux montants:" augend "et" addend ".
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
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%]
reduction
de la classe Bank
Nous matérialiserons la méthode «reduction» de la classe «Bank». Ici, nous allons cibler l'objet Sum
.
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)
Apportez les modifications suivantes:
reduction
de la classe Bank
vous permet de renvoyer le résultat du traitement de la méthode reduction
de l'objet Sum
.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)
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%]
reduction
de la classe Bank
(suite)Nous matérialiserons la méthode «reduction» de la classe «Bank». Ici, nous allons cibler l'objet Money
.
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)
Apportez les modifications suivantes:
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
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%]
Ici, nous allons réaliser le processus de conversion de 2 francs en 1 dollar.
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.
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)
Apportez les modifications suivantes:
--Add_rateméthode à la classe
Bank` (implémentation temporaire)
reduction
de l'objet Money
à partir de la méthode reduction
de la classe Bank
, transmettez votre propre objet Bank
.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
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%]
Sur la base du tableau des taux de change, nous réaliserons le processus de conversion de 2 francs en 1 dollar.
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"))
Apportez les modifications suivantes:
--Autoriser le tableau des taux de change à être maintenu dans la classe Banque
de l'objet
Bank`.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)
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%]
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)».
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)
Apportez les modifications suivantes:
--Modifier la méthode de dérivation ʻamount dans la méthode
reductionde l'objet
Sum --Définissez la méthode
plus 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
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%]
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
plus
de la classe Sum
Complétez la méthode «plus» de la classe «Sum».
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)
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))
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%]
times
de la classe Sum
Complétez la méthode «times» de la classe «Sum».
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)
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
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 **.
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.