Categorías
Python

Su guía para la función de impresión del pitón

 

Tabla de Contenidos

    Prueba

  • URL Asignaciones
  • Testing Ver funciones
  • Prueba TemplatesA nota sobre ImportsBack a plantillas
  • una nota sobre las importaciones
  • Volver a plantillas
  • plantilla Django Prueba Contexto
  • Conclusión
  • Una nota Las importaciones en
  • Volver a Plantillas

Bienvenido!

los artículos de esta serie:

  • Parte 1: Pruebas asíncrono con Django y PyVows
  • Parte 2: Unidad de Pruebas con pyVows y Django (artículo actual)
  • Parte 3: Integración de las pruebas con pyVows y Django

repo: TDD-Django

En el último artículo, hablamos acerca del uso de los pyVows increíbles y selenio para acelerar las pruebas de interfaz gráfica de usuario. Pero las pruebas de la GUI no son toda la historia. Prueba de la unidad es igualmente – posiblemente, aún más – importante. Y podemos seguir utilizando pyVows para nuestras pruebas unitarias también. No es necesario utilizar una herramienta diferente. Aunque, no verá una gran mejora de la velocidad de la naturaleza asíncrona de pyVows con algunas pruebas, con cientos de pruebas se quiere. Especialmente si sus pruebas hacen una buena cantidad de E / S.

Así que vamos a echar un vistazo a utilizar pyVows para las pruebas unitarias. Imaginemos que queríamos crear algunas funciones para administrar cuentas de usuario, probablemente a partir de un formulario de acceso. Después de activar su virtualenv, lo primero que vamos a hacer es crear una aplicación llamada cuentas (en estos días la gente django decir que todo debe estar en una aplicación y quién soy yo para discutir). Esto se hace muy fácilmente con este comando:

$ python manage.py startapp accounts

que creará un nuevo directorio llamado cuenta con un models.py, tests.py, andviews.py archivos:

├── README.mdown
├── requirements.txt
└── tddapp
├── accounts
│   ├── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── tddapp
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── uitests.py

Para nuestro ejemplo, el primero que queremos es empezar a construir una página de inicio de sesión accede desde / login. Por lo que podemos añadir una entrada en urls.py para asignar / entrada a nuestra función de vista:

