Skip to content

Testing

Índice

0:00:00 - INTRODUCCIÓN 0:09:49 - CREACIÓN DE TEST 0:21:44 - ESCRIBIENDO TESTS 0:44:02 - APILCANDO TDD 1:06:18 - EJEMPLO TESTS EN PROYECTOS 1:12:25 - CONCLUSIONES

INTRODUCCIÓN -

El testing es una disciplina dentro del desarrollo.

Independientemente de eso, todo desarrollador debe ser capaz de realizar los diferentes tests.

CREACIÓN DE TEST -

Lo primero:

  • ¿Qué vamos atestar ?
  • Cuándo?

1739543924081

1739543986705

Test Unitarios.

Función, acción en concreto. Pequeños trozos o piezas de nuestro código.

El ejemplo en construcciones SOLID es el test unitario sobre un controlador.

Probamos que la función no tiene problemas.

Test de Integración

Destinado a circuito completo, dada una ruta, comprobará que el sistema devuelve la vista que corresponde, con los datos, con el frontal, etc.

Importancia

Muchas veces, al inicio, se obvia la importancia a lahora de trabajar en casa, solos, cuando estamos empezando en pequeños proyectos.

Pero en cualquier trabajo, los test son obligatorios.

Formato de trabajo TDD

  • Se escriben los tests ANTES de hacer lo que vamos a desarrollar

1739544405847

Formato de trabajo después del desarrollo

  • Se hacen siempre antes de cada commit.

Diferencia en cómo se prueban los tests:

Podemos hacer:

  1. Crear los datos para que se guarden sólo en memoria (seeders / fáctories) y no en database
  2. Env especial para testing, para correr los test de la aplicación sin ensuciar las bases de datos.

ESCRIBIENDO TESTS -

Creamos nuevo proyecto

composer create-project laravel/laravel testexample

Ya tenemos el proyecto ya preparado, con la tabla usuario preparada para hacer estos primeros tests.

PHP Unit

En Laravel existe PHPUnit y Pest, vamos a comenzar por PHPUnit por ser el más clásico. Ambos tienen el mismo fin, pero con sintáxis distintas.

Vamos por tanto a crear el primero.

Convención Pascal Case: NombreDescriptivoTest

Por defecto, si no especificamos nada, los test creados serán Feature Test.

php artisan make:test UserTest

Si quisieramos que sea unitario, le debemos añadir --unit al final

php artisan make:test UserTest --unit

Creamos el primero, el Feature, y lo vemos:

1739545109748

Y os crea en la carpeta /testsFeature con la siguiente estructura base:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class UserTest extends TestCase
{
    /**
     * A basic feature test example.
     */
    public function test_example(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

Ambos test, aunque en carpetas separadas, tienen el mismo formato.

Funcionamiento

Dentro de un test, vamos a recurrir a métodos que van a ejecutar instrucciones automáticas.

  1. Vete a esta ruta
  2. Haz esto. ...
  3. Comprueba lo otro..

Por ejemplo, en el test Feature de ejemplo que carga Laravel, prueba una ruta raiz con get, y luego espera una respuesta 200. (ok)

Si todo va bien, el test pasará. Si el comportamiento NO es el esperado, se detendrá y nos avisará.

 /**
     * A basic test example.
     */
    public function test_the_application_returns_a_successful_response(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }

Así, tendremos dos grandes bloques de instrucciones

Instrucciones para pedirle que haga algo

Instrucciones ASSERT para comprobar algo

Vamos a crear primero un test que liste y que compruebe uno de los detalles


Hacemos el primer test

Primer test va a ser que podemos listar todos los usuarios de nuestro sistema.

Las funciones comienzan por test_nombre descriptivo.

Creamos las funciones de Testing

test_get_user_list

1739545577529

test_get_users_list

Vamos a pedir una serie de comprobaciones a un listado de usuarios API, y nos fijamos en las funciones como

  • assertStatus
  • assertJsonStructure
  • assertJsonFragment
  • assertJsonCount
  • Habrían muchas más comprobaciones, si se ha creado, si falta por ser borrado ...

Y en los comentarios del código de esta primera función test_:

public function test_get_users_list(): void
    {
        $response = $this->get('/users');

        //Qué queremos que ocurra?
        $response->assertStatus(200); //Primero, comprobamos que la respuesta sea 200

        //comprobamos que el contenido de la respuesta sea el esperado
        $response->assertJsonStructure([
                [
                    'id',
                    'name',
                    'email',
                    'email_verified_at',
                    'created_at',            
                    'updated_at',
                ],
            ]);

            //Ahora vamos a preguntar si nos devuelve un dato que sabemos que nos debe devolver.
            //En este caso, sabemos que debe devolver un usuario con nombre Antonio
            //Si no lo devuelve, la prueba fallará
        $response->assertJsonFragment([
                'name' => 'Antonio',
        ]);

        //Por último, comprobamos que el número de usuarios devueltos sea el esperado   
        //Sabemos, cuando la poblemos, cuántos usuarios hay, y si no devuelve el número esperado, la prueba fallará
        //Si poblamnos con 4 usuarios, debería devolver 4
        $response->assertJsonCount(4);
    }

test_get_user_detail

Vamos a crear una segunda función test_get_user_detail (personalizarlo con vuestro nombre)

public function test_get_user_detail(): void
    {
        $userId = 1; // Assuming we are testing with user ID 1
        $response = $this->get("/users/{$userId}");

        // Check that the response status is 200
        $response->assertStatus(200);

        // Check that the response has the expected structure
        //Ahora no es un array, si no que es solo un dato
        $response->assertJsonStructure([
            'id',
            'name',
            'email',
            'email_verified_at',
            'created_at',
            'updated_at',
        ]);

        // Check that the response contains the expected user data
        //Aquí comprobamos que el usuario con ID 1 se llame Antonio, no necesitamos contar el número de elementos
        $response->assertJsonFragment([
            'id' => $userId,
            'name' => 'Antonio', // Assuming the user with ID 1 is named Antonio
        ]);
    }

test_get_non_existing_user_detail

  • Hasta aquí, hemos visto HAPPY Path, es decir, todo funciona correctamente
  • Ahora vamos a ver cómo se comporta la aplicación cuando algo falla
  • Probamos qué ocurre cuando pedimos datos de un usuairo que no existe

test_get_non_existing_user_detail

//Hasta aquí, hemos visto HAPPY Path, es decir, todo funciona correctamente
    //Ahora vamos a ver cómo se comporta la aplicación cuando algo falla

    //Probamos qué ocurre cuando pedimos datos de un usuairo que no existe
    public function test_get_non_existing_user_detail(): void
    {
        $nonExistingUserId = 9999; // Assuming this user ID does not exist
        $response = $this->get("/users/{$nonExistingUserId}");

        // Check that the response status is 404
        $response->assertStatus(404);
    }

Tebemos que tener en cuenta que muchas emnpresas comienzan por los test y codificamos para satisfacer esos test (TDD)

Es lo que hemos hecho, vamos a correrlo y fallará, porque la app está vacía.

Eliminamos los test de ejemplo ExampleTest...


Definimos la estrategia. ENV Testing.

Podemos crear seeders de testeo.

Pero vamos a definir nuestro .env de testing copiando, pegando y renombrando el .env

1739546975442

Diferente al vídeo

Ten en cuenta que en el vídeo crea una base de datos con Mysql en lugar de con sqlite.

Cambiar en el .env el motor a: DB_CONNECTION=sqlite_testing

En config / database, agrega otra conexión para el env.testing así:

 'sqlite_testing' => [
        'driver' => 'sqlite',
          'database' => env('DB_DATABASE', database_path('databasetesting.sqlite')),
          'prefix' => '',
         'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        ],

1739547592281

.env.testing

DB_CONNECTION=sqlite_testing

DB_DATABASE=database/databasetesting.sqlite

Migramos el entorno testing con --env=testing

php artisan migrate --env=testing

Comprobamos que se ha ejecutado con éxito:

1739547800159


APLICANDO TDD -

Vamos a explicar que estos test heredan de la clase base TestCase que les da el comportamiento.

Vamos a ver qué nos dice el test (fallar) cuando los corramos:

php artisan test

1739548026779

1739548065169

Como vemos, nos da info de todo lo que ha pasado en la ejecución y por qué ha fallado

  1. Esperaba 200 y ha recibido un 404, con la instrucción que ha fallado
  2. Mismo motivo (ya que no hemos desarrollado nada) 200 / 404

Ahora vamos a solucionar los errores.

¿Cómo corregimos? vamos a hacerlo en API introduciendo lo necesario.

1º Cambiamos las rutas de los tests a API

span

  • $response = $this->get("api/users/{$userId}");
  • $response = $this->get("api/users/{$nonExistingUserId}");

Escribimos ruta Users en api.php,

Instalamos api

php artisan install:api

Migramos de nuevo con el test

php artisan migrate --env=testing

Generamos controlador

php artisan make:controller UserController

Y, por ahora, devolvemos un Hello World así

class UserController extends Controller
{
    //devuelve index hello world
    public function index()
    {
        return 'Hello World';
    }
}

Ahora, ya fallará el primer test pero por otro motivo, el status 200 está superado. Sólo nos falta la estructura.

1739549464051

Devolvemos el usuario esperado

Vamos a corregir lo anterior en el controlador de Users

class UserController extends Controller
{
    //devuelve index hello world
    public function index()
    {
        //return 'Hello World';
        // ahora devolvemos todos los usuario en JSON
        $users = User::all();
        return response()->json($users);
    }
}

Creamos un seeder para poblar

php artisan make:seeder UserSeeder

/**
     * Run the database seeds.
     */
    public function run(): void
    {
        //Creamos 4 usuario uno se debe llamar Antonio
       User::factory()->create([
            'name' => 'Antonio',
        ]);
       User::factory(3)->create();
    }

Creamos una nueva función en el testing para resetear las migraciones y poblarlas

Podríamos haberlo hecho a mano, pero así se automatiza más y no se nos olvida añadirle el entorno de testing (--env=testing)

  • Ya que se ejecutará en la base de datos de testing por defecto
 public function test_set_database_config(): void
    {
        Artisan::call('migrate:reset');
        Artisan::call('migrate');
        Artisan::call('db:seed');

        $response = $this->get('/');
        $response->assertStatus(200);

    }
  • Declaramos la ruta raiz en api.php para que no nos de errores.
  • Agregamos el Seeder al Database:
  • $this->call(UserSeeder::class);

Y comprobamos de nuevo, con un sólo fallo, vemos cual:

1739550462053

1739550514323

Nos ha fallado a la hora de devolvernos un detalle del ID 1

1739550846501

Vemos la base de datos si es correcta.

1739550488871

Pero claro, no hemos definido la vista para el detail (show)

Controlador Users/id detail

Y es que no lo tenemos definido ni en la ruta ni en el controlador.

Lo solucioinamos, en la API, añadimos:

Route::get('/users/{id}', [UserController::class, 'detail']);

Y en el controlador:

//devuelve un usuario por id
    public function detail($id)
    {
        $user = User::find($id);
        return response()->json($user);
    }

Comprobamos tras finalizar.

Pero vemos que el test donde el usuario no existe, falla

1739551163531

Para ello, hay que comprobar y devolver un 404

Volvemos a corregir:

  public function detail($id)
    {
        $user = User::find($id);

    //devolvemos 404 si el usuario no existe
        if (!$user) {
            //abort(404);
            return response()->json(['error' => 'User not found'], 404);
        }

        return response()->json($user);
    }

Y volvemos a probar y, si todo ha ido bien, hemos pasado los tests:

Ahora sí, podemos hacer el COMMIT a nuestro repositorio.

1739551282120


EJEMPLO TESTS EN PROYECTOS -

Segundo formato de trabajo

Hacer los test en un prouecto ya realizado, como por ejemplo vuestro PROYECTO

  • Guardar copia primero.

En el vídeo muestra un ejemplo con un proyecto de ellos.

CONCLUSIONES -


PROYECTO: Plan de Pruebas y documentación final.

Debemos realizar y documentar nuestro plan de pruebas del proyecto y ejecutarlo en el Sprint y presentación final.

1739548210225

Referencias