Pruebas de software automatizadas con Python

La prueba de software es el proceso en el que un desarrollador se asegura de que la salida real del software coincida con la salida deseada al proporcionar algunas entradas de prueba al software. La prueba de software es un paso importante porque, si se realiza correctamente, puede ayudar al desarrollador a encontrar errores en el software en muy poco tiempo.

Las pruebas de software se pueden dividir en dos clases, pruebas manuales y pruebas automatizadas . La prueba automatizada es la ejecución de sus pruebas utilizando un script en lugar de un humano. En este artículo, discutiremos algunos de los métodos de pruebas de software automatizadas con Python.

Escribamos una aplicación sencilla sobre la que realizaremos todas las pruebas.

class Square:
    def __init__(self, side):
        """ creates a square having the given side
        """
        self.side = side
  
    def area(self):
        """ returns area of the square
        """
        return self.side**2
  
    def perimeter(self):
        """ returns perimeter of the square
        """
        return 4 * self.side
  
    def __repr__(self):
        """ declares how a Square object should be printed
        """
        s = 'Square with side = ' + str(self.side) + '\n' + \
        'Area = ' + str(self.area()) + '\n' + \
        'Perimeter = ' + str(self.perimeter())
        return s
  
  
if __name__ == '__main__':
    # read input from the user
    side = int(input('enter the side length to create a Square: '))
      
    # create a square with the provided side
    square = Square(side)
  
    # print the created square
    print(square)

Nota: Para obtener más información sobre la función __repr__(), consulte este artículo .

Ahora que tenemos nuestro software listo, echemos un vistazo a la estructura de directorios de nuestra carpeta de proyecto y después de eso, comenzaremos a probar nuestro software.

---Software_Testing
   |--- __init__.py (to initialize the directory as python package)
   |--- app.py (our software)
   |--- tests (folder to keep all test files)
           |--- __init__.py

El módulo ‘unittest’

Uno de los principales problemas con las pruebas manuales es que requiere tiempo y esfuerzo. En las pruebas manuales, probamos la aplicación sobre alguna entrada, si falla, lo anotamos o depuramos la aplicación para esa entrada de prueba en particular, y luego repetimos el proceso. Con unittest, todas las entradas de prueba se pueden proporcionar a la vez y luego puede probar su aplicación. Al final, obtiene un informe detallado con todos los casos de prueba fallidos claramente especificados, si los hay.

El unittestmódulo tiene un marco de prueba incorporado y un corredor de prueba. Un marco de prueba es un conjunto de reglas que se deben seguir al escribir casos de prueba, mientras que un corredor de prueba es una herramienta que ejecuta estas pruebas con un montón de configuraciones y recopila los resultados.

Instalación: unittest está disponible en PyPI y se puede instalar con el siguiente comando:

pip install unittest

Uso: Escribimos las pruebas en un módulo de Python (.py). Para ejecutar nuestras pruebas, simplemente ejecutamos el módulo de prueba utilizando cualquier IDE o terminal.

Ahora, escribamos algunas pruebas para nuestro pequeño software discutido anteriormente usando el unittestmódulo.

  1. Cree un archivo llamado tests.pyen la carpeta llamada «pruebas».
  2. En tests.pyimportación unittest.
  3. Cree una clase llamada TestClassque herede de la clase unittest.TestCase.

    Regla 1: todas las pruebas se escriben como los métodos de una clase, que deben heredar de la clase unittest.TestCase.

  4. Cree un método de prueba como se muestra a continuación.
    Regla 2: el nombre de todos y cada uno de los métodos de prueba debe comenzar con «prueba», de lo contrario, el corredor de la prueba lo omitirá.

    def test_area(self):
        # testing the method Square.area().
          
        sq = Square(2)    # creates a Square of side 2 units.
     
        # test if the area of the above square is 4 units, 
        # display an error message if it's not.
     
        self.assertEqual(sq.area(), 4
            f'Area is shown {sq.area()} for side = {sq.side} units')

    Regla 3: usamos assertEqual()declaraciones especiales en lugar de las declaraciones integradas assertdisponibles en Python.

    El primer argumento de assertEqual()es la salida real, el segundo argumento es la salida deseada y el tercer argumento es el mensaje de error que se mostraría en caso de que los dos valores difieran entre sí (la prueba falla).

  5. Para ejecutar las pruebas que acabamos de definir, debemos llamar al método unittest.main(), agregar las siguientes líneas en el módulo «tests.py».

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

    Debido a estas líneas, tan pronto como ejecute el script «test.py», unittest.main()se llamará a la función y se ejecutarán todas las pruebas.