urlpatterns = patterns('',
url(r'^login/$', 'accounts.views.login',name='login'),

Asegúrese de añadir cuentas a su INSTALLED_APPS dentro del archivo settings.py.

Además, el código utilizado para este artículo se puede encontrar en el repositorio de Github asociado.

Prueba

URL Asignaciones

Mientras que la entrada anterior en urls.py es bastante sencillo, su todavía una buena idea para escribir una prueba unitaria para asegurarse de que tenemos todo conectado correctamente. También en este caso, una prueba es bueno tener para asegurarse de que alguien no accidentalmente clobber esta url regla en algún momento en el futuro. . (Tal vez mediante la inclusión de otra regla url que sobrescribe éste

Así que puede modificar las cuentas / tests.py archivo a tener este aspecto:

from pyvows import Vows, expect
from django_pyvows.context import DjangoHTTPContext

@Vows.batch
class LoginPageVows(DjangoHTTPContext):

def get_settings(self):
return "tddapp.settings"

def topic(self):
self.start_server()
return self.url("^login/$")

def url_should_be_mapped_to_login_view(self, topic):
from accounts.views import login
expect(topic).to_match_view(login)

Aquí estamos creando el mismo tipo de caso de prueba como lo hicimos en el firstarticle. las cosas importantes a tener en cuenta son:

la carne de la prueba está en el url_should_be_mapped_to_login_view functionwhich utiliza la afirmación to_match_view que se construye en django-pyvows.The afirmación to_match_view simplemente asegura que Django llamará al specifiedfunction en este caso accounts.views.. inicio de sesión para la URL especificada – que es lo que devuelve como el tema de nuestras pruebas:

return self.url("^login/$")

from django.http import HttpResponse

def login(request):
pass

con el fin de ejecutar la prueba correctamente es posible que tenga que proporcionar una variable de entorno PYTHONPATH pyVows por lo que no llega confundido acerca de dónde ubicar los módulos para ello debemos pyVows desde el directorio raíz del proyecto siempre se ejecutan con una línea de comando como el siguiente:.

$ env PYTHONPATH=$$PYTHONPATH:tddapp/ pyvows tddapp/accountsests.py

este basica LLY dice añadir el directorio tddapp / app en nuestro pathand pitón existente pyvows A continuación, ejecute para las pruebas en tddapp / cuentas / tests.py. Este shouldavoid cualquier problema con no ser capaz de encontrar el archivo settings.py y ensureall trabajo importaciones como se esperaba.

Testing Ver funciones

ahora que sabemos que nuestra urls.py es el mapeo de la opinión correcta la siguiente logicalstep es poner a prueba el fin de asegurarse de que se hace lo que se espera. Así actualizar views.py con una muy simple función de vista de inicio de sesión como esto:

from django.http import HttpResponse

def login(request):
return HttpResponse("this is a login")

Permite añadir las siguientes pruebas:

class LoginPageView(DjangoHTTPContext):

def topic(self):
return login(self.request())

def should_return_valid_HTTP_Response(self,topic):
expect(topic).to_be_http_response()

def should_return_login_page(self, topic):
expect(topic).to_have_contents_of("this is a login")

Tenga en cuenta que para nuestro tema que estamos llamando a la función de vista de acceso directa y que pasan en self.request (). self.request () es una función de ayuda proporcionada por DjangoHTTPContext que crea y devuelve una nueva HTTPRequest y por lo tanto hace que sea sencillo para probar Django Vistas.

Una vez que tengamos el valor de retorno de nuestra vista de inicio de sesión () Función probamos dos características diferentes:

más allá para demostrar que podemos ejecutar estas pruebas en paralelo podríamos refactorizar las pruebas un poco para tener este aspecto:

from accounts.views import login
from pyvows import Vows, expect
from django_pyvows.context import DjangoHTTPContext

@Vows.batch
class LoginPageVows(DjangoHTTPContext):

def get_settings(self):
return "tddapp.settings"

def topic(self):
self.start_server()

class LoginPageURL(DjangoHTTPContext):

def topic(self):
return self.url("^login/$")

def url_should_be_mapped_to_login_view(self, topic):
expect(topic).to_match_view(login)

class LoginPageView(DjangoHTTPContext):

def topic(self):
return login(self.request())

def should_return_valid_HTTP_Response(self,topic):
expect(topic).to_be_http_response()

def should_return_login_page(self, topic):
expect(topic).to_have_contents_of("this is a login")

Tenga en cuenta que ahora una instancia del servidor de Django en nuestros LoginPageVows clase de prueba más externa y luego las dos clases hijas a prueba la URL LoginPageURL y la vista LoginPageView. Si recuerda el artículo anterior, las clases de hermanos se ejecutan en paralelo. Por lo que las pruebas de URL y las pruebas de Vista se ejecutarán al mismo tiempo con la estructura de clases / prueba anterior.

Prueba plantillas

Podría decirse que la función de vista anterior es demasiado simple para ser utilizado nunca en una aplicación real. Más a menudo que no en una aplicación real que va a querer utilizar algún tipo de plantilla para mostrar su punto de vista dinámico. Bueno, no miedo, podemos probar que con Django-pyvows también.

para ilustrar que primero vamos a crear una plantilla para nuestra opinión de inicio de sesión. Vamos a crear el archivo de tddapp / cuentas / templates / login.html. El código podría ser:



Login<itle><br /> </head><br /> <body></p> <h1>Please Login</h1> <form method="POST" action="#" id="login-form"> <p>Username: <input type="text" id="username" /></p> <p>Password:<input type="password" id="password" /></p> <p> <input type="submit" id="login" value="Login"><br /> </form> <p> </body><br /> </html><br /> </code> </p> <p> Una vez creada la plantilla, simplemente cambiamos la función de vista de inicio de sesión para devolver la plantilla de este modo: </p> <p> <code>from django.shortcuts import render</p> <p>def login(request):<br /> #return HttpResponse("this is a login")<br /> return render(request, 'login.html')<br /> </code> </p> <p> el render función indica a la función de inicio de sesión para devolver la plantilla login.html envuelto en una HTTPResponse objeto. Así que ahora, en términos de pruebas, todo lo que tenemos que hacer es cambiar nuestra should_return_login_page comprobar para ver si se ha devuelto la plantilla login.html. Esto se puede hacer con los siguientes cambios: </p> <p> <code>def should_return_login_page(self, topic):<br /> from django.template.loader import render_to_string<br /> loginTemplate = render_to_string("login.html")<br /> expect(topic.content.decode()).to_equal(loginTemplate)<br /> </code> </p> <p> La primera línea de la función should_return_login_page ahora importa la función render_to_string que tiene una plantilla como un argumento (y un contexto opcional) y devuelve el HTML generado como una cadena. Esto hará que sea muy fácil de asegurarse de que estamos volviendo la plantilla login.html. Hacemos que mediante la generación de la plantilla mediante la función render_to_string (segunda línea en la función de should_return_login_page arriba). A continuación se compara el HTTPResponse regresamos a la loginTemplate generada (en la tercera línea) y su fuera de las carreras. </p> <h3> Una nota sobre las importaciones </h3> <p> Aviso en la función anterior se puso la importación en la función. Algunas personas prefieren poner todas las importaciones en la parte superior del archivo por lo que son fáciles de ver y todo se ejecutan a la vez, tan pronto como se carga el módulo. Todo esto está muy bien, pero cuando la unidad de pruebas de Django con pyVows uno debe ser consciente de cómo funciona la importación. </p> <p> En Python al importar un módulo o clase, o pitón función será a continuación, ejecutar todas las importaciones en el módulo que contiene. Así, por ejemplo, la importación de accounts.views de inicio de sesión de importación cargará el módulo accounts.views e importar todo lo que se hace referencia allí. Que en este caso es ahora de django.shortcuts importar render. Cuando eso sucede Django querrá la configuración del servidor (es decir settings.py) para ser ejecutado ya, pero en pyVows eso no sucede hasta que llamamos start_server. ¿Qué sucede después, por lo que se produce un error. </p> <p> Una forma de evitar este problema es no importar cualquier cosa que se basa en Django hasta después de que se ejecuta start_server. ¿Qué es lo que hicimos anteriormente en el should_return_login_page. Sin embargo esto puede ser difícil, especialmente si usted tiene un montón de pruebas (que es probable que) que dependerá de Django de alguna manera. </p> <p> tenga ningún miedo. django-pyVows ofrece una solución. La función DjangoHTTPContext.start_environment. Se pasa en el camino a su archivo de configuración para esa función y simplemente llamar a esa función antes de hacer cualquiera de las importaciones. La función start_environment obtendrá Django todo configurado y listo para ir por lo que no obtendrá extraños temas de importación más tarde. Aquí es un vistazo a cómo lo haríamos para nuestro archivo tests.py: </p> <p> <code>from pyvows import Vows, expect<br /> from django_pyvows.context import DjangoHTTPContext</p> <p>DjangoHTTPContext.start_environment("tddapp.settings")</p> <p>from accounts.views import login<br /> from django.template.loader import render_to_string</p> <p>@Vows.batch<br /> class LoginPageVows(DjangoHTTPContext):</p> <p> def topic(self):<br /> self.start_server()</p> <p> class LoginPageURL(DjangoHTTPContext):</p> <p> def topic(self):<br /> return self.url("^login/$")</p> <p> def url_should_be_mapped_to_login_view(self, topic):<br /> expect(topic).to_match_view(login)</p> <p> class LoginPageView(DjangoHTTPContext):</p> <p> def topic(self):<br /> return login(self.request())</p> <p> def should_return_valid_HTTP_Response(self,topic):<br /> expect(topic).to_be_http_response()</p> <p> def should_return_login_page(self, topic):<br /> loginTemplate = render_to_string("login.html")<br /> expect(topic.content.decode()).to_equal(loginTemplate)<br /> </code> </p> <p> también que ya no necesita funcionar los get_settings como ya hemos realizado la configuración, llamando a la función DjangoHTTPContext.start_environment. </p> <p> Vaya por delante y ejecute las pruebas: </p> <p> <code>$ env PYTHONPATH=$$PYTHONPATH:tddapp/ pyvows tddapp/accountsests.py<br /> </code> </p> <p> Todos ellos debe pasar. </p> <h3> Volver a Plantillas </h3> <p> Con las pruebas anteriores que ahora puede probar que nuestro punto de vista está llamando la plantilla adecuada y devolver el código HTML que se encuentra en nuestra plantilla. Pero ¿qué hay de probar la propia plantilla? Vamos a asegurarnos de que nuestra plantilla tiene un formulario de acceso con un campo de nombre de usuario y contraseña. </p> <p> Django-pyvows tiene CSSSelect construido en que es una biblioteca que permite a consulta HTML usando selectores CSS de la misma manera que lo haría si estuviera usando jQuery. Esto hace que las plantillas de prueba de una sincronización. </p> <p> En primer lugar vamos a crear un contexto de pruebas usando Django pyvows clase de plantilla. </p> <p> <code>class LoginPageTemplate(DjangoHTTPContext):</p> <p> def topic(self):<br /> return self.template("login.html", {})<br /> </code> </p> <p> función __init__ La plantilla tiene dos argumentos, el nombre de la plantilla (en este caso login.html) y el contexto no se confunda, estamos hablando de un contexto de plantillas de Django aquí, no una prueba pyvows contexto en el cual este caso está vacía, pero vamos a volver a contexto de la plantilla después. </p> <p> Ahora podemos escribir las pruebas con nuestros selectores CSS para verificar lo que está en la plantilla. He aquí algunos ejemplos: </p> <p> <code>def should_have_login_form(self, topic):<br /> expect(topic).to_contain("#login-form")</p> <p>def should_have_username_field(self,topic):<br /> expect(topic).to_contain("#username")</p> <p>def should_use_password_field(self,topic):<br /> expect(topic).to_contain("#password[type='password']")<br /> </code> </p> <p> Todo lo que tenemos que hacer es usar la afirmación to_contain y pasan de un selector CSS. a continuación, si no se encuentra el selector de nuestra prueba pasará. Podemos también explícitamente la prueba de que un elemento particular no se encuentra: </p> <p> <code>def should_not_have_settings_link(self,topic):<br /> expect(topic).Not.to_contain("a#settings")<br /> </code> </p> <h2> plantilla Django Prueba Contexto </h2> <p> Para páginas web dinámicas nuestras plantillas a menudo generan HTML dinámico mediante la lectura de las variables del contexto de la plantilla. Como dejó un ejemplo a suponer que queremos los usuarios bienvenida a nuestra página de inicio de sesión en base a la última página del visitada (es decir, Facebook, Google, etc.). Podríamos actualizar la plantilla de la siguiente manera: </p> <p> <code><html><br /> <head><br /> <title>Login<itle><br /> </head><br /> <body></p> <h1>Welcome {{ referrer }} user. Please login.</h1> <form method="POST" action="#" id="login-form"> <p>Username: <input type="text" id="username"/></p> <p>Password:<input type="password" id="password"/></p> <p> <input type="submit" id="login" value="Login"/><br /> </form> <p> </body><br /> </html><br /> </code> </p> <p> Luego, cuando creamos nuestra plantilla en la clase de prueba LoginPageTemplate que puede pasar en el contexto apropiado y verificar que se sustituye correctamente. Aquí está la clase de prueba de modificación: </p> <p> <code>class LoginPageTemplate(DjangoHTTPContext):</p> <p> def topic(self):<br /> return self.template("login.html", {"referrer":"Facebook"})</p> <p> def should_have_login_form(self, topic):<br /> expect(topic).to_contain("#login-form")</p> <p> ... removed some tests for brevity...</p> <p> class WelcomeMessage(DjangoHTTPContext):</p> <p> def topic(self, loginTemplate):<br /> return loginTemplate.get_text('h1')</p> <p> def should_welcome_user_from_referrer(self, topic):<br /> expect(topic).to_equal("Welcome Facebook user. Please login.")<br /> </code> </p> <p> Un par de cosas que son importantes aquí: </p> <p> En primer lugar, nuestra clase WelcomeMessage, como un niño de nuestra clase LoginPageTemplate tiene acceso al tema definido en LoginPageTemplate. Es por ello que podemos pasar en un argumento loginTemplate a nuestra función tema en la clase WelcomeMessage como se ilustra aquí: </p> <p> <code>def topic(self, loginTemplate):<br /> return loginTemplate.get_text('h1')<br /> </code> </p> <p> En segundo lugar, llamar a la función de un get_text django-pyvows.template se encuentra el elemento mediante un selector CSS y devolver el texto . A partir de ahí su sencillo añadir una afirmación como to_equal o to_be_like para asegurar el elemento tiene el texto apropiado. Esta técnica debe servir para probar la mayoría de los cambios dinámicos en plantillas de Django. </p> <p> Ejecutar las pruebas de nuevo: </p> <p> <code>$ env PYTHONPATH=$$PYTHONPATH:tddapp/ pyvows tddapp/accountsests.py<br /> </code> </p> <p> Todos ellos deben pasar. 🙂 </p> <h2> Conclusión </h2> <p> En este artículo hemos cubierto muchas de las características útiles que Django-pyVows ofrece para ayudar con la unidad de pruebas de Django Vistas de plantillas y asignaciones de URL. Lo que es más, podemos seguir utilizando el mismo marco que hicimos para nuestra prueba GUI y continuar recibiendo los beneficios de las pruebas en paralelo y aprovechar los numerosos accesos directos que la biblioteca nos proporciona. </p> <p> Asegúrese de agarrar el código desde el repositorio. </p> <p> que me haga saber lo que ustedes piensan. Nadie se usa Django pyvows para las pruebas unitarias? Por favor comparta sus pensamientos en los comentarios a continuación. </p> </div><!-- .entry-content --> </div><!-- .post-inner --> <div class="section-inner"> </div><!-- .section-inner --> <nav class="pagination-single section-inner" aria-label="Entrada" role="navigation"> <hr class="styled-separator is-style-wide" aria-hidden="true" /> <div class="pagination-single-inner"> <a class="previous-post" href="https://eltecnofilo.es/sintaxis-no-valida-en-python-razones-comunes-para-syntaxerror/"> <span class="arrow" aria-hidden="true">←</span> <span class="title"><span class="title-inner">Sintaxis no válida en Python: Razones comunes para SyntaxError</span></span> </a> <a class="next-post" href="https://eltecnofilo.es/frasco-por-ejemplo-la-implementacion-de-una-cola-de-tareas-redis/"> <span class="arrow" aria-hidden="true">→</span> <span class="title"><span class="title-inner">Frasco por Ejemplo – La implementación de una cola de tareas Redis</span></span> </a> </div><!-- .pagination-single-inner --> <hr class="styled-separator is-style-wide" aria-hidden="true" /> </nav><!-- .pagination-single --> <div class="comments-wrapper section-inner"> <div id="respond" class="comment-respond"> <h2 id="reply-title" class="comment-reply-title">Deja un comentario <small><a rel="nofollow" id="cancel-comment-reply-link" href="/su-guia-para-la-funcion-de-impresion-del-piton/#respond" style="display:none;">Cancelar la respuesta</a></small></h2><form action="https://eltecnofilo.es/wp-comments-post.php" method="post" id="commentform" class="section-inner thin max-percentage" novalidate><p class="comment-notes"><span id="email-notes">Tu dirección de correo electrónico no será publicada.</span> Los campos obligatorios están marcados con <span class="required">*</span></p><p class="comment-form-comment"><label for="comment">Comentario</label> <textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required="required"></textarea></p><p class="comment-form-author"><label for="author">Nombre <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" required='required' /></p> <p class="comment-form-email"><label for="email">Correo electrónico <span class="required">*</span></label> <input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" required='required' /></p> <p class="comment-form-url"><label for="url">Web</label> <input id="url" name="url" type="url" value="" size="30" maxlength="200" /></p> <p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes" /> <label for="wp-comment-cookies-consent">Guardar mi nombre, correo electrónico y sitio web en este navegador para la próxima vez que haga un comentario.</label></p> <p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Publicar el comentario" /> <input type='hidden' name='comment_post_ID' value='6971' id='comment_post_ID' /> <input type='hidden' name='comment_parent' id='comment_parent' value='0' /> </p></form> </div><!-- #respond --> </div><!-- .comments-wrapper --> </article><!-- .post --> </main><!-- #site-content --> <div class="footer-nav-widgets-wrapper header-footer-group"> <div class="footer-inner section-inner"> <aside class="footer-widgets-outer-wrapper" role="complementary"> <div class="footer-widgets-wrapper"> <div class="footer-widgets column-one grid-item"> <div class="widget widget_search"><div class="widget-content"><form role="search" method="get" class="search-form" action="https://eltecnofilo.es/"> <label for="search-form-2"> <span class="screen-reader-text">Buscar:</span> <input type="search" id="search-form-2" class="search-field" placeholder="Buscar …" value="" name="s" /> </label> <input type="submit" class="search-submit" value="Buscar" /> </form> </div></div> <div class="widget widget_recent_entries"><div class="widget-content"> <h2 class="widget-title subheading heading-size-3">Entradas recientes</h2> <ul> <li> <a href="https://eltecnofilo.es/vps-cloud-hosting/">VPS cloud hosting</a> </li> <li> <a href="https://eltecnofilo.es/versiones-de-ejecucion-de-python-en-acoplable-como-probar-la-ultima-release-python/">Versiones de ejecución de Python en acoplable: Cómo probar la última Release Python</a> </li> <li> <a href="https://eltecnofilo.es/leer-y-escribir-archivos-csv/">Leer y escribir archivos CSV</a> </li> <li> <a href="https://eltecnofilo.es/python-puro-vs-vs-numpy-tensorflow-comparacion-de-rendimiento/">Python puro vs vs NumPy TensorFlow Comparación de Rendimiento</a> </li> <li> <a href="https://eltecnofilo.es/estructura-python-programa-lexico/">Estructura Python Programa léxico</a> </li> </ul> </div></div><div class="widget widget_recent_comments"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Comentarios recientes</h2><ul id="recentcomments"><li class="recentcomments"><span class="comment-author-link"><a href="https://wordpress.org/" rel="external nofollow ugc" class="url">A WordPress Commenter</a></span> en <a href="https://eltecnofilo.es/hello-world/#comment-1">Hello world!</a></li></ul></div></div> </div> <div class="footer-widgets column-two grid-item"> <div class="widget widget_archive"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Archivos</h2> <ul> <li><a href='https://eltecnofilo.es/2020/04/'>abril 2020</a></li> <li><a href='https://eltecnofilo.es/2020/03/'>marzo 2020</a></li> <li><a href='https://eltecnofilo.es/2019/12/'>diciembre 2019</a></li> </ul> </div></div><div class="widget widget_categories"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Categorías</h2> <ul> <li class="cat-item cat-item-22"><a href="https://eltecnofilo.es/category/python/">Python</a> </li> <li class="cat-item cat-item-1"><a href="https://eltecnofilo.es/category/uncategorized/">Uncategorized</a> </li> </ul> </div></div><div class="widget widget_meta"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Meta</h2> <ul> <li><a rel="nofollow" href="https://eltecnofilo.es/wp-login.php">Acceder</a></li> <li><a href="https://eltecnofilo.es/feed/">Feed de entradas</a></li> <li><a href="https://eltecnofilo.es/comments/feed/">Feed de comentarios</a></li> <li><a href="https://es.wordpress.org/">WordPress.org</a></li> </ul> </div></div> </div> </div><!-- .footer-widgets-wrapper --> </aside><!-- .footer-widgets-outer-wrapper --> </div><!-- .footer-inner --> </div><!-- .footer-nav-widgets-wrapper --> <footer id="site-footer" role="contentinfo" class="header-footer-group"> <div class="section-inner"> <div class="footer-credits"> <p class="footer-copyright">© 2020 <a href="https://eltecnofilo.es/">My Blog</a> </p><!-- .footer-copyright --> <p class="powered-by-wordpress"> <a href="https://es.wordpress.org/"> Funciona gracias a WordPress </a> </p><!-- .powered-by-wordpress --> </div><!-- .footer-credits --> <a class="to-the-top" href="#site-header"> <span class="to-the-top-long"> Ir arriba <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-long --> <span class="to-the-top-short"> Subir <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-short --> </a><!-- .to-the-top --> </div><!-- .section-inner --> </footer><!-- #site-footer --> <script src='https://eltecnofilo.es/wp-includes/js/comment-reply.min.js?ver=5.3.4'></script> <script src='https://eltecnofilo.es/wp-includes/js/wp-embed.min.js?ver=5.3.4'></script> <script> /(trident|msie)/i.test(navigator.userAgent)&&document.getElementById&&window.addEventListener&&window.addEventListener("hashchange",function(){var t,e=location.hash.substring(1);/^[A-z0-9_-]+$/.test(e)&&(t=document.getElementById(e))&&(/^(?:a|select|input|button|textarea)$/i.test(t.tagName)||(t.tabIndex=-1),t.focus())},!1); </script> </body> </html>