{"id":1726,"date":"2015-01-15T10:44:16","date_gmt":"2015-01-15T08:44:16","guid":{"rendered":"https:\/\/inlab.fib.upc.edu\/?p=1726"},"modified":"2015-01-15T10:44:16","modified_gmt":"2015-01-15T08:44:16","slug":"testing-y-mocking-en-python","status":"publish","type":"post","link":"https:\/\/inlab.fib.upc.edu\/es\/uncategorized-ca\/testing-y-mocking-en-python","title":{"rendered":"Testing y Mocking en Python"},"content":{"rendered":"<p>Test unitarios, de integraci&oacute;n, regresi&oacute;n, aceptaci&oacute;n, comportamiento, TDD&#8230; Hay muchos tipos de test y t&eacute;cnicas de testing y muchas veces la&nbsp;frontera&nbsp;entre ellas no es tan clara como quisi&eacute;ramos.<\/p>\n<p><!--more--><\/p>\n<p dir=\"ltr\">Test unitarios, de integraci&oacute;n, regresi&oacute;n, aceptaci&oacute;n, comportamiento, TDD &#8230; Hay muchos tipos de test y t&eacute;cnicas de testing y muchas veces la frontera entre ellas no es tan clara como quisi&eacute;ramos.<\/p>\n<p>En este art&iacute;culo no intentaremos explicar ninguna de estas t&eacute;cnicas o filosof&iacute;as. Iremos a un caso concreto y claro: queremos probar una funcionalidad de nuestro programa que depende de otra sobre la que no tenemos control. Y queremos poder hacerlo de una forma automatizada y reproducible utilizando las herramientas que nos proporciona el lenguaje Python.<\/p>\n<h2 dir=\"ltr\"><span lang=\"es\"><span class=\"hps\">Mucho mejor<\/span> <span class=\"hps\">con<\/span> <span class=\"hps\">un ejemplo<\/span><\/span><\/h2>\n<p dir=\"ltr\">Nuestro ejemplo es muy simple: tenemos una clase que nos permite enviar notificaciones por mail a una lista de usuarios identificados por su nombre de usuario. Esta depender&aacute; de una clase que devuelve informaci&oacute;n sobre los usuarios (en principio a partir de una base de datos, aunque en nuestro ejemplo lo hemos simplificado) y otra clase que permite enviar mails.<\/p>\n<p>&nbsp;<\/p>\n<p dir=\"ltr\"><img decoding=\"async\" alt=\"\" src=\"http:\/\/yuml.me\/diagram\/scruffy\/class\/%5BNotifier%5D-%3E%5BUserRepository%5D,%20%5BNotifier%5D-%3E%5BMailer%5D.png\" \/><\/p>\n<p>&nbsp;<\/p>\n<h3>users.py<\/h3>\n<p>Esta es la clase que deber&iacute;a consultar la base de datos para obtener la informaci&oacute;n, pero como no es el objetivo del art&iacute;culo, haremos que tenga esta funcionalidad tan simple.<\/p>\n<pre class=\"brush:python\">\r\nclass UserRepository:\r\n    def get_user(self,username):\r\n        return {&quot;username:&quot;:username,&quot;mail&quot;:username+&quot;@upc.edu&quot;}<\/pre>\n<h3>\nmailer.py<\/h3>\n<pre class=\"brush:python\">\r\nimport smtplib\r\nfrom email.mime.text import MIMEText\r\n\r\nsender=&quot;reply@fib.upc.edu&quot;\r\n\r\nclass Mailer:\r\n    def send_mail(self,mail,subject,body):\r\n        msg = MIMEText(body)\r\n        msg[&#39;Subject&#39;] = subject\r\n        msg[&#39;From&#39;] = sender\r\n        msg[&#39;To&#39;] = mail\r\n\r\n        s = smtplib.SMTP(&#39;localhost&#39;)\r\n        s.sendmail(sender, [mail], msg.as_string())\r\n        s.quit()<\/pre>\n<h3>notifier.py<\/h3>\n<pre class=\"brush:python\">\r\nfrom users import UserRepository\r\nfrom mailer import Mailer\r\nimport sys\r\n\r\nclass Notifier:\r\n    def __init__(self,user_repository=UserRepository(),mailer=Mailer()):\r\n        self.user_repository=user_repository\r\n        self.mailer=mailer\r\n\r\n    def notify(self,message,usernames):\r\n        for username in usernames:\r\n            user=self.user_repository.get_user(username)\r\n            mail=user[&#39;mail&#39;]\r\n            self.mailer.send_mail(mail,message,message)\r\n\r\nif __name__ == &quot;__main__&quot;:\r\n    notifier=Notifier()\r\n    notifier.notify (sys.argv[1],sys.argv[1:])<\/pre>\n<p dir=\"ltr\"><span id=\"result_box\" lang=\"es\"><span class=\"hps\">Si queremos<\/span> <span class=\"hps\">podemos<\/span> <span class=\"hps\">ejecutar el programa<\/span> <span class=\"hps\">pasando<\/span> <span class=\"hps\">el mensaje<\/span> <span class=\"hps\">y la lista<\/span> <span class=\"hps\">de<\/span> <span class=\"hps\">usernames<\/span> <span class=\"hps\">como par&aacute;metros<\/span><\/span><\/p>\n<pre dir=\"ltr\">\r\n$python notifier.py &quot;Agafa les claus de casa&quot; jaume.moral albert.obiols<\/pre>\n<p dir=\"ltr\">Dado este sistema, como podemos hacer pruebas que realmente se env&iacute;en mails correctamente &#8230; sin que realmente se env&iacute;en? Como podemos simular que el UserRepository nos devuelva un cierto valor para poder probar nuestro c&oacute;digo? Y sobre todo y no menos importante &#8230; como poder hacer esto de una forma f&aacute;cil?<\/p>\n<p>Antes de continuar, debemos decir que ya de entrada hemos tomado una decisi&oacute;n de dise&ntilde;o que nos ayudar&aacute; a escribir pruebas por nuestro c&oacute;digo: hemos creado la clase Notifier de forma que le podamos &quot;inyectar&quot; las dependencias, es decir, pasarle por par&aacute;metro en el momento de la creaci&oacute;n los objetos con los que se deber&aacute; comunicar, en vez de crearlos ella misma. Esto nos facilita la sustituci&oacute;n de estas dependencias durante las pruebas.<\/p>\n<h2 dir=\"ltr\">Pruebas unitarios con &quot;unittest&quot;<\/h2>\n<p dir=\"ltr\">Unittest es un paquete est&aacute;ndar de Python que permite, como su nombre indica, hacer pruebas unitarios. La forma de usarlo es parecido al JUnit de Java, y b&aacute;sicamente consiste en<\/p>\n<ul dir=\"ltr\">\n<li>&nbsp;&nbsp;&nbsp;&nbsp; Hacer una clase que extienda unittest.TestCase<\/li>\n<li>&nbsp;&nbsp;&nbsp;&nbsp; A&ntilde;adir m&eacute;todos que empiecen por &quot;test_&quot; con la implementaci&oacute;n de las pruebas<\/li>\n<li>&nbsp;&nbsp;&nbsp;&nbsp; Hacer aserciones para comprobar que el resultado es el esperado<\/li>\n<\/ul>\n<p dir=\"ltr\">Para empezar, haremos una peque&ntilde;a prueba para testear la implementaci&oacute;n &quot;de mentira&quot; que hemos hecho de nuestro servicio de usuarios. Sabemos que si vamos a buscar el usuario &quot;jaume.moral&quot; el mail que nos deber&iacute;a devolver se &quot;jaume.moral@upc.edu&quot;. Escribiremos un test para codificar este comportamiento.<\/p>\n<pre class=\"brush:python\">\r\nimport unittest\r\nfrom users import UserRepository\r\n\r\nclass NotificationsTestCase(unittest.TestCase):\r\n\r\n    def test_user_repository(self):\r\n        users=UserRepository()\r\n        user=users.get_user(&quot;jaume.moral&quot;)\r\n        self.assertEquals(&quot;jaume.moral@upc.edu&quot;,user[&#39;mail&#39;])\r\n\r\nif __name__ == &#39;__main__&#39;:\r\n        unittest.main()\r\n<\/pre>\n<p dir=\"ltr\">Guardaremos este c&oacute;digo en un fichero &ldquo;test.py&rdquo; y la ejecutamos<\/p>\n<pre>\r\n$ python test.py\r\n.\r\n----------------------------------------------------------------------\r\nRan 1 test in 0.000s\r\nOK<\/pre>\n<p dir=\"ltr\">Este test parece una tonter&iacute;a, pero ahora imaginemos que empezamos a hacer una implementaci&oacute;n que se conecte al servicio de directorio real. Querremos asegurarnos que cuando pedimos el mail nos sigue devolviendo lo que toca. El test nos sirve para asegurarnos de que no rompamos cosas que ya funcionaban.<\/p>\n<h2 dir=\"ltr\">Pruebas sustituyendo funcionalidades con &quot;mock&quot;<\/h2>\n<p dir=\"ltr\">Pasamos ahora a un test m&aacute;s complicado. Queremos testear nuestro notificador que, como sabemos, depende directamente de UserRepository y de Mailer.<\/p>\n<p dir=\"ltr\">Para hacer estas pruebas, tendremos que sustituir los objetos reales por unos de mentira. Queremos que tengan los mismos m&eacute;todos con los mismos par&aacute;metros, pero que no hagan nada. Bueno, realmente lo que queremos es que hagan 2 cosas:<\/p>\n<ul dir=\"ltr\">\n<li>&nbsp;&nbsp;&nbsp;&nbsp; que nos devuelvan los valores que a nosotros nos van bien cuando ejecutamos un test en concreto<\/li>\n<li>&nbsp;&nbsp;&nbsp;&nbsp; que podamos consultar si se ha hecho una cierta llamada a un m&eacute;todo<\/li>\n<\/ul>\n<p dir=\"ltr\">Para hacer esto en Python, tenemos el m&oacute;dulo &quot;mock&quot;, que es parte de la librer&iacute;a est&aacute;ndar desde la version 3.0 y que se puede descargar en https:\/\/pypi.python.org\/pypi\/mock para las versiones anteriores. El repositorio GitHub con los ejemplos hay tambi&eacute;n el paquete.<\/p>\n<h2 dir=\"ltr\">Test con una lista vac&iacute;a de usernames<\/h2>\n<p dir=\"ltr\">Vamos paso a paso: imaginemos primero que queremos enviar una notificaci&oacute;n a una lista vac&iacute;a de usernames. Nuestro UserRepository de mentira no tendr&aacute; que hacer nada y nuestro Mailer tampoco deber&aacute; enviar ning&uacute;n email. As&iacute; pues, nuestro test podr&aacute; ser algo como esto:<\/p>\n<pre class=\"brush:python\">\r\n    def test_0_user(self):\r\n        users=mock.create_autospec(UserRepository)\r\n        mailer=mock.create_autospec(Mailer)\r\n        notifier=Notifier(users,mailer)\r\n        notifier.notify(self.text,[])\r\n        self.assertEquals(mailer.send_mail.call_count,0)<\/pre>\n<p dir=\"ltr\">La funci&oacute;n &quot;create_autospec&quot; lo que hace es crear un objeto con una interfaz igual que la clase que le pasamos, pero sin comportamiento. Esto es lo que llamamos &quot;mock&quot;<\/p>\n<p dir=\"ltr\">Este mock guarda informaci&oacute;n sobre las llamadas que se hacen y por ello podemos consultar cu&aacute;ntas veces se ha llamado al m&eacute;todo send_mail con la sintaxis mailer.send_mail.call_count<\/p>\n<h2 dir=\"ltr\">Test con 1 username<\/h2>\n<p dir=\"ltr\">Imaginemos ahora que env&iacute;en el mail a una lista de un usuario, concretamente el usuario &quot;jaume.moral&quot;<\/p>\n<p>En este caso, nuestro UserRegistry nos deber&aacute; devolver un objeto usuario con el mail de este usuario (jaumem@fib.upc.edu) y tendremos que comprobar que hemos llamado a la funci&oacute;n send_mail con este mail y el texto que hemos pasado el notificador.<\/p>\n<p dir=\"ltr\">Simular este comportamiento es tan simple como asignarle un valor al atributo &quot;return_value&quot; del m&eacute;todo del objeto &quot;mockejat&quot;<\/p>\n<pre class=\"brush:python\">\r\n    def test_1_user(self):\r\n        users=mock.create_autospec(UserRepository)\r\n        users.get_user.return_value={&quot;mail&quot;:&quot;jaumem@fib.upc.edu&quot;}\r\n        mailer=mock.create_autospec(Mailer)\r\n        notifier=Notifier(users,mailer)\r\n        notifier.notify(self.text,[&quot;jaume.moral&quot;])\r\n        mailer.send_mail.assert_called_with(&quot;jaumem@fib.upc.edu&quot;,self.text,self.text)<\/pre>\n<p dir=\"ltr\">Aqu&iacute; la aserci&oacute;n es un poco m&aacute;s compleja: estamos comprobando no s&oacute;lo que hemos llamado el m&eacute;todo send_mail, sino que la hemos llamado con unos par&aacute;metros en concreto.<\/p>\n<p dir=\"ltr\">Como podemos ver, la idea de estos tests es siempre preparar el objeto falso porque nos devuelva unos datos conocidos y\/o comprobar que hemos llamado ciertos m&eacute;todos del objeto falso.<\/p>\n<h2 dir=\"ltr\">Test con 2 usernames<\/h2>\n<p dir=\"ltr\">Muy bien, ahora que ya sabemos que nuestros objetos mockeados nos devuelven el valor que le hemos dicho &#8230; como hacemos que si lo llamamos m&aacute;s de una vez nos devuelvan objetos diferentes? Pues creando un &quot;side effect&quot;. En vez de asignar el &quot;return_value&quot;, que es est&aacute;tico, asignaremos un valor a la propiedad &quot;side_effect&quot; del m&eacute;todo. Podemos asignarle una funci&oacute;n o, en nuestro caso y para simplificar, una lista los elementos de la cual ir&aacute; devolviendo uno por uno cada vez que llamamos el m&eacute;todo.<\/p>\n<p dir=\"ltr\">La idea de cambiar el c&oacute;digo que ejecuta un m&eacute;todo de un objeto en tiempo de ejecuci&oacute;n es lo que se conoce como &quot;monkeypatching&quot;. Es una t&eacute;cnica peligrosa pero que utilizada para testear se convierte en extremadamente &uacute;til, especialmente cuando est&aacute; controlada por una librer&iacute;a espec&iacute;fica como es este caso.<\/p>\n<pre class=\"brush:python\">\r\n    def test_2_users(self):\r\n        users=mock.create_autospec(UserRepository)\r\n        users.get_user.side_effect=[{&quot;mail&quot;:&quot;jaumem@fib.upc.edu&quot;},{&quot;mail&quot;:&quot;anna@fib.upc.edu&quot;}]\r\n        mailer=mock.create_autospec(Mailer)\r\n        notifier=Notifier(users,mailer)\r\n        notifier.notify(self.text,[&quot;jaume.moral&quot;,&quot;anna.casas&quot;])\r\n        mylist=[\r\n            call(&quot;jaumem@fib.upc.edu&quot;,self.text,self.text),\r\n            call(&quot;anna@fib.upc.edu&quot;,self.text,self.text)\r\n        ]\r\n        self.assertEqual(mailer.send_mail.call_args_list,mylist)\r\n<\/pre>\n<p>En este caso la aserci&oacute;n comprueba si hemos hecho una serie de llamadas con sus respectivos par&aacute;metros. Una aserci&oacute;n m&aacute;s simple ser&iacute;a ver que hemos llamado al send_mail 2 veces, pero la opci&oacute;n elegida es mucho m&aacute;s expresiva.<\/p>\n<h2>Conclusiones<\/h2>\n<p>Testear en Python es f&aacute;cil y tenemos formas mucho m&aacute;s simples y potentes que las que nos ofrecen lenguajes m&aacute;s estrictos como Java.<\/p>\n<p>En este art&iacute;culo s&oacute;lo hemos rascado un poco la superficie de lo que nos ofrecen las librer&iacute;as Mock y Unittest. Hay otras formas de utilizarlas como aplicando el decoratorpatch, la clase MagicMock &#8230; pero todo esto lo dejamos para el lector que tenga curiosidad.<\/p>\n<p>Puede encontrar todo el c&oacute;digo de los ejemplos a https:\/\/github.com\/jaumemoral\/python-testing\/<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Test unitarios, de integraci&oacute;n, regresi&oacute;n, aceptaci&oacute;n, comportamiento, TDD&#8230; Hay muchos tipos de test y t&eacute;cnicas de testing y muchas veces la&nbsp;frontera&nbsp;entre ellas no es tan clara como quisi&eacute;ramos.<\/p>\n","protected":false},"author":594,"featured_media":1722,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"experteses":[27],"class_list":["post-1726","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized-ca","experteses-knowledgeyserviceengineering-es"],"acf":[],"_links":{"self":[{"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/posts\/1726","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/users\/594"}],"replies":[{"embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/comments?post=1726"}],"version-history":[{"count":0,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/posts\/1726\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/media\/1722"}],"wp:attachment":[{"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/media?parent=1726"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/categories?post=1726"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/tags?post=1726"},{"taxonomy":"experteses","embeddable":true,"href":"https:\/\/inlab.fib.upc.edu\/es\/wp-json\/wp\/v2\/experteses?post=1726"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}