Finalmente, el módulo «tests.py» debería parecerse al código que se muestra a continuación.

import unittest
from .. import app
  
class TestSum(unittest.TestCase):
  
    def test_area(self):
        sq = app.Square(2)
  
        self.assertEqual(sq.area(), 4, 
            f'Area is shown {sq.area()} rather than 9')
  
if __name__ == '__main__':
    unittest.main()

Habiendo escrito nuestros casos de prueba, ahora probemos nuestra aplicación en busca de errores. Para probar su aplicación, simplemente necesita ejecutar el archivo de prueba «tests.py» usando el símbolo del sistema o cualquier IDE de su elección. La salida debería ser algo como esto.

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

En la primera línea, un .(punto) representa una prueba exitosa mientras que una ‘F’ representaría un caso de prueba fallido. El OKmensaje, al final, nos dice que todas las pruebas fueron pasadas con éxito.

Agreguemos algunas pruebas más en «tests.py» y volvamos a probar nuestra aplicación.

import unittest
from .. import app
  
class TestSum(unittest.TestCase):
  
    def test_area(self):
        sq = app.Square(2)
        self.assertEqual(sq.area(), 4, 
            f'Area is shown {sq.area()} rather than 9')
  
    def test_area_negative(self):
        sq = app.Square(-3)
        self.assertEqual(sq.area(), -1, 
            f'Area is shown {sq.area()} rather than -1')
  
    def test_perimeter(self):
        sq = app.Square(5)
        self.assertEqual(sq.perimeter(), 20, 
            f'Perimeter is {sq.perimeter()} rather than 20')
  
    def test_perimeter_negative(self):
        sq = app.Square(-6)
        self.assertEqual(sq.perimeter(), -1, 
            f'Perimeter is {sq.perimeter()} rather than -1')
  
if __name__ == '__main__':
    unittest.main()
