Programowanie oparte o testy
14 February 2010
Comments
Załóżmy że mamy napisać kod wykonujący jakąś czynność. Zamiast zabrać się od razu za pisanie kodu zaczniemy od testów jednostkowych - obrazując TDD (Test Driven Development), czyli metodę pisania kodu w oparciu o krótkie testy sprawdzające poprawność kodu z założeniami. W punktach można przedstawić to tak:
- piszemy test
- "patrzymy" jak się nie powodzi
- piszemy kod by test się wykonał
- piszemy kod by test udał się
- porządkujemy kod i sprawdzamy testami
- porządkujemy testy
Dzięki temu tworzymy API, które jest od razu używane i wiemy w jakim jest stanie (czy działa, czy nie, jak szybko itd.). Dodatkowo nakładając siatkę małych testów nie zaczniemy tworzyć ogromnych bloków kodu trudnych do utrzymania i rozbudowy. W Pytonie znajdziemy wbudowany unittest, a także inne implementacje jak nose, nosy, pymock, py.test. Przejdźmy więc do przykładu.
A więc chcemy napisać klasę, która np. będzie mogła przeliczać kwoty podane w Euro, czy funtach na złotówki. Zaczynamy od testu, tworzymy plik tests.py o kodzie:# -*- coding: utf-8 -*-
import unittest
class TestCurrencyCalculator(unittest.TestCase):
def testCurrencyCalculatorExist(self):
calculator = CurrencyCalculator()
if __name__=="__main__":
unittest.main()
======================================================================
ERROR: testCurrencyCalculatorExist (__main__.TestCurrencyCalculator)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 7, in testCurrencyCalculatorExist
calculator = CurrencyCalculator()
NameError: global name 'CurrencyCalculator' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
# -*- coding: utf-8 -*-
import unittest
from calculator import CurrencyCalculator
class TestCurrencyCalculator(unittest.TestCase):
def testCurrencyCalculatorExist(self):
calculator = CurrencyCalculator()
if __name__=="__main__":
unittest.main()
Traceback (most recent call last): File "tests.py", line 3, infrom calculator import CurrencyCalculator ImportError: No module named calculator
Traceback (most recent call last): File "tests.py", line 3, infrom calculator import CurrencyCalculator ImportError: cannot import name CurrencyCalculator
class CurrencyCalculator:
pass
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
class TestCurrencyCalculator(unittest.TestCase):
def testCurrencyCalculatorExist(self):
calculator = CurrencyCalculator()
def testCurrencyCalculatorHasEuroExchangeRate(self):
calculator = CurrencyCalculator()
assert calculator.euro_exchange_rate != None, "calculator euro_exchange_rate is None"
def testCurrencyCalculatorHasPoundExchangeRate(self):
calculator = CurrencyCalculator()
assert calculator.pound_exchange_rate != None, "calculator pound_exchange_rate is None"
AttributeError: CurrencyCalculator instance has no attribute 'euro_exchange_rate'
Więc dodajemy je do klasy, powiedzmy że napiszemy taki kod:
class CurrencyCalculator:
def __init__(self):
euro_exchange_rate = 0
pound_exchange_rate = 0
class CurrencyCalculator:
def __init__(self):
self.euro_exchange_rate = 0
self.pound_exchange_rate = 0
def testCurrencyCalculatorSetEuroExchangeRate(self):
calculator = CurrencyCalculator()
calculator.setEuroExchangeRate(4.1)
assert calculator.euro_exchange_rate == 4.1, "calculator euro_exchange_rate did not set correctly"
def testCurrencyCalculatorSetEuroExchangeRate(self):
calculator = CurrencyCalculator()
calculator.setPoundExchangeRate(4.6)
assert calculator.pound_exchange_rate == 4.6, "calculator pound_exchange_rate did not set correctly"
AttributeError: CurrencyCalculator instance has no attribute 'setPoundExchangeRate'
def setPoundExchangeRate(self, value):
pass
AssertionError: calculator pound_exchange_rate did not set correctly
class CurrencyCalculator:
def __init__(self):
self.euro_exchange_rate = 0
self.pound_exchange_rate = 0
def setEuroExchangeRate(self, value):
self.euro_exchange_rate = value
def setPoundExchangeRate(self, value):
self.pound_exchange_rate = value
# -*- coding: utf-8 -*-
import unittest
from calculator import CurrencyCalculator
class TestCurrencyCalculator(unittest.TestCase):
def setUp(self):
self.calculator = CurrencyCalculator()
def testCurrencyCalculatorHasEuroExchangeRate(self):
assert self.calculator.euro_exchange_rate != None, "calculator euro_exchange_rate is None"
def testCurrencyCalculatorHasPoundExchangeRate(self):
assert self.calculator.pound_exchange_rate != None, "calculator pound_exchange_rate is None"
def testCurrencyCalculatorSetEuroExchangeRate(self):
self.calculator.setEuroExchangeRate(4.1)
assert self.calculator.euro_exchange_rate == 4.1, "calculator euro_exchange_rate did not set correctly"
def testCurrencyCalculatorSetEuroExchangeRate(self):
self.calculator.setPoundExchangeRate(4.6)
assert self.calculator.pound_exchange_rate == 4.6, "calculator pound_exchange_rate did not set correctly"
if __name__=="__main__":
unittest.main()
def testCurrencyCalculatorPoundsToPLN(self):
self.calculator.setPoundExchangeRate(4.6)
assert self.calculator.poundsToPLN(1) == 4.6, "calculator poundsToPLN failed"
assert self.calculator.poundsToPLN(2) == 9.2, "calculator poundsToPLN failed"
def testCurrencyCalculatorEurosToPLN(self):
self.calculator.setEuroExchangeRate(4.1)
assert self.calculator.eurosToPLN(1) == 4.1, "calculator eurosToPLN failed"
assert self.calculator.eurosToPLN(2) == 8.2, "calculator eurosToPLN failed"
def eurosToPLN(self, ammount):
return self.euro_exchange_rate*ammount
def poundsToPLN(self, ammount):
return self.pound_exchange_rate*ammount
Powyższy prosty przykład stanowi wprowadzenie do TDD i nie wyczerpuje tematu, jak i wszystkich metod pisania testów. Zainteresowanym polecam m.in. prezentację Test Driven Development in Python.
RkBlog
Comment article