Categorías
Python

Acelerar su programa de Python con la Concurrencia

 

Tabla de Contenidos

  • básico SetupWhat que realmente está pasando aquí?
  • Lo que realmente está pasando aquí? de
  • deje correr las cosas en paralelo
  • Una cuestión importante a considerar
  • Embalaje para arriba de
  • Lo que realmente está pasando aquí?

Bienvenido!

los artículos de esta serie:

    Parte

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

repo: TDD-Django

PyVows es un puerto del desarrollo votos populares comportamiento impulsado (o BDD) marco para Node.js. Para este artículo me centraré en el uso de PyVows y selenio para ejecutar las pruebas de interfaz gráfica de usuario contra Django, y en el siguiente artículo de esta serie, voy a hablar de las pruebas unitarias con PyVows.

Antes de empezar, tómese un minuto para leer este artículo que describe las diferencias entre el comportamiento impulsado y desarrollo basado en pruebas. El trabajo de instalación de

básico

Vamos a través de un conjunto de ejemplos. Iniciar un nuevo proyecto de Django (con virtualenvwrapper, django-pyVows y selenio):

$ mkvirtualenv TDD-Django --no-site-packages
$ workon TDD-Django
$ pip install pyVows django django-pyVows selenium lxml cssselect
$ django-admin.py startproject tddapp

Esta configuración es todo lo que necesitamos por ahora. Vamos a crear nuestra primera prueba GUI con pyVows añadiendo uitests.py a tddapp, y luego añadir el siguiente código:

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

@Vows.batch
class TDDDjangoApp(DjangoHTTPContext):

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

def topic(self):
self.start_server()
browser = webdriver.Firefox()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_login_page(self, topic):
expect(topic.title).to_include('Django')

Lo que realmente está pasando aquí?

el inicio de las importaciones de prueba todas las bibliotecas necesarias, como pyvows, django_pyvows y selenio:

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

La siguiente línea crea lo que en pyVows que se conoce como un lote de prueba. Un lote de prueba en pyVows se identifica por el decorador @ Vows.batch y es sinónimo de unittest.TestCase en el módulo de prueba de unidad de la biblioteca estándar de Python.

lotes de prueba

son contextos que describen diferentes componentes y estados que desea probar. En general, un lote de prueba heredará del Vows.Context pero en nuestro caso estamos heredando de DjangoHTTPContext como esa clase proporciona toda la funcionalidad del ayudante al horno en django_pyvows. Además de la herencia de DjangoHTTPContext necesita sobrescribir los get_settings función () y devolver la ruta de acceso al archivo de settings.py que desea utilizar para ejecutar las pruebas:

@Vows.batch
class TDDDjangoApp(DjangoHTTPContext):

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

Cada contexto debe contener un solo tema, que es el el objeto que desee prueba y un número de votos que realizan las pruebas en contra de dicho tema. Para nuestro tema empezamos el servidor Django usando la función auxiliar start_server (), el fuego de selenio y selenio punto a la raíz de nuestra aplicación Django. Entonces, ya que esta es una prueba de interfaz gráfica de usuario, nuestro tema devuelve el WebDriver selenio para que podamos ejecutar nuestras pruebas.

Nota: self.get_url ( «/») traduce la dirección URL relativa a la URL absoluta que necesitamos para acceder al sitio.

A continuación, nuestros primeros cheques voto / prueba para ver si el título de la página incluye ‘Django’:

def topic(self):
self.start_server()
browser = webdriver.Firefox()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_django_page(self, topic):
expect(topic.title).to_include('Django')

Ejecución de la prueba en este punto debe pasar, porque la página por defecto en Django tiene un título de “Bienvenido a Django” . Esto verifica que nuestra configuración básica es correcta y podemos seguir adelante y empezar a programar algo. La salida de la ejecución de la prueba es el siguiente: la atención

$ pyVows -vvv uitests.py

============
Vows Results
============

Tdd django app
✓ should prompt the user with a django page
✓ OK » 1 honored • 0 broken (2.489381s)

And a failed test would look like this:

$ pyVows -vvv uitests.py

============
Vows Results
============

Tdd django app
✗ should prompt the user with a django page
Expected topic(u'Page Not Found') to include 'Login'

...snipping Traceback to save space...

✗ OK » 0 honored • 1 broken (2.489381s)

atención a la denominación de las pruebas en el informe. La línea indentada más exterior es el contexto (TDDDjangoApp), con cada voto / test en ese contexto que aparece y debajo de sangría. Esto hace que la presentación de informes y la búsqueda de defectos muy fácil, ya que se lee como una serie de requisitos. Considere el siguiente informe:

