Las DUnit y el test Programming
En este artículo profundizaré un poco más sobre los test unitarios y su funcionamiento. Como expliqué en su día (con el Test Driven Development), tenemos que realizar las pruebas unitarias de nuestros módulos y automatizarlas. Para éste post, crearé un ejemplo sencillo con una pequeña clase llamada TArithmetic que tiene unos cuantos métodos matemáticos, y luego lanzaré un pequeño test unitario para ver si el módulo está correctamente diseñado o no.Cómo funciona el test unitario?
Las xUnit, crean un test por cada clase que tenga, por lo tanto testa los métodos de mi clase.
- Ejemplo clase TArithmetic:
unit Arithmetic;
interface
type
TArithmetic = class(TObject)
private
Fx: integer;
Fy: integer;
procedure Setx(const Value: integer);
procedure Sety(const Value: integer);
public
property x: integer read Fx write Setx;
property y: integer read Fy write Sety;
function plus(): integer;
function minus(): integer;
function multiply(): integer;
function divide(): integer;
function floatDivision(): double;
function sinus(): double;
function cosinus(): double;
end;
implementation
{ TArithmetic }
function TArithmetic.cosinus: double;
begin
result := cos(self.Fx);
end;
function TArithmetic.divide: integer;
begin
result := Self.Fx div Self.Fy;
end;
function TArithmetic.floatDivision: double;
begin
result := Self.Fx / Self.Fy;
end;
function TArithmetic.minus: integer;
begin
result := Self.Fx - Self.Fy;
end;
function TArithmetic.multiply: integer;
begin
result := Self.Fx * Self.Fy;
end;
function TArithmetic.plus: integer;
begin
result := Self.Fx + Self.Fy;
end;
procedure TArithmetic.Setx(const Value: integer);
begin
Fx := Value;
end;
procedure TArithmetic.Sety(const Value: integer);
begin
Fy := Value;
end;
function TArithmetic.sinus: double;
begin
result := sin(self.Fx);
end;
end.
- Creando el Test Project:
Selecciono Test Project, y lo añado a mi grupo de proyectos:
Su configuración es bastante sencilla, primero solo tenemos que configurar la ruta donde está nuestro proyecto y seleccionar la interfaz GUI:
Una vez tenemos el Test Project creado, tenemos que añadir una Test Case. Seleccionamos el Proyecto del test que hemos añadido, y luego nos dirigimos a File -> New -> Other y seleccionamos "Test Case".Seleccionamos los métodos que queremos para nuestro test y luego le damos un nombre y lo asociamos a nuestro proyecto:

Una vez el asistente ha acabado, nos genera una unit llamada TestArithmetic, con el siguiente contenido:unit TestArithmetic;
{
Delphi DUnit Test Case
----------------------
This unit contains a skeleton test case class generated by the Test Case Wizard.
Modify the generated code to correctly setup and call the methods from the unit
being tested.
}
interface
uses
TestFramework, Arithmetic;
type
// Test methods for class TArithmetic
TestTArithmetic = class(TTestCase)
strict private
FArithmetic: TArithmetic;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Testplus;
procedure Testminus;
procedure Testmultiply;
procedure Testdivide;
procedure TestfloatDivision;
procedure Testsinus;
procedure Testcosinus;
end;
implementation
procedure TestTArithmetic.SetUp;
begin
FArithmetic := TArithmetic.Create;
end;
procedure TestTArithmetic.TearDown;
begin
FArithmetic.Free;
FArithmetic := nil;
end;
procedure TestTArithmetic.Testplus;
var
ReturnValue: Integer;
begin
ReturnValue := FArithmetic.plus;
// TODO: Validate method results
end;
procedure TestTArithmetic.Testminus;
var
ReturnValue: Integer;
begin
ReturnValue := FArithmetic.minus;
// TODO: Validate method results
end;
procedure TestTArithmetic.Testmultiply;
var
ReturnValue: Integer;
begin
ReturnValue := FArithmetic.multiply;
// TODO: Validate method results
end;
procedure TestTArithmetic.Testdivide;
var
ReturnValue: Integer;
begin
ReturnValue := FArithmetic.divide;
// TODO: Validate method results
end;
procedure TestTArithmetic.TestfloatDivision;
var
ReturnValue: Double;
begin
ReturnValue := FArithmetic.floatDivision;
// TODO: Validate method results
end;
procedure TestTArithmetic.Testsinus;
var
ReturnValue: Double;
begin
ReturnValue := FArithmetic.sinus;
// TODO: Validate method results
end;
procedure TestTArithmetic.Testcosinus;
var
ReturnValue: Double;
begin
ReturnValue := FArithmetic.cosinus;
// TODO: Validate method results
end;
initialization
// Register any test cases with the test runner
RegisterTest(TestTArithmetic.Suite);
end.
Como podéis ver, ha generado una llamada a cada uno de nuestros métodos y la asignación de variables para poder realizar el test. Si ahora iniciamos el proyecto y hacemos un run para pasar las pruebas unitarias sucede lo siguiente:
Como podéis ver, no ha pasado las pruebas porqué hay divisiones por 0. Entonces nuestra clase no está protegida contra este tipo de valores, y hay que protegerla.Si suponemos que el valor del divisor puede ser 0, entonces tenemos que indicarle al test que posiblemente le llegará una excepción de este tipo. Para realizar esto, tenemos que hacer lo siguiente:
unit TestArithmetic;
uses
SysUtils;
procedure TestTArithmetic.Testdivide;
var
ReturnValue: Integer;
begin
StartExpectingException(EDivByZero);
ReturnValue := FArithmetic.divide;
StopExpectingException();
end;
procedure TestTArithmetic.TestfloatDivision;
var
ReturnValue: Double;
begin
StartExpectingException(EInvalidOp);
ReturnValue := FArithmetic.floatDivision;
StopExpectingException();
end;
y poner el tipo de excepción que esperamos que suceda al realizar la operación del método. Aunque nos aparezca el mensaje:
luego las pruebas unitarias pasan los diferentes test:
También podemos especificar las operaciones añadiendo valores al método y haciendo la comparación. Por ejemplo si en la operación suma sabemos que el resultado es un valor concreto lo podemos comparar con el valor devuelto del método y así podemos ver si el resultado es correcto o no. A modo de ejemplo tendríamos:procedure TestTArithmetic.Testplus;
var
ReturnValue: Integer;
begin
FArithmetic.x := 10;
FArithmetic.y := 10;
ReturnValue := FArithmetic.plus;
checkEquals(ReturnValue, 20);
end;
Si miramos la respuesta del test unitario obtenemos:
Tenemos un fallo, esperábamos un 21 (a modo de ejemplo), pero el método devolvió un 20. Con ésto nos podemos imaginar como podríamos testar los otros métodos. Después de montar todos los test, las pruebas unitarias tienen que pasar sin problemas. Luego mientras hacemos cambios en nuestro código tenemos que tener en cuenta que las pruebas unitarias tienen que pasar y debemos tener la mayoría de los casos pensados. Lo interesante del TDD, es que primero se genera el test y luego se crea la clase, de esta manera el desarrollo de la clase no pierde el rumbo y solo creamos aquello que necesitamos.- Cómo funciona el xtesting framework?
El programa lanza threats y los lanza en paralelo para ir más rápido. Los test han de ser repetibles, por lo tanto en cada método hay que crear el estado para el test, ejecutar el test y luego destruir el estado. Para cada método del test, se llama al SetUp y al TearDown. Cada vez que se hace una prueba, se llama a una nueva instáncia del Test/Case, por lo tanto todo es bastante independiente.
El TestSuite es agrupar un montón de TestCase. Registrando tu TestCase en una TestSuite nos permite hacer lo mismo. Lo que te propone el TestSuite es hacer un test por método, pero lo que te sugieren es hacer un test por expectación.
Los test, hay que automatizarlos. Por pequeño que sea el cambio, ejecuto el test. En los siguientes post, hablaré del Extreme Programming y de diversos sistemas para realizar pruebas unitarias y de integración. Existen muchas herramientas tales como FIT (Framework for integrated test), Mock Objects, etc. Y sobretodo tenemos que tener en cuenta los bad smells en nuestro código, como el DRY (Don't Repeat Yourself) y el YAGNI (You Ain't Gonna Need It) que hablaré un poco más de éstos en los siguientes artículos.
Espero que os sirva de ayuda!.
- Enlaces de interés:
Pruebas automatizadas con DUnit.
%20applied%20to%20Transformer%20models%20in%20machine%20learning.%20The%20image%20shows%20a%20neural%20networ.webp)

Comments
Post a Comment