View
221
Download
2
Category
Preview:
DESCRIPTION
Tomás Henríquez2-11-2012tchenriquez@gmail.com
Citation preview
Pruebas Unitarias con Django Tomás Henríquez tchenriquez@gmail.com 2-11-2012
Historia de las pruebas
en el software
http://clearspecs.com/joomla15/downloads/ClearSpecs16V01_GrowthOfSoftwareTest.pdf
Historia de las pruebas
Período orientado al Debugging
(antes - 1956)
Historia de las pruebas
Período orientado a la demostación
(1957 - 1978)
Historia de las pruebas
Historia de las pruebas
Período orientado a la destrucción
(1979 - 1982)
Historia de las pruebas
Período orientado a la evaluación
(1983 - 1987)
Historia de las pruebas
“Ninguna técnica puede garantizar software sin errores, sin embargo, un conjunto de
técnicas cuidadosamente elegidas para un proyecto en específico puede ayudar a
asegurar el desarrollo y mantenimiento de software de calidad para un proyecto”
Historia de las pruebas
Período orientado a la prevención
(1988 - Hoy)
Pruebas Unitarias
Pruebas
¿Cómo uno usualmente prueba
su código?
Pruebas
¿Qué problemas tiene
el debugging?
Pruebas
¿Cómo persistir
estas pruebas?
Pruebas
¡NO ES FÁCIL!
Pruebas
¡NO ES FÁCIL!
● Carga de datos para la prueba● No debe afectar la base de datos● Super rápidas
Sheet Got Serious
Carga de datos
FIXTURES
Carga de datos
class Profession(models.Model): name = models.CharField(max_length=30, unique=True) specialist = models.CharField(max_length=30, blank=True)
Carga de datos[ { "pk": 1, "model": "my_app.profession", "fields": { "name": "panadero", "specialist": "pizza", } }, { "pk": 2, "model": "my_app.profession", "fields": { "name": "forever_alone", ... ]
Carga de datos
class Profession(models.Model): name = models.CharField(max_length=30, unique=True) specialist = models.CharField(max_length=30, blank=True) full_time = models.BooleanField(default=False) <----
Carga de datos[ { "pk": 1, "model": "my_app.profession", "fields": { "name": "panadero", "specialist": "pizza", "full_time": false, <--- Ay no..... } }, { "pk": 2, "model": "my_app.profession", "fields": { "name": "forever_alone", ... ]
Carga de datos[ { "pk": 1, "model": "my_app.profession", "fields": { "name": "panadero", "specialist": "pizza", "full_time": false, <--- Ay no..... } }, { "pk": 2, "model": "my_app.profession", "fields": { "name": "forever_alone", ... ]
Carga de datos
FIXTURES
¿Para qué son buenos?
Carga de datos
Estructura del App
my_app/
__init__.py
models.py
fixtures/base.json <------
tests.py
Carga de datos
class UserTest(TestCase): fixtures = ['base.json']
def setUp(self): pass
def tearDown(self): pass
def test_coolness(self): # usuario con id == 1 existe gracias al fixture
user = User.objects.get(id=1)
self.assertTrue(user.is_cool, False) user.become_a_hipster() self.assertTrue(user.is_cool, True)
Carga de datos
Factories
https://github.com/dnerdy/factory_boy
Carga de datos
Factories● Organizado <------
Carga de datos
from django.db import models
class Receipt(models.Model): user_id = fields.IntegerField() merchant = fields.CharField(max_length=30)
class ReceiptItem(models.Model): name = models.CharField(max_length=30) quantity = models.IntegerField(default=1) alert_sent = models.BooleanField(default=False) receipt = models.ForeignKey(Receipt)
class Attach(models.Model): name = models.CharField(max_length=30) item = models.ForeignKey(ReceiptItem)
Carga de datos
class ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): rep = Receipt(user_id=1, merchant='mami') rep.save()
ri = ReceiptItem(name='medias', quantity=2, receipt=rep) ri.save()
att = Attach(name='foto_de_juanita.jpg', item=ri) att.save() ...
Carga de datos
class ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): rep = Receipt(user_id=1, merchant='mami') rep.save()
ri = ReceiptItem(name='medias', quantity=2) ri.save()
att = Attach(name='foto_de_juanita.jpg', item=ri) att.save() ...
Carga de datos
Estructura del App
my_app/
__init__.py
models.py
fixtures/base.json
factories.py <-------
tests.py
Carga de datosclass ReceiptFactory(factory.Factory): FACTORY_FOR = Receipt
user_id = 1 merchant = "mami"
class ReceiptItemFactory(factory.Factory): FACTORY_FOR = ReceiptItem
name = factory.Sequence(lambda n: 'item-%s' % n) quantity = 1 receipt = ReceiptFactory()
class AttachmentFactory(factory.Factory): FACTORY_FOR = Attachment
name = factory.Sequence(lambda n: 'attachment-%s' % n) item = ReceiptItemFactory()
Carga de datosclass ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): ReceiptFactory() ...
Carga de datos
Carga de datos
Carga de datos
Carga de datos
Factories● Organizado● Flexible <-------
Carga de datosclass ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): # No se crea en base de datos ReceiptFactory.build() ...
Carga de datosclass ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): # Crear muchos objects ReceiptFactory.create_batch(5) ...
Carga de datos
Factories● Organizado● Flexible● Facil de migrar <-------
Carga de datosfrom mongoengine.document import Document, EmbeddedDocument
class Attach(EmbeddedDocument): name = fields.StringField(required=True)
class ReceiptItem(EmbeddedDocument): name = fields.StringField(required=True) quantity = fields.DecimalField(default=Decimal(1)) alert_sent = fields.BooleanField(default=False) attachments = fields.ListField(fields.EmbeddedDocumentField(Attach))
class Receipt(Document): user_id = fields.IntField(required=True) merchant = fields.StringField(required=True)
items = fields.ListField(fields.EmbeddedDocumentField(ReceiptItem))
Carga de datos
class AttachmentFactory(factory.Factory): FACTORY_FOR = Attachment
name = factory.Sequence(lambda n: 'attachment-%s' % n)
class ReceiptItemFactory(factory.Factory): FACTORY_FOR = ReceiptItem
name = factory.Sequence(lambda n: 'item-%s' % n) quantity = 1 Attachments = [AttachmentFactory.build()]
class ReceiptFactory(factory.Factory): FACTORY_FOR = Receipt
user_id = 2 merchant = "Amazon" items = [ReceiptItemFactory.build()]
Carga de datosclass ReceiptTest(TestCase): fixtures = ['base.json']
def setUp(self): ReceiptFactory() ...
MOCKERS
http://labix.org/mocker
http://www.voidspace.org.uk/python/mock/
Mockers
Mockersdef tweet(tokens, body): consumer = oauth.Consumer(TWITTER_KEY, settings.TWITTER_SECRET) token = oauth.Token(tokens.oauth, tokens.oauth_secret) client = oauth.Client(consumer, token)
header, body = client.request("http://api.twitter.com/1/statuses/" "update.json", "POST", body="status=%s" % body)
if header['status'] == '401': return False, ('Twitter account not authorized.' ' Please connect your account again.')
body = json.loads(body) if header['status'] != '200': return False, body.get('error', 'Unknown Twitter error') return True, sbody['id_str']
Mockersfrom mocker import Mocker, ARGS, KWARGS
class TweetTest(TestCase): fixtures = ['base.json']
def setUp(self): mocker = Mocker() mock_client = mocker.replace('oauth.Client') mock_client.request(ARGS, KWARGS) mocker.response({'code': 200, 'text': 'OK', 'description': 'Success!'}) <---- self.mocker = mocker
def test_tweet(self): user = User.objects.get(id=1) self.assertTrue(user.tweets, 0)
with self.mocker: res = tweet(user.tokens,
'esto es un Tweet de prueba')
self.assertEquals(res, True)
Mockers
from mocker import Mocker, ARGS, KWARGS
class TweetTest(TestCase): fixtures = ['base.json']
def setUp(self): mocker = Mocker() mock_client = mocker.replace('oauth.Client') mock_client.request(ARGS, KWARGS) mocker.throw(ConnectionException) <---- self.mocker = mocker
def test_tweet(self): user = User.objects.get(id=1) # Podemos manejar la excepcion? with self.mocker:
tweet(user.tokens, 'esto es un Tweet de prueba')
Buenas y MalasPracticas
Tips
¿Hacer Pruebas antes de lanzar código?
TipsMALMAL
def test_gmail(self): expected_msg = { 'subject': 'SeamlessWeb Order', 'plain': '[image: Seamless Web Logo] ...' # MAL 'html': '\n<table width="640" border=...' # MAL } m = Message.objects.get(pk=1) msg = { 'subject': m.subject, 'html': m.body_mimetype_html, 'plain': m.body_mimetype_plain } msg = strip_forwarding(msg)
self.assertEqual(msg['subject'], expected_msg['subject']) self.assertEqual(msg['plain'], expected_msg['plain']) self.assertEqual(msg['html'], expected_msg['html'])
TipsBIENBIEN
def test_gmail(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') checks = {'subject': u'SeamlessWeb Order'}
msg = strip_forwarding(msg)
# Validar que los valores sean los esperados for key, val in checks.items(): self.assertEqual(val, msg[key]) # Asegurar que estos valores no se encuentren en el email for strip in strips: for m in msg.values(): self.assertNotIn(strip, m)
Tips
No hacer multiples pruebas en una función
TipsMALMAL
def test_emails(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') checks = {'subject': u'SeamlessWeb Order'}
gmail, aol, hotmail = message.objects.all()[:2] msgs = (strip_forwarding(gmail), strip_forwarding(aol), strip_forwarding(hotmail))
for msg in msgs: for key, val in checks.items(): self.assertEqual(val, msg[key]) for strip in strips: for m in msg.values(): self.assertNotIn(strip, m)
TipsBIENBIEN
def test_gmail(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') checks = {'subject': u'SeamlessWeb Order'}
gmail = message.objects.get(id=1) msgs = strip_forwarding(gmail) for key, val in checks.items(): self.assertEqual(val, msg[key]) for strip in strips: for m in msg.values(): self.assertNotIn(strip, m)
def test_aol(self): strips = ('---------- Forwarded message ----------',...
TipsBIENBIEN
def test_gmail(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') checks = {'subject': u'SeamlessWeb Order'}
gmail = message.objects.get(id=1) msgs = strip_forwarding(gmail) for key, val in checks.items(): self.assertEqual(val, msg[key]) for strip in strips: for m in msg.values(): self.assertNotIn(strip, m)
def test_aol(self): strips = ('---------- Forwarded message ----------',...
TipsPruebas por AserciónPruebas por Aserción
def test_gmail_checks(self): checks = {'subject': u'SeamlessWeb Order'}
gmail = message.objects.get(id=1) msgs = strip_forwarding(gmail) for key, val in checks.items(): self.assertEqual(val, msg[key]) <---- Asercion def test_gmail_strips(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') gmail = message.objects.get(id=1) msgs = strip_forwarding(gmail) for strip in strips: for m in msg.values(): self.assertNotIn(strip, m) <---- Asercion
TipsPruebas por AcciónPruebas por Acción
def test_gmail(self): strips = ('---------- Forwarded message ----------', 'Fwd: ', 'From: ', 'Date: ', 'Subject: ', 'To: ') checks = {'subject': u'SeamlessWeb Order'}
gmail = message.objects.get(id=1) msgs = strip_forwarding(gmail) <---- ACCION for key, val in checks.items(): self.assertEqual(val, msg[key]) for strip in strips: for m in msg.values(): self.assertNotIn(strip, m)
TipsProbar todas las ramasProbar todas las ramas
def handle_coolness(self, user): if user.is_cool: do_cool_stuff(user) else: raise NotCoolDudeException
Pruebas de Integración
Pruebas de Integracióndjango client librarydjango client library
def test_view(self): user = User.objects.get(id=1) url = '/login/'
response = self.client.post(url, { 'username': user.username, 'password': 'fakybaby', 'timezone': 'America/Caracas' } self.assertEquals(response.status_code, 200) self.assertEquals(response.cookies_set['logged_name'], \ User.username)
Pruebas de Integracióndjango client librarydjango client library
def test_view(self): user = User.objects.get(id=1) url = '/login/'
response = self.client.post(url, { 'username': user.username, 'password': 'fakybaby', 'timezone': 'America/Caracas' } self.assertEquals(response.status_code, 200) self.assertEquals(response.cookies_set['logged_name'], \ User.username)
Pruebas de IntegraciónWebTest!WebTest!
def test_view(self): user = User.objects.get(id=1) url = '/login/'
form = self.app.get(url).forms['login-form'] form['username'] = user.username form['password'] = 'fakybaby' response = form.submit().follow() self.assertEquals(response.status_code, 200) self.assertEquals(response.cookies_set['logged_name'], \ user.username)
Continuous deployment
http://jenkins-ci.org/
Continuous Deployment
Integrado por django_jenkins
Correr Pruebas unitarias
Verificacion de codigo (Pep8, jslint, etc)
Reportes
Emails
Continuous Deployment
if len(sys.argv) > 1 and sys.argv[1] in ['test', 'jenkins']: # test mongo db MONGO_DBNAME = 'db_test' db = mongoengine.connect(MONGO_DBNAME, username=MONGO_USERNAME, password=MONGO_PASSWORD, host=MONGO_HOST, port=MONGO_PORT, safe=True) db.drop_database(MONGO_DBNAME) # drop entire database
¿Preguntas?
@hazuek
tchenriquez@gmail.com
github.com/hassek
Recommended