.F.F
======================================================================
FAIL: test_area_negative (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests_unittest.py", line 11, in test_area_negative
    self.assertEqual(sq.area(), -1, f'Area is shown {sq.area()} rather than -1 for negative side length')
AssertionError: 9 != -1 : Area is shown 9 rather than -1 for negative side length

======================================================================
FAIL: test_perimeter_negative (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests_unittest.py", line 19, in test_perimeter_negative
    self.assertEqual(sq.perimeter(), -1, f'Perimeter is {sq.perimeter()} rather than -1 for negative side length')
AssertionError: -24 != -1 : Perimeter is -24 rather than -1 for negative side length

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=2)

Algunas cosas a tener en cuenta en el informe de prueba anterior son:

  • La primera línea representa que la prueba 1 y la prueba 3 se ejecutaron con éxito mientras que la prueba 2 y la prueba 4 fallaron
  • Cada caso de prueba fallido se describe en el informe, la primera línea de la descripción contiene el nombre del caso de prueba fallido y la última línea contiene el mensaje de error que definimos para ese caso de prueba.
  • Al final del informe puede ver el número de pruebas fallidas, si ninguna prueba falla, el informe terminará conOK

Nota: Para mayor conocimiento puede leer la documentación completa de unittest.

El módulo “nariz2”

El propósito de nose2es ampliar unittestpara facilitar las pruebas. nose2es compatible con las pruebas escritas con el unittestmarco de pruebas y se puede usar como reemplazo del ejecutor de unittestpruebas.

Instalación: nose2 se puede instalar desde PyPI usando el comando,

pip install nose2

Uso: nose2 no tiene ningún marco de prueba y es simplemente un corredor de prueba que es compatible con el unittestmarco de prueba. Por lo tanto, ejecutaremos las mismas pruebas que escribimos anteriormente (para unittest) usando nose2. Para ejecutar las pruebas usamos el siguiente comando en el directorio fuente del proyecto («Software_Testing» en nuestro caso),

nose2

En nose2terminología, todos los módulos de python (.py) cuyo nombre comienza con «test» (es decir, test_file.py, test_1.py) se consideran archivos de prueba. En la ejecución, nose2buscará todos los archivos de prueba en todos los subdirectorios que se encuentran en una o más de las siguientes categorías,

  • que son paquetes de python (contienen “__init__.py”).
  • cuyo nombre comienza con «prueba» después de estar en minúsculas, es decir, TestFiles, pruebas.
  • que se denominan «src» o «lib».

nose2primero carga todos los archivos de prueba presentes en el proyecto y luego se ejecutan las pruebas. Por lo tanto, nose2tenemos la libertad de dividir nuestras pruebas entre varios archivos de prueba en diferentes carpetas y ejecutarlas a la vez, lo cual es muy útil cuando se trata de una gran cantidad de pruebas.

Ahora aprendamos sobre las diferentes opciones de personalización proporcionadas por nose2las cuales nos pueden ayudar durante el proceso de prueba.

  1. Cambiar el directorio de búsqueda:
    si queremos cambiar el directorio en el que nose2busca los archivos de prueba, podemos hacerlo usando los argumentos de la línea de comando -so --start-dircomo,
    nose2 -s DIR_ADD DIR_NAME

    aquí, DIR_NAMEes el directorio en el que queremos buscar los archivos de prueba y DIR_ADDes la dirección del directorio principal en DIR_NAMErelación con el directorio de origen del proyecto (es decir, use «./» si el directorio de prueba está en el directorio de origen del proyecto).
    Esto es extremadamente útil cuando desea probar solo una característica de su aplicación a la vez.

  2. Ejecución de casos de prueba específicos: también podemos ejecutar una prueba específica a la vez utilizando los argumentos de la línea de comando
    y como ,nose2-s--start-dir
    nose2 -s DIR_ADD DIR_NAME.TEST_FILE.TEST_CLASS.TEST_NAME
    • TEST_NAME: nombre del método de prueba.
    • TEST_CLASS: clase en la que se define el método de prueba.
    • TEST_FILE: nombre del archivo de prueba en el que se define el caso de prueba, es decir, test.py.
    • DIR_NAME: directorio en el que existe el archivo de prueba.
    • DIR_ADD: dirección del directorio padre de DIR_NAME relativo a la fuente del proyecto.

    Con esta función, podemos probar nuestro software en entradas específicas.

  3. Ejecutar pruebas en un solo módulo:
    nose2 también se puede usar unittestllamando a la función nose2.main()tal como la llamamos unittest.main()en ejemplos anteriores.
  4. Además de las personalizaciones básicas anteriores , nose2proporciona funciones avanzadas como cargar varios complementos y archivos de configuración o crear su propio corredor de prueba.

El módulo «pytest»

pytestes el marco de prueba más popular para python. Puede probar pytestcualquier cosa, desde scripts básicos de python hasta bases de datos, API y UI. Aunque pytestse usa principalmente para pruebas de API, en este artículo cubriremos solo los conceptos básicos de pytest.

Instalación: puede instalar pytestdesde PyPI usando el comando,

pip install pytest

Uso:pytest se llama al ejecutor de pruebas mediante el siguiente comando en el código fuente del proyecto ,

py.test

A diferencia nose2de , pytestbusca archivos de prueba en todas las ubicaciones dentro del directorio del proyecto. Cualquier archivo cuyo nombre comience con «test_» o termine con «_test» se considera un archivo de prueba en la pytestterminología. Vamos a crear un archivo «test_file1.py» en la carpeta «tests» como nuestro archivo de prueba.

Creación de métodos de prueba:
pytest admite los métodos de prueba escritos en el unittestmarco, pero el pytestmarco proporciona una sintaxis más sencilla para escribir pruebas. Consulte el código a continuación para comprender la sintaxis del método de prueba del pytestmarco.

from .. import app
  
def test_file1_area():
    sq = app.Square(2)
    assert sq.area() == 4, 
        f"area for side {sq.side} units is {sq.area()}"
  
def test_file1_perimeter():
    sq = app.Square(-1)
    assert sq.perimeter() == -1, 
        f'perimeter is shown {sq.perimeter()} rather than -1'

Nota: similar a unittest, pytestrequiere que todos los nombres de prueba comiencen con «prueba».

A diferencia unittestde , pytestusa las assertdeclaraciones de Python predeterminadas que lo hacen aún más fácil de usar.

Tenga en cuenta que ahora la carpeta «pruebas» contiene dos archivos, a saber, «tests.py» (escrito en el unittestmarco) y «test_file1.py» (escrito en el pytestmarco). Ahora vamos a ejecutar el pytestcorredor de prueba.

py.test

Obtendrá un informe similar al obtenido mediante el uso de unittest.

============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.4.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/manthan/articles/Software_testing_in_Python
collected 6 items                                                              

tests/test_file1.py .F                                                   [ 33%]
tests/test_file2.py .F.F                                                 [100%]

=================================== FAILURES ===================================

Los porcentajes en el lado derecho del informe muestran el porcentaje de pruebas que se han completado en ese momento, es decir, 2 de los 6 casos de prueba se completaron al final de «test_file1.py».

Aquí hay algunas personalizaciones básicas más que vienen con pytest.

  1. Ejecución de archivos de prueba específicos: para ejecutar solo un archivo de prueba específico, use el comando,
    py.test <filename>
  2. Coincidencia de substrings: supongamos que queremos probar solo el area()método de nuestra Squareclase, podemos hacerlo usando la coincidencia de substrings de la siguiente manera:
    py.test -k "area"

    Con este comando pytestse ejecutarán solo aquellas pruebas que tengan la string «área» en sus nombres, es decir, «test_file1_area()», «test_area()», etc.

  3. Marcado: como sustituto de la coincidencia de substrings, el marcado es otro método con el que podemos ejecutar un conjunto específico de pruebas. En este método ponemos una marca en las pruebas que queremos ejecutar. Observe el ejemplo de código dado a continuación,

    # @pytest.mark.<tag_name>
    @pytest.mark.area     
     
    def test_file1_area():
        sq = app.Square(2)
        assert sq.area() == 4
            f"area for side {sq.side} units is {sq.area()}"

    En el ejemplo de código anterior test_file1_area()está marcado con la etiqueta «área». Todos los métodos de prueba que han sido marcados con alguna etiqueta se pueden ejecutar usando el comando,

    py.test -m <tag_name>
  4. Procesamiento paralelo: si tiene una gran cantidad de pruebas, pytestpuede personalizarlas para ejecutar estos métodos de prueba en paralelo. Para eso, debe instalar pytest-xdist, que se puede instalar con el comando,
    pip install pytest-xdist

    Ahora puede usar el siguiente comando para ejecutar sus pruebas más rápido usando multiprocesamiento,

    py.test -n 4

    Con este comando pytestasigna 4 trabajadores para realizar las pruebas en paralelo, puede cambiar este número según sus necesidades.

    Si sus pruebas son seguras para subprocesos, también puede usar subprocesos múltiples para acelerar el proceso de prueba. Para eso necesitas instalar pytest-parallel(usando pip). Para ejecutar sus pruebas en subprocesos múltiples, use el comando,

    pytest --workers 4

Publicación traducida automáticamente

Artículo escrito por Manthanchauhan y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

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