Escribir pruebas unitarias profesionales en Python

Las pruebas son la base del desarrollo de software sólido. Hay muchos tipos de pruebas, pero el tipo más importante es la prueba unitaria. Las pruebas unitarias le dan mucha confianza de que puede usar piezas bien probadas como primitivas y confiar en ellas cuando las elabora para crear su programa. Aumentan su inventario de código de confianza más allá de las bibliotecas incorporadas y estándar de su idioma. Además, Python proporciona un gran soporte para escribir pruebas unitarias..

Ejemplo de ejecución

Antes de sumergirse en todos los principios, heurísticas y pautas, veamos una prueba de unidad representativa en acción. los SelfDrivingCar clase es una implementación parcial de la lógica de conducción de un auto-auto. Se trata principalmente de controlar la velocidad del coche. Es consciente de los objetos que tiene delante, el límite de velocidad y si llegó o no a su destino.. 

clase SelfDrivingCar (objeto): def __init __ (self): self.speed = 0 self.destination = None def _accelerate (self): self.speed + = 1 def _decelerate (self): if self.speed> 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distancia < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() def __init__(self): self.speed = 0 self.destination = None def _accelerate(self): self.speed += 1 def _decelerate(self): if self.speed > 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distancia < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() 

Aquí hay una prueba de unidad para el detener() Método para abrir el apetito. Voy a entrar en los detalles más tarde. 

desde unittest import TestCase clase SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar () def test_stop (self): self.car.speed = 5 self.car.stop () # Verificar que la velocidad es 0 después detener self.assertEqual (0, self.car.speed) # Verifique que esté bien para detenerse nuevamente si el automóvil ya está parado self.car.stop () self.assertEqual (0, self.car.speed)

Pautas de prueba de unidad

Cometer

Escribir buenas pruebas unitarias es un trabajo duro. Escribir pruebas unitarias lleva tiempo. Cuando realice cambios en su código, generalmente también tendrá que cambiar sus pruebas. A veces tendrás errores en tu código de prueba. Eso significa que tienes que estar realmente comprometido. Los beneficios son enormes, incluso para proyectos pequeños, pero no son gratuitos..

Ser disciplinado

Usted debe ser disciplinado. Se consistente. Asegúrese de que las pruebas siempre pasan. No permita que se rompan las pruebas porque "sabe" que el código está bien.

Automatizar

Para ayudarlo a ser disciplinado, debe automatizar sus pruebas unitarias. Las pruebas deben ejecutarse automáticamente en puntos significativos como la confirmación previa o el despliegue previo. Idealmente, su sistema de administración de control de fuente rechaza el código que no pasó todas sus pruebas.

El código no probado está roto por definición

Si no lo probaste, no puedes decir que funcione. Esto significa que debes considerarlo roto. Si es un código crítico, no lo implemente en producción.

Fondo

Que es una unidad?

Una unidad para el propósito de la prueba de unidad es un archivo / módulo que contiene un conjunto de funciones relacionadas o una clase. Si tiene un archivo con varias clases, debe escribir una prueba de unidad para cada clase.

A TDD o no a TDD

El desarrollo guiado por pruebas es una práctica en la que escribes las pruebas antes de escribir el código. Este enfoque tiene varios beneficios, pero recomiendo evitarlo si tiene la disciplina para escribir las pruebas adecuadas más adelante.. 

La razón es que diseño con código. Escribo el código, lo miro, lo reescribo, lo miro de nuevo y lo vuelvo a escribir muy rápidamente. Las pruebas de escritura primero me limitan y me frenan. 

Una vez que haya terminado con el diseño inicial, escribiré las pruebas inmediatamente, antes de integrarme con el resto del sistema. Dicho esto, es una excelente manera de presentarse a las pruebas unitarias y garantiza que todo su código tendrá pruebas.

El Módulo Unittest

