Logo-Spanish

GR

 

 

 

QA AUTOMATION ENGINEER

 

Parametrización o Aleatorización

"Locura es hacer la misma cosa una y otra vez esperando obtener diferentes resultados".

Albert Einstein

Hace unos días me encontraba revisando un script que corre diariamente. Había fallado un Caso de prueba y decidí usar un usuario diferente para correr de nuevo el script manualmente ya que el usuario anterior ya no cumplía con ciertos pre-requisitos. La medida resultó contraproducente, al correr de nuevo el script con el nuevo usuario lanzó 3 errores más. Este script se dedica a hacer un barrido en un sitio en 12 diferentes idiomas buscando varios strings o textos confirmando que estén en el idioma adecuado.

Al analizar que estaba pasando, me di cuenta que había fallado en algunos checks en varios strings o textos. Lo cual me pareció muy raro, porque no era el problema original y además yo tenía algo de tiempo corriendo ese script sin problema alguno y nunca había fallado en esos puntos.

Al revisar qué había diferente o qué había cambiado, caí en cuenta que la única diferencia entre ambos usuarios era el sexo. Cuando fui a revisar las diferencias entre los textos donde falló el script para el Usuario A como para el Usuario B encontré que había una discrepancia en la traducción para ambos usuarios en cierto idioma, era un texto que debía ser el mismo cuando el usuario era hombre que cuando el usuario era mujer y eso no estaba pasando. Estaba mostrando textos diferentes para cada sexo.

Entonces, ¿Qué paso? ¿Porqué no lo había detectado antes?.

Mi script no era lo suficientemente variable para detectarlo, tenía todo ese tiempo corriendo el script con sólo usuarios hombres, no había pensado en todas las variables envueltas, me hacia falta la parametrización o aleatorización de mis pruebas. Pero, ¿Qué es eso? ¿Cómo funciona?

Definición.

¿Qué es? Haciendo una búsqueda en internet podemos encontrar varias definiciones y algunos "sinónimos" o formas diferente de llamarlo como, pruebas controladas por datos (Data-Driven Testing), Aleatorización, etc. Algunas de las definiciones que podemos encontrar en internet:

"parameterization is passing multiple input values instead of constant values to verify the functionality."

"The problem with testing any piece of software is that complexity blows up pretty quickly. The fact is, you can't test all possible combinations of parameters... This is where parameterization is used"

"Checking the same operations with multiple sets of data"

"Enables you to expand the scope of a basic test or component by replacing fixed values with parameters. This process, known as parameterization, it greatly increases the power and flexibility of your test or component."


En resumen, parametrización, aleatorización o prueba controladas por datos es un procedimiento que le permite al Tester revisar más a profundidad algo, por medio de la variacion aleatoria de datos durante la ejecución. Pero, ¿Porque decidí hablar de parametrización?.

¿Para qué necesitamos la parametrización?

He visto gran cantidad de posts de blog relacionados a la automatización allá afuera que nos hablan de qué herramienta usar, como usarla, trucos para hacer tal o cual paso, cómo escribir scripts escalabes, etc. Sin embargo, he encontrado muy pocos que hablen acerca de la importancia de la parametrización dentro de la automatización o dentro del campo del Testing en general.

Creo que se nos olvida que estamos aquí, no para programar scripts, sino para probar aplicaciones, para asegurarnos que son aplicaciones de calidad y seguras. Por lo tanto no podemos concebir la automatización sola como herramienta de pruebas, si bien, nos puede ayudar a ahorrarnos un poco de tiempo por sí sola, sino incluímos un buen grado de parametrización en esas pruebas automaticas no nos es realmente de gran ayuda.

Por lo tanto, cuando estemos trabajando en nuestros proyectos, debemos siempre tomar la parametrización en cuenta, e invertir menos tiempo desarrollando scripts y mas tiempo parametrizándolos, por ejemplo. Supongamos que queremos automatizar las pruebas diarias que hacemos en el login de Gmail. ¿Qué casos de prueba aplicaríamos?

1. Intentar entrar con datos (email y password correctos) esperando entrar correctamente

2. Intentar entrar sin password, esperando no entrar y recibir mensaje de error

3. Intentar entrar sin email, esperando lo mismo que en el caso anterior

4. Intentar entrar con email incorrecto, esperando error tambien

5. Con password incorrecto, esperando error

6. Con ambos datos incorrectos, esperando error.

y luego recordaríamos probar mas a fondo las expresiones regulares que rigen las validaciones de ambos campos y probaríamos algo como:

7. Intentar con email con terminacion .com.mx

8. Con password con caracteres #"$%

y podríamos tal vez, agregar unos cuantos más. Nuestros test lucirían algo como:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re

