Hoy en día vivimos en un mundo cada vez más exigente, con mucha competencia y es vital entregar un producto software de forma rápida y segura. Un error en cliente puede conllevar quedarse fuera del proyecto, por lo que el asegurar la calidad es algo fundamental, y se necesita por tanto una estrategia clara para disponer de unas pruebas automatizadas, que aseguren la calidad de nuestras entregas, y permitan una respuesta rápida a las necesidades del mercado.
Es sabido por todos que, cuanto antes se detecte un fallo, menor será su coste de reparación. Si se descubre en la fase de especificación, basta con cambiar el texto del requisito, mientras que si se da en producción, los resultados pueden ser fatídicos, si se trata de un error muy grave.
Existen varios niveles de pruebas:
- Pruebas unitarias: responsabilidad del equipo de Desarrollo.
- Pruebas de integración: acción conjunta de Desarrollo y QA (Quality Assurance).
- Pruebas de sistema: tarea de QA.
Además se pueden diferenciar dos grandes conjuntos de pruebas:
1. Pruebas funcionales:
- Pruebas funcionales.
- Pruebas de humo.
- Pruebas de regresión.
- Pruebas de aceptación.
- Alpha testing.
- Beta testing.
2. Pruebas no funcionales:
- Pruebas de seguridad.
- Pruebas de usabilidad.
- Pruebas de rendimiento.
- Pruebas de internacionalización y localización.
- Pruebas de escalabilidad.
- Pruebas de mantenibilidad.
- Pruebas de instalabilidad.
- Pruebas de portabilidad.
En este primer post queremos centrarnos en las pruebas funcionales, también conocidas como las ‘coded ui test’ (CUITs) o ‘robots’ e indicaremos algunas recomendaciones identificadas por el equipo de Quality Assurance (QA) tras emplear Visual Studio 2012 en la realización de pruebas que garantizan el óptimo desarrollo del software.
A la hora de emplear herramientas software para la ejecución de pruebas funcionales la idea es crear un ‘puzzle’ compuesto de pequeñas pruebas que iremos combinando como piezas simples y para posteriormente poder crear paso a paso tests más complejos. Así mismo, el objetivo es conseguir que dichas “minipruebas” sean lo más configurable posibles. Lo ideal es disponer de un datasource, Data Driven CUIT à CUIT basada en datos: csv, excel, xml, sql, etc.
Para ello hay que configurar cada prueba en particular de forma muy sencilla, por ejemplo se puede poner una línea antes de cada test y especificar si el tipo de acceso es secuencial o aleatorio, tal y como aparece en el ejemplo.
Ejemplo con un datasource de tipo data.csv y con acceso secuencial:
[csharp]
[DataSource(«Microsoft.VisualStudio.TestTools.DataSource.CSV», «|DataDirectory|data.csv», «data#csv», DataAccessMethod.Sequential), DeploymentItem(«data.csv»), TestMethod]
/// y luego acceder con string paramVal = TestContext.DataRow[«Input1»]
[/csharp]
También aconsejamos grabar menos de 10 acciones cada vez, para hacerlo más modular y manejable.
Hay que usar nombres significativos, para luego identificar las pruebas fácilmente, lo que dará más facilidad a la hora de reutilizar y ejecutar dichas pruebas.
Se pueden experimentar problemas a la hora de generar CUITs si la aplicación a probar hace uso de controles de terceros. Esto se debe a que algunos no ofrecen soporte o el soporte es incompleto para CUITs. Existen 4 niveles de soporte:
- Soportar la grabación, reproducción, y validación de propiedades implementando la accesibilidad.
- Añadir la validación de propiedades personalizadas, implementando un proveedor de propiedades.
- Soportar la generación de código, implementando una clase para acceder a las propiedades personalizadas.
- Soportar acciones conscientes de intenciones, implementando un filtro de acciones.
Si se implementan estos cuatro niveles, el soporte será completo.
También para reducir los problemas asociados al uso de controles de terceros, se pueden crear controles personalizados basados en controles de terceros o de .NET que implementen los cuatro niveles de soporte.
A continuación explicamos un ejemplo de automatización de pruebas que han sido configuradas para la realización de pruebas funcionales de nuestro producto Fractalia Media, plataforma de gestión y distribución de contenidos en Digital Signage.
En concreto las pruebas efectuadas se corresponden al:
- Login: Acceso a la herramienta.
- CrearCanal. En nuestro producto Fractalia Media, un canal se basa en una plantilla ya creada en la que se añaden todos los spots. En esta plantilla se encuentran preconfiguradas las zonas de reproducción, la duración de los spots y los efectos de transición, entre otros parámetros.
- CreaGrupoPeriférico: En Fractalia Media, es necesario dar de alta a los dispositivos sobre los que se van a asignar los contenidos multimedia. Para facilitar la distribución de contenidos y listas de reproducción (playlist), es posible crear grupos compuestos por múltiples dispositivos multimedia.
Las pruebas se pueden visualizar en el test explorer y lanzar desde ahí. Tiene multitud de filtros, para poder ejecutar sólo el conjunto que nos interese cada vez.
En el ejemplo concreto que os presentamos se pueden apreciar:
- DEMO_HacerLogin.
- DEMO_CrearCanal.
- DEMO_CrearGrupoPeriferico.
- DEMO_CrearGrupoPerifericoConDatasource.
Lo interesante de estas pruebas, es que se tratan de pruebas atómicas, configurables y se pueden reutilizar. Así para componer la prueba funcional de creación de un canal (DEMO_CrearCanal), se llama primero a la de login (DEMO_HacerLogin). Todas estas pruebas llevan en la cabecera la palabra ‘TestMethod’, para que puedan estar accesibles desde el test explorer y por tanto ser reutilizables.
Ejemplo:
[csharp]
[TestMethod]
public void DEMO_CrearCanal()
{
Login oLogin = new Login();
oLogin.DEMO_HacerLogin(Properties.Settings1.Default.User, Properties.Settings1.Default.Password);
this.UIMap.RC0_CrearCanal();
}
[/csharp]
A continuación se muestra un ejemplo de código para la creación de grupos de periféricos con funciones propias para no repetir el nombre (DEMO_CrearGrupoPeriferico), y otra con un datasource de tipo csv (DEMO_CrearGrupoPerifericoConDatasource), donde se guardan previamente el nombre de los grupos.
Es preciso introducir esta codificación por parte del equipo de desarrollo ya que en la creación de grupos periféricos Fractalia Media no permite la existencia de grupos con el mismo nombre, por lo que existiría una incongruencia al grabar la prueba, en el caso de que se quiere repetir.
De esta forma, conseguimos que se pueden realizar tantas iteraciones de estas pruebas funcionales como se quieran y obtener de forma rápida un gran volumen de datos de grupos de periféricos en base de datos de Fractalia Media, que sirvan como datasource para pruebas de carga, o simplemente para pruebas de regresión, para comprobar que los casos básicos de la aplicación siguen funcionando correctamente.
Ejemplo de Creación de Grupo Periférico
[csharp]
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.CodeDom.Compiler;
using System.Windows.Input;
using System.Windows.Forms;
using System.Drawing;
using Microsoft.VisualStudio.TestTools.UITesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UITest.Extension;
using Microsoft.VisualStudio.TestTools.UITesting.HtmlControls;
using Microsoft.VisualStudio.TestTools.UITesting.WinControls;
using Microsoft.VisualStudio.TestTools.UITesting.WpfControls;
using Keyboard = Microsoft.VisualStudio.TestTools.UITesting.Keyboard;
using Mouse = Microsoft.VisualStudio.TestTools.UITesting.Mouse;
using MouseButtons = System.Windows.Forms.MouseButtons;
using DynamicDataGenerator;
namespace UITestFMedia.DEMO
{
/// <summary>
/// Descripción resumida de CodedUITest1
/// </summary>
[CodedUITest]
public class GrupoPeriferico
{
public GrupoPeriferico()
[TestMethod]
public void DEMO_CrearGrupoPeriferico()
{
//FMedia 5.7 RC0
//Necesario estar en la pantalla del Content Manager
//Con código en esta propia clase
for (int iCont = 0; iCont < 5; iCont++)
{
CrearGrupoPeriferico(ObtainARandomString(5));
}
//Con referencia a una dll de otro proyecto
for (int iCont = 0; iCont < 5; iCont++)
[/csharp]
Ejemplo de Creación Grupo Periférico con DataSource
[csharp]
CrearGrupoPeriferico(DynamicData.CreateRandomString(5));
[DataSource(«Microsoft.VisualStudio.TestTools.DataSource.CSV», «.data.csv», «data#csv», DataAccessMethod.Sequential), DeploymentItem(«data.csv»), TestMethod]
public void DEMO_ CrearGrupoPerifericoConDatasource()
{
//FMedia 5.7 RC0
//Necesario estar en la pantalla del Content Manager
//A partir de datasource: csv
CrearGrupoPeriferico(testContextInstance.DataRow[«Nombre_Grupo»].ToString());
}
public static string ObtainARandomString(int lenght)
{
string returnedString = string.Empty;
int randomNumber = 0;
for (int i = 0; i < lenght; i++)
{
// We create chars from 65 to 90 (ASCII code) A – Z
randomNumber = CreateRandomNumber(65, 90);
returnedString += Convert.ToChar(randomNumber);
}
return returnedString;
}
public static int CreateRandomNumber(int min, int max)
{
Random oRandom = SingletonRandom.Instance.getRandomObject();
int NewNumber = oRandom.Next(min, max);
System.Threading.Thread.Sleep(10);
return NewNumber;
}
public void CrearGrupoPeriferico(string sName)
{
#region Variable Declarations
WinClient uITreeGenericClient = UIMap.UIFractaliaMediaVersioWindow9.UIGestordePantallasWindow.UITreeGenericWindow.UITreeGenericClient;
WinMenuItem uIGruponuevoMenuItem = UIMap.UIItemWindow.UIDropDownMenu.UIGruponuevoMenuItem;
WinEdit uITextBoxEdit = UIMap.UINuevogrupodepantallaWindow.UITextBoxWindow.UITextBoxEdit;
WinButton uIAceptarButton = UIMap.UINuevogrupodepantallaWindow.UIAceptarWindow.UIAceptarButton;
#endregion
// Derecha-Clic ‘Tree Generic’ cliente
Mouse.Click(uITreeGenericClient, MouseButtons.Right, ModifierKeys.None, new Point(149, 9));
// Clic ‘Grupo nuevo’ elemento de menú
Mouse.Click(uIGruponuevoMenuItem, new Point(42, 13));
uITextBoxEdit.Text = sName;
// Clic ‘Aceptar’ botón
Mouse.Click(uIAceptarButton, new Point(49, 11));
}
///
///Obtiene o establece el contexto de las pruebas que proporciona
///información y funcionalidad para la ejecución de pruebas actual.
///
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
private TestContext testContextInstance;
public UIMap UIMap
{
get
{
if ((this.map == null))
{
this.map = new UIMap();
}
return this.map;
}
}
private UIMap map;
}
public class SingletonRandom
{
private static SingletonRandom instance = null;
private Random oRandom;
protected SingletonRandom()
{
oRandom = new Random(unchecked((int)(DateTime.Now.Ticks)));
}
public static SingletonRandom Instance
{
get
{
if (instance == null)
instance = new SingletonRandom();
return instance;
}
}
public Random getRandomObject()
{
return oRandom;
}
}
}
[/csharp]
Leave a Comment