El módulo unittest viene con la biblioteca estándar de Python. Proporciona una clase llamada Caso de prueba, de la que puedes derivar tu clase. Entonces puedes anular un preparar() método para preparar un dispositivo de prueba antes de cada prueba y / o classSetUp () Método de clase para preparar un dispositivo de prueba para todas las pruebas (no restablecer entre pruebas individuales). Hay correspondientes demoler() y classTearDown () métodos que puede anular también.

Aquí están las partes relevantes de nuestro SelfDrivingCarTest clase. Yo uso solo el preparar() método. Creo un fresco SelfDrivingCar instancia y almacenarlo en auto.car por lo que está disponible para cada prueba.

desde unittest import TestCase clase SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar ()

El siguiente paso es escribir métodos de prueba específicos para probar ese código bajo prueba: la SelfDrivingCar La clase en este caso es hacer lo que se supone que debe hacer. La estructura de un método de prueba es bastante estándar:

  • Preparar el entorno (opcional)..
  • Preparar el resultado esperado.
  • Llame al código bajo prueba.
  • Afirmar que el resultado real coincide con el resultado esperado.

Tenga en cuenta que el resultado no tiene que ser la salida de un método. Puede ser un cambio de estado de una clase, un efecto secundario como agregar una nueva fila en una base de datos, escribir un archivo o enviar un correo electrónico.

Por ejemplo, el detener() método de la SelfDrivingCar clase no devuelve nada, pero cambia el estado interno al establecer la velocidad a 0. La assertEqual () método proporcionado por el Caso de prueba La clase base se usa aquí para verificar que las llamadas detener() trabajó como se esperaba.

def test_stop (self): self.car.speed = 5 self.car.stop () # Verifique que la velocidad sea 0 después de detener self.assertEqual (0, self.car.speed) # Verifique que esté bien para detenerse nuevamente si el el auto ya está parado self.car.stop () self.assertEqual (0, self.car.speed)

En realidad hay dos pruebas aquí. La primera prueba es asegurarse de que si la velocidad del auto es 5 y detener() se llama, entonces la velocidad se convierte en 0. Luego, otra prueba es asegurarse de que nada salga mal si se llama detener() de nuevo cuando el coche ya está parado.

Más adelante, introduciré varias pruebas más para funcionalidad adicional..

El modulo doctest

El módulo doctest es bastante interesante. Le permite utilizar ejemplos de códigos interactivos en su cadena de documentos y verificar los resultados, incluidas las excepciones planteadas. 

No uso ni recomiendo doctest para sistemas a gran escala. La prueba adecuada de la unidad requiere mucho trabajo. El código de prueba suele ser mucho más grande que el código bajo prueba. Las cadenas de documentos no son el medio adecuado para escribir pruebas completas. Aunque son geniales. Esto es lo que un factorial La función con las pruebas doc parece:

import math def factorial (n): "" "Devuelve el factorial de n, un entero exacto> = 0. Si el resultado es lo suficientemente pequeño como para caber en un int, devuelve un int. Else devuelve un largo. >>> [factorial (n) para n en el rango (6)] [1, 1, 2, 6, 24, 120] >>> [factorial (largo (n)) para n en el rango (6)] [1, 1, 2, 6, 24, 120] >>> factorial (30) 265252859812191058636308480000000L >>> factorial (30L) 265252859812191058636308480000000L >>> factorial (-1) Traceback (última llamada más reciente):… ValueError: n debe ser> = 0 Factorial de están bien, pero el flotador debe ser un entero exacto: >>> factorial (30.1) Rastreo (última llamada más reciente):… ValorError: n debe ser un entero exacto >>> factorial (30.0) 265252859812191058636308480000000L También debe ser ridiculamente grande : >>> Factorial (1e100) Rastreo (última llamada más reciente): ... OverflowError: n demasiado grande "" "si no n> = 0: aumenta ValueError (" n debe ser> = 0 ") si math.floor (n )! = n: aumenta ValueError ("n debe ser un entero exacto") si n + 1 == n: # captura un valor como 1e300 raise OverflowE rror ("n demasiado grande") resultado = 1 factor = 2 mientras factor <= n: result *= factor factor += 1 return result if __name__ == "__main__": import doctest doctest.testmod()