$ pyVows -vvv uitests.py

============
Vows Results
============

Tdd django app
on Chrome
✓ should prompt the user with a django page
on Firefox
✗ should prompt the user with a django page

...snipping Traceback to save space...

✗ OK » 1 honored • 1 broken (3.119354s)

En el ejemplo ficticio anterior, podemos ver que nuestra configuración está funcionando forChrome y no en Firefox. Así, mediante el uso de nombres intuitiva de las pruebas, pyVowsproduces un informe simple pero altamente eficaz para la localización de problemas. de

deje correr las cosas en paralelo

Además se están acercando a la prueba paralela asíncrona en varios navegadores! Vamos a echar un vistazo al código de la prueba que se habría producido ese informe:

@Vows.batch
class TDDDjangoApp(DjangoContext):

class OnFirefox(DjangoHTTPContext):

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

def topic(self):
self.start_server(port=8888)
browser = webdriver.Firefox()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_django_page(self, topic):
expect(topic.title).to_include('string not in the title')

class OnChrome(DjangoHTTPContext):

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

def topic(self):
self.start_server(port=8887)
browser = webdriver.Chrome()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_django_page(self, topic):
expect(topic.title).to_include('Django')

En pyVows, hermanos contextos son ejecutados en contextos paralelos y anidadas se ejecutan de forma secuencial – lo que significa TDDDjangoApp sería ejecutado primero, y luego sus dos contextos anidados OnFirefox y OnChrome serían ejecutados en paralelo (porque son hermanos de la otra). sin embargo, podemos eliminar una gran parte del código duplicado mediante la creación de un conjunto estándar de pruebas y ejecutar esas pruebas contra múltiples contextos. La programación funcional

def onBrowser(webdriver, port):
class BrowserTests(DjangoHTTPContext):

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

def topic(self):
self.start_server(port=port)
browser = webdriver()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_django_page(self, topic):
expect(topic.title).to_include('Django')

return BrowserTests

@Vows.batch
class TDDDjangoApp(DjangoContext):

class OnChrome(onBrowser(webdriver.Chrome, 8887)):
pass

class OnFirefox(onBrowser(webdriver.Firefox, 8888)):
pass

class OnPhantonJS(onBrowser(webdriver.PhantomJS, 8886)):
pass

al rescate! (Bueno, algo así …) Una de las ventajas de Python es que no estará sujeto a un paradigma de programación en particular. Así que podemos usar una variedad de soluciones. En este caso, queremos crear dinámicamente un contexto de prueba para cada navegador y el puerto que queremos ejecutar las pruebas en contra.

Para hacer esto podemos escribir todas nuestras pruebas una vez (en la clase BrowserTests, que se devuelve por la función OnBrowser). Lo bueno de esta técnica es que la función tema () en las declaraciones de clase BrowserTests cualquier WebDriver (también conocido como navegador) que se pasa a la función onBrowser.

Desde aquí nuestro lote de prueba (denotado por @ Vows.batch) es simplemente describiendo lo que será RAN / informó. La salida de los cuales sería el siguiente:

============
Vows Results
============

Tdd django app
On phantom js
✓ should prompt the user with a django page
On chrome
✓ should prompt the user with a django page
On firefox
✓ should prompt the user with a django page
✓ OK » 3 honored • 0 broken (4.068020s)

Con esta configuración cada prueba añadimos a la clase BrowserTests se ejecutará againsteach navegador especificamos. Por ejemplo, si añadimos las siguientes pruebas para la clase theBrowserTests …

def heading_should_tell_the_user_it_worked(self,topic):
heading_text = topic.find_element_by_css_selector("#summary h1").text
expect(heading_text).to_equal("It worked!")

def should_display_debug_message(self,topic):
explain_text = topic.find_element_by_id("explanation").text
expect(explain_text).to_include("DEBUG = True")

… entonces el informe mostraría:

============
Vows Results
============

Tdd django app
On firefox
✓ should prompt the user with a django page
✓ heading should tell the user it worked
✓ should display debug message
On chrome
✓ heading should tell the user it worked
✓ should prompt the user with a django page
✓ should display debug message
On phantom js
✓ should prompt the user with a django page
✓ should display debug message
✓ heading should tell the user it worked
✓ OK » 9 honored • 0 broken (4.238522s)

Una cuestión importante a considerar

