Diferència entre revisions de la pàgina «Unit Tests en Python»

De Cacauet Wiki
Salta a la navegació Salta a la cerca
(Es crea la pàgina amb « == Introducció == ... <br> == Llibreria unittest en Python == Convé llegir la [https://docs.python.org/2/library/unittest.html documentació oficial de la llibrer...».)
 
 
(Hi ha 7 revisions intermèdies del mateix usuari que no es mostren)
Línia 1: Línia 1:
 +
Utilitzarem la llibreria ''unittest'' de Python per realitzar alguns tests sobre un [[Web Services]], en concret el [[Web Services: exemple Escacs]].
 +
 +
Llegeix aquests articles prèviament.
 +
  
 
== Introducció ==
 
== Introducció ==
...
+
Realitzar uns tests sobre un software és una tasca molt important però molt repetitiva i tediosa, i que pren molt temps. Una bona manera d'estalviar temps de desenvolupament és automatitzar el procés de testeig amb un ''script'' que ho faci per nosaltres.
 +
 
 +
També resulta crucial cada cop que es fa alguna modificació d'algun software que està en producció i que abans d'actualitzar-ho hem d'assegurar la seva funcionalitat a fons.
 +
 
 +
Bàsicament es tracta de provar certes funcions crítiques donant una entrada simulada, i comprovar després la resposta si és l'esperada amb les [https://docs.python.org/2/library/unittest.html#assert-methods mètodes ASSERT].
 +
 
 +
En aquest article primer veurem com testejar una funció simple i després ho complicarem una mica més amb un Web Service que cal engegar abans de fer els tests.
 +
 
 +
Referències:
 +
* [https://docs.python.org/2/library/unittest.html Python unittest module]
 +
* [https://docs.python.org/2/library/unittest.html#assert-methods assert methods] pels tests
 +
* [http://stackoverflow.com/questions/14260101/unittesting-cherrypy-webapp Fent unit testing amb CherryPy]
 +
* [https://docs.python.org/2/library/httplib.html Crides HTTP en Python (httplib)]
 +
<br>
 +
 
 +
== Test bàsic ==
 +
Utilitzarem la llibereia [https://docs.python.org/2/library/unittest.html Python unittest module]. Sol fer-se un arxiu a part del nostre mòdul a comprovar, on crearem els tests en una classe derivada de '''TestCase'''.
 +
 
 +
Si agafem l'exemple dels escacs, podriem comprovar que la funció '''crea_partida''' genera una partida correctament amb totes les peces. En aquest exemple comprovem només 1 fitxa, el peó blanc de la 1a columna.
 +
 
 +
<syntaxhighlight lang="python">
 +
# arixu escacs_tests.py
 +
import unittest
 +
import cherrypy
 +
import escacs
 +
import httplib, urllib
 +
import json
 +
 
 +
class TestsInterns(unittest.TestCase):
 +
"""
 +
Test de la funcio interna "crea_partida"
 +
"""
 +
def test_crea_partida(self):
 +
# Creem instancia del WS
 +
e = escacs.Escacs()
 +
partida = e.crea_partida()
 +
self.assertNotEqual( partida, None )
 +
fitxa1 = {"color":"b","figura":"peo","fila":2,"col":1}
 +
# comprovem que la fitxa estigui a l'array de figures
 +
self.assertIn( fitxa1, partida["figures"] )
 +
 
 +
if __name__ == "__main__":
 +
unittest.main()
 +
</syntaxhighlight>
 +
 
 +
Fixeu-vos en diverses coses:
 +
* Heredem de la classe TestCase
 +
* Cada mètode que realitzem serà un test independent. S'executaran tots els mètodes que fem (en aqeust cas només tenim 1).
 +
* En aquest test hi ha 2 comprovacions (asserts):
 +
** <code>self.assertNotEqual(partida,None)</code> : comprovem que ens retorni alguna cosa (que no sigui ''None'')
 +
** <code>self.assertIn( fitxa1, partida["figures"] )</code> : comprovem que la ''fitxa1'' estigui dins de l'array de figures. Una partida acabada d'iniciar hauria de tenir un peó blanc a la fila 2, columna 1.
 +
* Òbviament caldria comprovar la resta de figures.
 +
 
 +
<big class="exercici">OJU ! les funcions que s'executaran com a test han de començar per la paraula '''test_'''. Si no, s'interpretaran com una funció interna (que sí es pot cridar des de qualsevol test).</big>
 +
 
 +
Ara, si executem obtindrem:
 +
<pre>
 +
(env)enric@tao:~/dev/cherry$ python escacs_tests.py
 +
.
 +
----------------------------------------------------------------------
 +
Ran 1 test in 0.003s
 +
 
 +
OK
 +
(env)enric@tao:~/dev/cherry$
 +
</pre>
 
<br>
 
<br>
  
 +
== Testejant serveis web ==
 +
Tenim entre mans un servei web, i hem pogut comprovar una funció del nostre servidor, però si ens cal comprovar les requests/responses d'un sistema REST, la cosa se'ns complica.
 +
 +
Abans de fer una crida al servei (amb URLLIB) cal engengar el server. Això ho realitzem a la funció '''setUp()'''. Al acabar cada test s'executarà també '''tearDown()''' , on posarem el codi per apagar el servidor.
 +
 +
Al codi d'abans pots afegir aquest:
 +
 +
<syntaxhighlight lang="python">
 +
class TestsExterns(unittest.TestCase):
 +
"""
 +
Test extern (curl) amb creacio de web service
 +
"""
 +
def setUp(self):
 +
'''setUp: es crida abans d'efectuar cada test
 +
arranquem el WS
 +
'''
 +
# {environment:embedded} evita que el servidor printi missatges (silent)
 +
cherrypy.config.update({'environment':'embedded'})
 +
cherrypy.tree.mount( escacs.Escacs() , '/' )
 +
# amb start() no es bloqueja el thread i podem seguir fent els tests
 +
cherrypy.engine.start()
 +
def tearDown(self):
 +
'''tearDown: es crida al acabar cada test
 +
parem el WS
 +
'''
 +
cherrypy.engine.exit()
 +
# test1: comprovem amb crida URL
 +
def test_root(self):
 +
conn = httplib.HTTPConnection('localhost', 8080, timeout=2)
 +
conn.request( "GET", "/" )
 +
resp = conn.getresponse()
 +
data = resp.read()
 +
self.assertEqual( data, "SUPER ESCACS !!!!")
 +
# test2: comprovem amb crida URL
 +
def test_login(self):
 +
params = json.dumps( {'email':'[email protected]',"nick":"manolo"} )
 +
headers = {"Content-type":"application/json"}
 +
conn = httplib.HTTPConnection('localhost', 8080, timeout=2)
 +
conn.request( "POST", "/login", params, headers )
 +
resp = conn.getresponse()
 +
data = resp.read()
 +
#print "RESP=",resp.reason
 +
self.assertEqual(resp.status, 200) # status = 200 OK
 +
#print "BODY=",data
 +
jsondata = json.loads( data )
 +
self.assertTrue( jsondata["status"] )
 +
</syntaxhighlight>
 +
 +
Si l'executem podrem comprovar que tenim 2 tests (test_root i test_login) + 1 de la classe TestsInterns (total = 3 tests).
 +
 +
Fixa't en què:
 +
* Per cada test s'executa abans la funció setUp()
 +
* A l'acabar cada test s'executa la funció tearDown()
 +
* Creem una HTTPConnection amb la llibreria httplib
 +
* Fem la request amb paràmetres i headers
 +
* En test_root() només comprovem que el ''body'' de la response sigui el string "SUPER ESCACS !!!!"
 +
* En test_login() enviem les dades dels ''params'' en JSON (json.dumps)
 +
* ...i les rebem també en JSON (json.loads)
 +
* En el test_login() comprovem:
 +
** <code>self.assertEqual(resp.status, 200)</code> : que el ''status'' de la response sigui OK (200)
 +
** <code>self.assertTrue( jsondata["status"] )</code> : que el ''status'' de les dades retornades en JSON és correcte (true)
 +
 +
Finalment, comprova que el test falla, per exemple, si no enviem les dades correctes. Per exemple, treu el ''nick'' de les dades dels ''params'', i torna a córrer el test. T'hauria de sortir quelcom similar a això:
 +
 +
<pre>
 +
(env)enric@tao:~/dev/cherry$ python escacs_tests.py
 +
ERROR in /login
 +
F..
 +
======================================================================
 +
FAIL: test_login (__main__.TestsExterns)
 +
----------------------------------------------------------------------
 +
Traceback (most recent call last):
 +
  File "escacs_tests.py", line 61, in test_login
 +
    self.assertTrue( jsondata["status"] )
 +
AssertionError: False is not true
 +
 +
----------------------------------------------------------------------
 +
Ran 3 tests in 5.248s
  
== Llibreria unittest en Python ==
+
FAILED (failures=1)
Convé llegir la [https://docs.python.org/2/library/unittest.html documentació oficial de la llibreria unittest de Python].
+
(env)enric@tao:~/dev/cherry$
 +
</pre>
  
Utilitzarem la llibreria per realitzar alguns tests sobre un [[Web Services]], en concret el [[Web Services: exemple Escacs]].
+
== Exercicis ==
 +
Realitza els tests adequats per les funcions de la API:
 +
* juga : hauria d'iniciar una partida nova
 +
* en_espera
 +
* mou : testeja quan una figura es menja una altra

Revisió de 18:54, 11 maig 2015

Utilitzarem la llibreria unittest de Python per realitzar alguns tests sobre un Web Services, en concret el Web Services: exemple Escacs.

Llegeix aquests articles prèviament.


Introducció[modifica]

Realitzar uns tests sobre un software és una tasca molt important però molt repetitiva i tediosa, i que pren molt temps. Una bona manera d'estalviar temps de desenvolupament és automatitzar el procés de testeig amb un script que ho faci per nosaltres.

També resulta crucial cada cop que es fa alguna modificació d'algun software que està en producció i que abans d'actualitzar-ho hem d'assegurar la seva funcionalitat a fons.

Bàsicament es tracta de provar certes funcions crítiques donant una entrada simulada, i comprovar després la resposta si és l'esperada amb les mètodes ASSERT.

En aquest article primer veurem com testejar una funció simple i després ho complicarem una mica més amb un Web Service que cal engegar abans de fer els tests.

Referències:


Test bàsic[modifica]

Utilitzarem la llibereia Python unittest module. Sol fer-se un arxiu a part del nostre mòdul a comprovar, on crearem els tests en una classe derivada de TestCase.

Si agafem l'exemple dels escacs, podriem comprovar que la funció crea_partida genera una partida correctament amb totes les peces. En aquest exemple comprovem només 1 fitxa, el peó blanc de la 1a columna.

# arixu escacs_tests.py
import unittest
import cherrypy
import escacs
import httplib, urllib
import json

class TestsInterns(unittest.TestCase):
	"""
	Test de la funcio interna "crea_partida"
	"""
	def test_crea_partida(self):
		# Creem instancia del WS
		e = escacs.Escacs()
		partida = e.crea_partida()
		self.assertNotEqual( partida, None )
		fitxa1 = {"color":"b","figura":"peo","fila":2,"col":1}
		# comprovem que la fitxa estigui a l'array de figures
		self.assertIn( fitxa1, partida["figures"] )

if __name__ == "__main__":
	unittest.main()

Fixeu-vos en diverses coses:

  • Heredem de la classe TestCase
  • Cada mètode que realitzem serà un test independent. S'executaran tots els mètodes que fem (en aqeust cas només tenim 1).
  • En aquest test hi ha 2 comprovacions (asserts):
    • self.assertNotEqual(partida,None) : comprovem que ens retorni alguna cosa (que no sigui None)
    • self.assertIn( fitxa1, partida["figures"] ) : comprovem que la fitxa1 estigui dins de l'array de figures. Una partida acabada d'iniciar hauria de tenir un peó blanc a la fila 2, columna 1.
  • Òbviament caldria comprovar la resta de figures.

OJU ! les funcions que s'executaran com a test han de començar per la paraula test_. Si no, s'interpretaran com una funció interna (que sí es pot cridar des de qualsevol test).

Ara, si executem obtindrem:

(env)enric@tao:~/dev/cherry$ python escacs_tests.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.003s

OK
(env)enric@tao:~/dev/cherry$ 


Testejant serveis web[modifica]

Tenim entre mans un servei web, i hem pogut comprovar una funció del nostre servidor, però si ens cal comprovar les requests/responses d'un sistema REST, la cosa se'ns complica.

Abans de fer una crida al servei (amb URLLIB) cal engengar el server. Això ho realitzem a la funció setUp(). Al acabar cada test s'executarà també tearDown() , on posarem el codi per apagar el servidor.

Al codi d'abans pots afegir aquest:

class TestsExterns(unittest.TestCase):
	"""
	Test extern (curl) amb creacio de web service
	"""
	def setUp(self):
		'''setUp: es crida abans d'efectuar cada test
				arranquem el WS
		'''
		# {environment:embedded} evita que el servidor printi missatges (silent)
		cherrypy.config.update({'environment':'embedded'})
		cherrypy.tree.mount( escacs.Escacs() , '/' )
		# amb start() no es bloqueja el thread i podem seguir fent els tests
		cherrypy.engine.start()
	def tearDown(self):
		'''tearDown: es crida al acabar cada test
				parem el WS
		'''
		cherrypy.engine.exit()
	# test1: comprovem amb crida URL
	def test_root(self):
		conn = httplib.HTTPConnection('localhost', 8080, timeout=2)
		conn.request( "GET", "/" )
		resp = conn.getresponse()
		data = resp.read()
		self.assertEqual( data, "SUPER ESCACS !!!!")
	# test2: comprovem amb crida URL
	def test_login(self):
		params = json.dumps( {'email':'[email protected]',"nick":"manolo"} )
		headers = {"Content-type":"application/json"}
		conn = httplib.HTTPConnection('localhost', 8080, timeout=2)
		conn.request( "POST", "/login", params, headers )
		resp = conn.getresponse()
		data = resp.read()
		#print "RESP=",resp.reason
		self.assertEqual(resp.status, 200) # status = 200 OK
		#print "BODY=",data
		jsondata = json.loads( data )
		self.assertTrue( jsondata["status"] )

Si l'executem podrem comprovar que tenim 2 tests (test_root i test_login) + 1 de la classe TestsInterns (total = 3 tests).

Fixa't en què:

  • Per cada test s'executa abans la funció setUp()
  • A l'acabar cada test s'executa la funció tearDown()
  • Creem una HTTPConnection amb la llibreria httplib
  • Fem la request amb paràmetres i headers
  • En test_root() només comprovem que el body de la response sigui el string "SUPER ESCACS !!!!"
  • En test_login() enviem les dades dels params en JSON (json.dumps)
  • ...i les rebem també en JSON (json.loads)
  • En el test_login() comprovem:
    • self.assertEqual(resp.status, 200) : que el status de la response sigui OK (200)
    • self.assertTrue( jsondata["status"] ) : que el status de les dades retornades en JSON és correcte (true)

Finalment, comprova que el test falla, per exemple, si no enviem les dades correctes. Per exemple, treu el nick de les dades dels params, i torna a córrer el test. T'hauria de sortir quelcom similar a això:

(env)enric@tao:~/dev/cherry$ python escacs_tests.py 
ERROR in /login
F..
======================================================================
FAIL: test_login (__main__.TestsExterns)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "escacs_tests.py", line 61, in test_login
    self.assertTrue( jsondata["status"] )
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 5.248s

FAILED (failures=1)
(env)enric@tao:~/dev/cherry$ 

Exercicis[modifica]

Realitza els tests adequats per les funcions de la API:

  • juga : hauria d'iniciar una partida nova
  • en_espera
  • mou : testeja quan una figura es menja una altra