Como puede ver, el docstring es mucho más grande que el código de función. No promueve la legibilidad.

Pruebas de carrera

DE ACUERDO. Usted escribió sus pruebas de unidad. Para un sistema grande, tendrá decenas / cientos / miles de módulos y clases en posiblemente varios directorios. ¿Cómo se ejecutan todas estas pruebas??

El módulo unittest proporciona varias facilidades para agrupar pruebas y ejecutarlas programáticamente. Echa un vistazo a las pruebas de carga y ejecución. Pero la forma más fácil es el descubrimiento de pruebas. Esta opción se agregó solo en Python 2.7. Antes de la 2.7 se podía usar la nariz para descubrir y realizar pruebas. Nariz tiene algunas otras ventajas como ejecutar funciones de prueba sin tener que crear una clase para sus casos de prueba. Pero a los efectos de este artículo, sigamos con unittest.

Para descubrir y ejecutar sus pruebas basadas en la prueba de unidad, simplemente escriba en la línea de comandos:

python -m unittest descubrir

unittest escaneará todos los archivos y subdirectorios, ejecutará todas las pruebas que encuentre y proporcionará un buen informe y tiempo de ejecución. Si desea ver qué pruebas se están ejecutando, puede agregar el indicador -v:

python -m unittest descubrir -v

Hay varias banderas que controlan la operación:

python -m unittest -h Uso: python -m unittest [opciones] [pruebas] Opciones: -h, --help Muestra este mensaje -v, --verbose Salida verbosa -q, --quiet Salida mínima -f, - failfast Deténgase en el primer fallo -c, --catch Catch control-C y muestra los resultados -b, --buffer Stdout y stderr durante las ejecuciones de prueba Ejemplos: python -m unittest test_module - ejecuta pruebas desde el test_module. - ejecute pruebas desde el módulo.TestClass python -m unittest module.Class.test_method - ejecute el método de prueba especificado [pruebas] puede ser una lista de cualquier número de módulos de prueba, clases y métodos de prueba. Uso alternativo: python -m unittest discover [opciones] Opciones: -v, --verbose Salida verbosa -f, --failfast Detener en el primer fallo -c, --catch Catch control-C y mostrar resultados -b, --buffer Buffer stdout y stderr durante las pruebas de ejecución Directorio -s directorio para iniciar el descubrimiento ('.' Predeterminado) -p Patrón Patrón para hacer coincidir los archivos de prueba ('prueba * .py' predeterminado) -t directorio Directorio del proyecto de nivel superior (predeterminado para iniciar el directorio ) Para el descubrimiento de pruebas, todos los módulos de prueba deben ser importantes desde el directorio de nivel superior del proyecto..

Cobertura de prueba

La cobertura de la prueba es un campo a menudo descuidado. La cobertura significa cuánto de su código es realmente probado por sus pruebas. Por ejemplo, si tiene una función con un si-si no declaración y prueba sólo el Si rama, entonces no sabes si el más La rama trabaja o no. En el siguiente ejemplo de código, la función añadir() Comprueba el tipo de sus argumentos. Si ambos son enteros, simplemente los agrega. 

Si ambas son cadenas, intenta convertirlas en números enteros y las agrega. De lo contrario se plantea una excepción. los test_add () prueba de la función añadir() funciona con argumentos que son enteros y con argumentos que son flotantes y verifica el comportamiento correcto en cada caso. Pero la cobertura de la prueba es incompleta. El caso de argumentos de cadena no fue probado. Como resultado, la prueba pasa con éxito, pero el error tipográfico en la rama donde los argumentos son ambas cadenas no se descubrió (¿ve la "intg" allí?).

import unittest def add (a, b): "" "Esta función agrega dos números a, b y devuelve su suma a y b pueden ser enteros" "" si isinstance (a, int) e isinstance (b, int): return a + b elseif isinstance (a, str) y isinstance (b, str): return int (a) + intg (b) else: raise Exception ('Argumentos no válidos') Prueba de clase (unittest.TestCase): def test_add (self) : self.assertEqual (5, add (2, 3)) self.assertEqual (15, add (-6, 21)) self.assertRaises (Exception, add, 4.0, 5.0) unittest.main () 

Aquí está la salida:

---------------------------------------------------------------------- Prueba de Ran 1 en 0.000s OK Proceso terminado con código de salida 0

Pruebas unitarias prácticas

Escribir pruebas de unidad de fuerza industrial no es fácil ni simple. Hay varias cosas a considerar y compensaciones que hacer.

Diseño para Testability

Si su código es lo que se denomina código de espagueti formal o una gran bola de barro donde se mezclan diferentes niveles de abstracción y cada pieza de código depende de cada otra pieza de código, tendrá dificultades para probarlo. Además, siempre que cambies algo, tendrás que actualizar un montón de pruebas también.

La buena noticia es que el diseño de software adecuado para fines generales es exactamente lo que necesita para la capacidad de prueba. En particular, el código modular bien factorizado, donde cada componente tiene una clara responsabilidad e interactúa con otros componentes a través de interfaces bien definidas, hará que sea un placer escribir buenas pruebas de unidad.

Por ejemplo, nuestro SelfDrivingCar La clase es responsable de la operación de alto nivel del automóvil: ir, parar, navegar. Tiene un calcular_distancia_de_objeto_en_front () Método que aún no se ha implementado. Esta funcionalidad probablemente debería ser implementada por un subsistema totalmente separado. Puede incluir leer datos de varios sensores, interactuar con otros autos automáticos, una pila de visión artificial completa para analizar imágenes de múltiples cámaras.

Veamos cómo funciona esto en la práctica. los SelfDrivingCar aceptará un argumento llamado detector de objetos que tiene un metodo llamado calcular_distancia_de_objeto_en_front (), y delegará esta funcionalidad a este objeto. Ahora, no hay necesidad de hacer una prueba unitaria porque la detector de objetos Es responsable (y debe ser probado) para ello. Aún desea probar por unidad el hecho de que está utilizando el detector de objetos correctamente.

clase SelfDrivingCar (objeto): def __init __ (self, object_detector): self.object_detector self.speed = 0 self.destination = None def _calculate_distance_to_object_to_object

Coste-beneficio

La cantidad de esfuerzo que pone en las pruebas debe estar correlacionada con el costo de la falla, qué tan estable es el código y qué tan fácil es solucionarlo si se detectan problemas en el futuro..

Por ejemplo, nuestra clase de auto-manejo es súper crítica. Si el detener() El método no funciona correctamente, nuestro auto-auto podría matar personas, destruir propiedades y descarrilar todo el mercado de autos con auto-conducción. Si desarrolla un automóvil autónomo, sospecho que sus pruebas de unidad para el detener() El método será un poco más riguroso que el mío.. 

Por otro lado, si un solo botón en su aplicación web en una página que está oculta tres niveles por debajo de su página principal parpadea un poco cuando alguien hace clic en él, puede corregirlo, pero probablemente no agregará una prueba de unidad dedicada para este caso. La economía simplemente no lo justifica.. 

Mentalidad de prueba

Probar la mentalidad es importante. Un principio que utilizo es que cada fragmento de código tiene al menos dos usuarios: el otro código que lo está utilizando y la prueba que lo está probando. Esta regla simple ayuda mucho con el diseño y las dependencias. Si recuerda que tiene que escribir una prueba para su código, no agregará muchas dependencias que son difíciles de reconstruir durante la prueba..

Por ejemplo, suponga que su código necesita computar algo. Para hacer eso, necesita cargar algunos datos de una base de datos, leer un archivo de configuración y consultar dinámicamente algunas API REST para obtener información actualizada. Todo esto puede ser necesario por varias razones, pero poner todo eso en una sola función hará que sea bastante difícil realizar la prueba unitaria. Todavía es posible con la burla, pero es mucho mejor estructurar su código correctamente.

Funciones puras

El código más fácil de probar es puras funciones. Las funciones puras son funciones que acceden solo a los valores de sus parámetros, no tienen efectos secundarios y devuelven el mismo resultado cada vez que se llaman con los mismos argumentos. No cambian el estado de su programa, no acceden al sistema de archivos ni a la red. Sus beneficios son demasiados para contarlos aquí.. 

¿Por qué son fáciles de probar? Porque no hay necesidad de establecer un entorno especial para probar. Simplemente pasa argumentos y prueba el resultado. También sabe que mientras el código bajo prueba no cambie, su prueba no tiene que cambiar. 

Compáralo con una función que lee un archivo de configuración XML. Su prueba tendrá que crear un archivo XML y pasar su nombre de archivo al código bajo prueba. No es gran cosa. Pero suponga que alguien decidió que XML es abominable y que todos los archivos de configuración deben estar en JSON. Se ocupan de sus asuntos y convierten todos los archivos de configuración a JSON. Se ejecutan todas las pruebas incluyendo sus pruebas y todos pasar! 

¿Por qué? Porque el código no cambió. Todavía espera un archivo de configuración XML, y su prueba todavía construye un archivo XML para él. Pero en producción, su código obtendrá un archivo JSON, que no podrá analizar.

Prueba de manejo de errores

El manejo de errores es otra cosa que es fundamental para probar. También es parte del diseño. ¿Quién es responsable de la corrección de la entrada? Cada función y método debe ser claro al respecto. Si es responsabilidad de la función, debe verificar su entrada, pero si es responsabilidad de la persona que llama, la función puede continuar con su trabajo y asumir que la entrada es correcta. La corrección general del sistema se garantizará al realizarle pruebas a la persona que llama para verificar que solo pasa la entrada correcta a su función.

Por lo general, desea verificar la entrada en la interfaz pública de su código porque no necesariamente sabe quién llamará su código. Echemos un vistazo a la conducir() Método del auto-manejo del auto. Este método espera un parámetro 'destino'. El parámetro 'destino' se usará más adelante en la navegación, pero el método de manejo no hace nada para verificar que sea correcto. 

Supongamos que el destino se supone que es una tupla de latitud y longitud. Se pueden realizar todo tipo de pruebas para verificar que sean válidas (por ejemplo, es el destino en el medio del mar). Para nuestros propósitos, solo asegurémonos de que sea una tupla de flotadores en el rango de 0.0 a 90.0 para latitud y de -180.0 a 180.0 para longitud..

Aquí está la actualización SelfDrivingCar clase. Implementé trivialmente algunos de los métodos no implementados porque el conducir() El método llama a algunos de estos métodos directa o indirectamente..

clase SelfDrivingCar (objeto): def __init __ (self, object_detector): self.object_detector = object_detector self.speed = 0 self.destination = None def _accelerate (self): self.speed + = 1 def _decelerate (self): if self. speed> 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () si distancia < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): return True def _calculate_distance_to_object_in_front(self): return self.object_detector.calculate_distance_to_object_in_front() def _get_speed_limit(self): return 65 def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Para probar el manejo de errores en la prueba, pasaré argumentos no válidos y verificaré que se rechacen correctamente. Puedes hacer esto usando el self.assertRaises () método de unittest.TestCase. Este método tiene éxito si el código bajo prueba de hecho genera una excepción.

Vamos a verlo en acción. los prueba de conducción() El método pasa la latitud y la longitud fuera del rango válido y espera que el conducir() método para plantear una excepción.

desde unittest import TestCase desde self_driving_car import SelfDrivingCar class MockObjectDetector (objeto): def calcula_distance_to_object_in_front (self): return 20 clase SelfDrivingCarTaccaccac self.car.speed = 5 self.car.stop () # Verifique que la velocidad sea 0 después de detener self.assertEqual (0, self.car.speed) # Verifique que esté bien para detenerse nuevamente si el auto ya está parado. car.stop () self.assertEqual (0, self.car.speed) def test_drive (self): # Destino válido self.car.drive ((55.0, 66.0)) # Destino no válido rango incorrecto self.assertRaises (Exception, self .car.drive, (-55.0, 200.0))

La prueba falla, porque la conducir() El método no comprueba la validez de sus argumentos y no genera una excepción. Obtiene un buen informe con información completa sobre lo que falló, dónde y por qué.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... FAIL test_stop (untitled.test_self_driving_car.SelfDrivingCarTest)… ok =================== =============================================== FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ------------------------------------------- --------------------------- Traceback (última llamada más reciente): Archivo "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py" , línea 29, en test_drive self.assertRaises (Exception, self.car.drive, (-55.0, 200.0)) AssertionError: Excepción no generada -------------------- -------------------------------------------------- Corrieron 2 pruebas en 0.000s FALLIDAS (fallas = 1)

Para solucionarlo actualicemos el conducir() Método para comprobar realmente el rango de sus argumentos:

unidad de disco (auto, destino): lat, lon = destino si no es (0.0 <= lat <= 90.0): raise Exception('Latitude out of range') if not (-180.0 <= lon <= 180.0): raise Exception('Latitude out of range') self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Ahora, todas las pruebas pasan.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok ----------------------- ----------------------------------------------- Ran 2 tests en 0.000s OK 

Prueba de métodos privados

¿Deberías probar cada función y método? En particular, ¿debería probar métodos privados llamados únicamente por su código? La respuesta típicamente insatisfactoria es: "depende". 

Intentaré ser útil aquí y decirte de qué depende. Usted sabe exactamente quién llama a su método privado: es su propio código. Si sus pruebas para los métodos públicos que llaman a su método privado son exhaustivas, entonces ya las prueba exhaustivamente. Pero si un método privado es muy complicado, es posible que desee probarlo de forma independiente. Usa tu juicio.

Cómo organizar tus pruebas unitarias

En un sistema grande, no siempre está claro cómo organizar sus pruebas. ¿Debería tener un archivo grande con todas las pruebas para un paquete o un archivo de prueba para cada clase? Si las pruebas están en el mismo archivo que el código bajo prueba, o en el mismo directorio?

Aquí está el sistema que utilizo. Las pruebas deben estar totalmente separadas del código bajo prueba (por lo tanto, no uso doctest). Idealmente, su código debería estar en un paquete. Las pruebas para cada paquete deben estar en un directorio de hermanos de su paquete. En el directorio de pruebas, debe haber un archivo para cada módulo de su paquete llamado prueba_

Por ejemplo, si tiene tres módulos en su paquete: módulo_1.py, módulo_2.py y módulo_3.py, debe tener tres archivos de prueba: test_module_1.py, test_module_2.py y test_module_3.py bajo el directorio de pruebas. 

Esta convención tiene varias ventajas. Lo deja claro simplemente explorando directorios que no olvidó probar por completo algún módulo. También ayuda a organizar las pruebas en trozos de tamaño razonable. Suponiendo que sus módulos tienen un tamaño razonable, entonces el código de prueba para cada módulo estará en su propio archivo, que puede ser un poco más grande que el módulo bajo prueba, pero aún así es algo que cabe cómodamente en un archivo. 

Conclusión

Las pruebas unitarias son la base del código sólido. En este tutorial, exploré algunos principios y pautas para las pruebas de unidad y expliqué el razonamiento detrás de varias mejores prácticas. Cuanto más grande sea el sistema que está construyendo, más importantes serán las pruebas unitarias. Pero las pruebas unitarias no son suficientes. También se necesitan otros tipos de pruebas para sistemas a gran escala: pruebas de integración, pruebas de rendimiento, pruebas de carga, pruebas de penetración, pruebas de aceptación, etc..