Usted puede preguntarse por qué, sin embargo, estamos llamando start_server por cada nuevo contexto. En realidad no tenemos que hacer esto, pero estamos trabajando en torno a una limitación de django_pyvows. Desafortunadamente la función DjangoHTTPContext.start_server () crea un solo hilo, lo que disminuye el rendimiento. Para este conjunto de pruebas, por ejemplo, tiempo de ejecución aumenta desde unos 5 segundos (lo escrito anteriormente) a aproximadamente 21 segundos si sólo crean un servidor CherryPy.

Lo ideal sería que desee crear un solo servidor de prueba en contra y perhapsmake el servidor multi-hilo, de modo que no tenemos que incurrir en el performancepenalty. Así que vamos a ver si podemos hacer que eso ocurra. En primer lugar la actualización del código Let TheTest utilizar pyVows noción de contexto herencia tan sólo tenemos que createthe servidor una vez.

def onBrowser(webdriver):
class BrowserTests(DjangoHTTPContext):

def topic(self):
browser = webdriver()
browser.get(self.get_url("/"))
return browser

def should_prompt_the_user_with_a_login_page(self, topic):
expect(topic.title).to_include('Django')

return BrowserTests

@Vows.batch
class TDDDjangoApp(DjangoHTTPContext):

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

def topic(self):
self.start_server()

class OnChrome(onBrowser(webdriver.Chrome)):
pass

class OnFirefox(onBrowser(webdriver.Firefox)):
pass

class OnPhantonJS(onBrowser(webdriver.PhantomJS)):
pass

Aviso cómo el contexto TDDDjangoApp ahora llama start_server () de su topicfunction y ya no hay necesidad de pasar de un puerto a nuestra función contextcreation onBrowser (). Ahora tenemos una única versión del servidor de instantiate theCherryPy y ejecutar todas las pruebas en contra de eso. Esto tiene la ventaja de utilizar menos resourcesand hace que el código sea más fácil de mantener.

para lidiar con el problema de rendimiento, que puede manejar manualmente los hilos para obtener pruebas que se ejecutan más rápido:

def onBrowser(webdriver):
class BrowserTests(DjangoHTTPContext):

def topic(self):
webdriver.get(self.get_url("/"))
return webdriver

def teardown(self):
webdriver.quit()

def should_prompt_the_user_with_a_login_page(self, topic):
expect(topic.title).to_include('Django')

return BrowserTests

@Vows.batch
class TDDDjangoApp(DjangoHTTPContext):

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

def topic(self):
self.start_server()
#manual add some more threads to the CherryPy server
self.server.thr.server.requests.grow(3)

def teardown(self):
#clean up the threads so we can exit cleanly
self.server.thr.server.requests.stop(timeout=1)

class OnChrome(onBrowser(webdriver.Chrome())):
pass

class OnFirefox(onBrowser(webdriver.Firefox())):
pass

class OnPhantonJS(onBrowser(webdriver.PhantomJS())):
pass

Hemos hecho tres cosas aquí. En primer lugar, en la función de tema () hemos añadido tres morethreads a grupo de subprocesos de CherryPy:

def topic(self):
self.start_server()
#manual add some more threads to the CherryPy server
self.server.thr.server.requests.grow(3)

En segundo lugar, era necesario asegurarse de que los hilos se limpiaron o la prueba de runwill nunca es completa.

def teardown(self):
#clean up the threads so we can exit cleanly
self.server.thr.server.requests.stop(timeout=1)

Por último, aseguró que WebDriver limpia después de sí mismo mediante la adición de una función de desmontaje a nuestra clase BrowserTest:

def teardown(self):
webdriver.quit()

Embalaje para arriba de

nodo no es el único juego en la ciudad que se pueden hacer las cosas de forma asíncrona. Y con el puerto de Vows.js a pyVows y un poco de ingenio podemos obtener pruebas en paralelo con varios navegadores para Django en marcha con muy poco esfuerzo.

Teniendo esto un paso más allá, que podría vincularse a algo así como BrowserMob o SauceLabs a prueba contra un gran número de navegadores y entonces las cosas sería realmente comenzar a ponerse interesante. Además, con sencillo, fácil de entender que informa y una sintaxis muy limpia, sus pruebas de interfaz gráfica de usuario puede llegar a ser (relativamente) indoloro y rápido!

El código utilizado para este artículo se puede encontrar en el repositorio de Github asociado.

me dejó saber lo que piensa en los comentarios a continuación. ¡Salud!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *