testeando dependencias difíciles – Sin Mock

Hace unos días estuve “intentado” mejorar una librería para poder enviar comandos a Varnish y me encontré con unos cuantos problemillas para poder testar la librerías y así llegar a refactorizarla.

Varnish Cache

Según la wikipedia Varnish Cache es un acelerador de aplicaciones web, también conocido como caché de proxy HTTP inversa. Se instala delante de cualquier servidor HTTP y se configura para almacenar en el caché del servidor una copia del recurso solicitado. Esta ideado para aumentar el rendimiento de aplicaciones web con contenidos pesados y APIs altamente consumidas.

Básicamente lo que hace Varnish es ante una petición HTTP guardar la respuesta a esa petición y cada vez que un cliente realice la misma petición devolver el contenido guardado. Esto hace que nuestras aplicaciones multipliquen su velocidad por muchísimo, pero a la vez acarrea algunos problemas para invalidar la caché.

Motivación / el marrón

Un ejemplo básico sería un blog, si utilizamos Varnish para cachear los post ganaremos mucha en performance,pero que pasa si editamos un post ya publicado, para corregir una errata, para actualizarlo o lo que sea . Sería necesario descachear, pero no descachearlo todo, sino solo dicho post.

Una opción sencilla sería cada vez que editamos un post ejecutar el comando

/etc/init.d/varnish restart
  • Con esto reiniciamos Varnish y limpiamos todas las páginas cacheadas :).
  • El problema es que al descachearlo todo, ese performance que ganamos, lo perdemos cada vez que reiniciamos varnish 😦

La posible solución

Una solución pasaría por utilizar una petición PURGE utilizando CURL, para ello podemos crear peticiónes como esta

curl -X PURGE http://www.jesuslc.com/post/id/128384

Hacerlo a mano puede estar bien, o utilizar algún plugin para hacer peticiones desde el navegador,… pero al final acaba resultado un poco tedioso.

Buscando en internet

Buscando un poco por Internet encontré miles de soluciones para esto, desde un plugin para wordpress pasando por miles de respuestas en stackoverflow, unas cuantas soluciones en PHP, incluso unas cuantas librerías. De todas ellas esta https://github.com/timwhitlock/php-varnish tenía buena pinta y funcionó bien.

Al actualizar a Varnish 4 teníamos que actualizar la librería para adaptarnos a los cambios de Varnish por ello he publicado está librería https://github.com/jeslopcru/VarnishAdmin

La solución

Basándome en https://github.com/timwhitlock/php-varnish he reescrito un poco la librería y la he publicado en github. Uno de los retos a la hora de actualizar la librería ha sido testearla por completo para poder realizar cambios de una manera sencilla.

Los Retos

La librería de timwhitlock lo que hace es conectarse a Varnish a través de un socket y enviar comandos, de la misma manera que si utilizamos varnishadm, lo difícil a la hora de testar esta librería es poder hacer test unitarios sin necesidad de conectarnos a Varnish. Así pues los retos son:

  • Tener test unitarios
  • Crear una interfaz sencilla de utilizar
  • Utilizar composer para que sea sencillo de integrar
  • integrarlo con scrutinzer y Travis para poder seguir mejorando

Y lo más difícil, que todo siga funcionando 🙂

Testeando lo intesteable

Lo primero que tenía en mente cuando empecé a adaptar la librería de timwhitlock a Varnish 4 era la robustez, necesitaba que fuese robusta y que siguiese funcionando en Varnish 3, por lo que primero tenía que tener una serie de test con los que tener cobertura para poder refactorizar.

Para ello en vez de utilizar Mocks cree una clase llamada VarnishAdminSocketFake que extendiese de VarnishAdminSocket para así poder reescribir los métodos que necesitase para que fuese más sencillo testear.

Recubriendo los métodos

Como hemos comentado, empezamos recubriendo la clase, así en los tests intanci´sbamos esta nueva clase, con lo que era más sencillo reescribir métodos para no tener que utilizar Mocks.

class VarnishAdminSocketTest extends PHPUnit_Framework_TestCase
{

    public function testConstructDefaultValues()
    {
        $admin = new VarnishAdminSocketFake();
        $this->assertSame($admin->host, '127.0.0.1');
        $this->assertSame($admin->port, 6082);
        $this->assertSame($admin->version, 3);

    }

class VarnishAdminSocketFake extends VarnishAdminSocket
{

}

De la misma manera y con el fin de ayudar a tener test y cobertura sobre los métodos, fue necesario cambiar la visibilidad de las variables de private a protected

Poco a poco, mientras íbamos recubriendo de test cada una de las funciones nos dimos cuenta de que la mayor parte de la funcionalidad recae en la función command así que utilizamos una pequeña “triquiñuela” para poder testar el resto de funciones.

class VarnishAdminSocketTest extends PHPUnit_Framework_TestCase
{

    public function testQuitOk()
    {
        $admin = new VarnishAdminSocketFake();
        $this->assertSame('quit', $this->quit());
    }

class VarnishAdminSocketFake extends VarnishAdminSocket
{
    public $commandFake = true;

    public function command($cmd, &$code, $ok = 200)
    {
        if($this->commandFake == true) {
            return $cmd;
        }else{
            parent::command($cmd, &$code, $ok = 200)

        }
    }
}

Quizás os preguntaréis para qué la variable, pues para poder testar el comando command 🙂 Así podemos ir jugando con la implementación real de los métodos y con nuestra imlementación falsa/fake para no morir en el intento de testar.

La interfaz

A medida que íbamos testeando nos dimos cuenta de que no estábamos utilizando toda la funcionalidad y que además necesitábamos un plus, sobre todo a la hora de “purgar” url’s. Por ello pensamos que la mejor manera era crear un “contrato” con el que en un futuro poder crear otras implementaciones y que el resto de nuestro código siguiese siendo compatible.

interface VarnishAdmin
{
    /**
     * Brutal close, doesn't send quit command to varnishadm.
     */
    public function close();

    /**
     * Connect to admin socket.
     * @param int $timeout in seconds, defaults to 5; used for connect and reads
     * @return string the banner, in case you're interested
     */
    public function connect($timeout = 5);

    /**
     * Shortcut to purge function.
     * @see https://www.varnish-cache.org/docs/4.0/users-guide/purging.html
     * @param string $expr is a purge expression in form "<field> <operator> <arg> [&& <field> <oper> <arg>]..."
     * @return string
     */
    public function purge($expr);

    /**
     * Shortcut to purge.url function.
     * @see https://www.varnish-cache.org/docs/4.0/users-guide/purging.html
     * @param string $url is a url to purge
     * @return string
     */
    public function purgeUrl($url);

    /**
     * Graceful close, sends quit command.
     */
    public function quit();

    /**
     * @return bool
     */
    public function start();

    /**
     * Test varnish child status.
     * @return bool whether child is alive
     */
    public function status();

    /**
     * Set authentication secret.
     * Warning: may require a trailing newline if passed to varnishadm from a text file.
     * @param string
     */
    public function setSecret($secret);

    /**
     * @return bool
     */
    public function stop();
}

Aquí definimos los métodos que de verdad estamos utilizando y que necesitamos para no tener que hacer demasiado código adicional cada vez que necesitábamos interaccionar con la Varnish.

Adaptandonos

Una vez que ya teníamos toda la librería de timwhitlock testada y además una interfaz definida empezamos a ver como adaptarnos para conseguir una librería que fuese compatible tanto con Varnish 3 como con Varnish 4. Si no habéis utilizado Varnish antes, he de decir que ha habido un cambio sustancial al subir de versión de Varnish, ya que cambian desde comandos hasta las fases por las que pasa una petición en Varnish (el flujo de Varnish)

Así pues en el constructor, es necesario especificar la versión de Varnish y a partir de esta versión nosotros seteamos los comandos necesarios para después utilizarlos

    //Different directives depends Varnish version
    if ($this->version == 4) {
        $this->purgeUrlCommand = $this->purgeCommand . ' req.url ~';
    } elseif ($this->version == 3) {
        $this->purgeUrlCommand = $this->purgeCommand . '.url';
    } else {
        throw new \Exception('Only versions 3 and 4 of Varnish are supported');
    }

Una vez que ya teníamos todo esto, ha sido mu sencillo ir adaptando/creando la distintas funciones de Varnish basándonos en la librería de timwhitlock.

Conclusiones

Con esta librería hemos conseguido un sistema de gestión sencilla para mantener cacheadas nuestras páginas, hacer que la performance de nuestro website se incremente muchísimo. Ahora tenemos nos es mucho más fácil invalidar solo parte de nuestra cache cuando ha habido algún cambio/actualización por parte de un usuario.

Tenemos un sistema que es fácil de adaptar a otras plataformas, si en un futuro empezamos a utilizar otra tecnología, o incluso otra versión de Varnish, teniendo la interfaz podemos seguir manteniendo todo el código del resto de nuestro website. Estamos protegidos frente a cambios 🙂

De la misma manera, si hay un bug o necesitamos añadir nueva funcionalidad es relativamente sencillo añadirla, además con los tests podemos rápidamente ver que todo sigue funcionando.

Anuncios

Comenta la entrada

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s