class Gmailexample(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.implicitly_wait(30)
        self.base_url = "https://accounts.google.com/"
        self.verificationErrors = []
        self.accept_next_alert = True
    
    def test_gmail_testcase1_correct(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("examplepassword")
        driver.find_element_by_id("signIn").click()
        self.assertEqual("Recibidos (1)", driver.find_element_by_link_text("Recibidos (1)").text)

    def test_gmail_testcase2_incorrect_email(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleincorrectemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("examplepassword")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))

    def test_gmail_testcase3_incorrect_password(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("exampleincorrectpassword")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
		
    def test_gmail_testcase4_noemail(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("examplepassword")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
		
    def test_gmail_testcase5_nopassword(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
		
    def test_gmail_testcase6_bothincorrect(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleincorrectemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("exampleincorrectpassword")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
		
    def test_gmail_testcase7_extendedemail(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@gmail.com.mx")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("examplepassword")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
		
    def test_gmail_testcase8_weirdchar_password(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@gmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("3xmpl3p$$word")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))

    def test_gmail_testcase8_different_domain(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
        driver.find_element_by_id("Email").send_keys("exampleemail@hotmail.com")
        driver.find_element_by_id("Passwd").clear()
        driver.find_element_by_id("Passwd").send_keys("3xmpl3p$$word")
        driver.find_element_by_id("signIn").click()
        self.assertTrue(self.is_element_present(By.ID, "errormsg_0_Passwd"))
    
    def is_element_present(self, how, what):
        try: self.driver.find_element(by=how, value=what)
        except NoSuchElementException, e: return False
        return True
    
    def is_alert_present(self):
        try: self.driver.switch_to_alert()
        except NoAlertPresentException, e: return False
        return True
    
    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to_alert()
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally: self.accept_next_alert = True
    
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)

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

 

Sin embargo, nos estamos olvidando de muchas cosas.

a). Navegadores, tenemos al menos 3 mas populares (si sacamos a Opera del juego) cada uno de esos tiene allá fuera al menos 3 o 4 versiones

b). Valores maximos y minimos de caracteres permitidos para ambos campos

c). Caracteres permitidos de ambos campos

d). probar tanto con solo el usuario como con el correo completo, etc.

No es imperativo, tener que modificar nuestro script y terminar con 1000 lineas de código para cumplir con la meta. Como en mi caso, al iniciar este artículo, no fue necesario editar el script que ya estaba corriendo, sino editar los datos de los que el script se alimenta.

En el caso anterior de gmail, podríamos generar un csv de la forma

 

email1@gmail.com,password1,success_assert
email2@gmail.com,password2,failed_assert
email3@gmail.com,password3,failed_assert
email4@gmail.com,password4,failed_assert
email5@gmail.com,password5,failed_assert
email6@gmail.com,password6,failed_assert
.
.
.
email1000@gmail.com,password1000,failed_assert

Donde cada una de las combinaciones email, password prueben un caso específico y nuestro codigo de prueba quedaría algo como:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re

class Gmailexample(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Firefox()
        self.driver.implicitly_wait(30)
        self.base_url = "https://accounts.google.com/"
        self.verificationErrors = []
        self.accept_next_alert = True
		
		filename = 'test.csv'
		line_number = 1
		with open(filename, 'rb') as f:
			mycsv = csv.reader(f)
			mycsv = list(mycsv)
			self.username=mycsv[line_number][0]
			self.password=mycsv[line_number][1]
			self.assert=mycsv[line_number][2]
			self.verificationErrors = []
    
    def test_gmail_testcase1_correct(self):
        driver = self.driver
        driver.get(self.base_url + "/ServiceLogin?sacu=1&scc=1&continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=es&service=mail")
        driver.find_element_by_id("Email").clear()
		for index in range(len(self.username)):
			driver.find_element_by_id("Email").send_keys(self.username)
			driver.find_element_by_id("Passwd").clear()
			driver.find_element_by_id("Passwd").send_keys(self.password)
			driver.find_element_by_id("signIn").click()
			self.assertEqual(self.assert, driver.find_element_by_link_text(self.assert).text)

    def is_element_present(self, how, what):
        try: self.driver.find_element(by=how, value=what)
        except NoSuchElementException, e: return False
        return True
    
    def is_alert_present(self):
        try: self.driver.switch_to_alert()
        except NoAlertPresentException, e: return False
        return True
    
    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to_alert()
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally: self.accept_next_alert = True
    
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)

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

 

de esta manera parametrizaríamos mejor nuestro script, podriamos tomar una gran cantidad de datos y utilizarlos y realizar una cantidad de pruebas mayor con diferentes variables. Además, agregar o quitar casos de prueba sería tan sencillo como remover o agregar una línea de datos en el documento CSV y como una ventaja extra podemos ver que nuestro código se reduce significativamente.

En mi próximo post les mostraré un par de herramientas bastante interesantes que nos pueden ser muy utiles y con las cuales podemos aleatorizar nuestros datos, que manejan diferente ángulo de ataque al problema de obtener datos aleatorios para usar en pruebas automatizadas.