Pruebas generativas

¿Qué son las pruebas generativas?

Las pruebas generativas son pruebas sobre código que se realizan usando datos “aleatorios” sobre las funciones que nosotros escribimos. La idea es probar estos datos “aleatorios” contra propiedades de la salida deseada de las funciones.

En Clojure existe una biblioteca llamada clojure.test.check, la cuál se encarga de proveer herramientas para construir estas pruebas generativas.

Las pruebas generativas, básicamente se encargan de separar la definición del dominio de entrada respecto al concepto de generadores y validadores.

Pruebas en clojure

Clojure, a través del espacio de nombres clojure.test, provee funciones y macros para realizar pruebas unitarias en sobre las funciones que implementemos en clojure.

Las pruebas en clojure generalmente lucen de la siguiente forma:

(require '[clojure.test :refer [deftest]])
(defn add
  "Realiza la suma de |x| con |y|"
  [x y]
  (+ x y))

(deftest add-x-to-y
  (is (= 5 (add 2 3))))

El ejemplo anterior, es un ejemplo pequeño de como realizar pruebas unitarias en clojure. Pero en ocasiones esto no es suficiente, para esto, se define una nueva forma de probar el código que implementemos utilizando datos generados de forma “aleatoria” para introducirla a nuestras funciones y a partir de las propiedades de la salida, determinar si estas funciones son correctas o no. Este tipo de pruebas se les conoce como pruebas generativas.

El espacio de nombres clojure.test.check (una biblioteca inspirada en Quickcheck de Haskell), provee una serie de funciones y macros para poder crear pruebas generativas sobre el código que nosotros escribimos. Estas pruebas generativas como mencionamos previamente, están sustentadas sobre la prueba de propiedades de las salidas de una función.

Ejemplos de como realizar pruebas generativas usando clojure.test.check las podemos encontrar en la documentación del mismo proyecto, la cuál podemos acceder a través de esta liga.

Aún así se agrega a este documento un ejemplo pequeño como muestra:

(require '[clojure.test.check :as tc]
         '[clojure.test.check.generators :as gen]
         '[clojure.test.check.properties :as prop])

(defn ascending?
  "clojure.core/sorted? doesn't do what we might expect, so we write our own
  function"
  [coll]
  (every? (fn [[a b]] (<= a b))
          (partition 2 1 coll)))

(def property
  (prop/for-all [v (gen/vector gen/int)]
    (let [s (sort v)]
      (and (= (count v) (count s))
           (ascending? s)))))

;; test our property
(tc/quick-check 100 property)

Entendiendo el como usar las pruebas generativas

Del código anterior, se invocan los siguientes espacios de nombres:

(require '[clojure.test.check :as tc]
         '[clojure.test.check.generators :as gen]
         '[clojure.test.check.properties :as prop])

Los cuáles son los encargados de proveer funciones y macros para la generación y prueba de funciones. De estos espacios de nombres podemos observar las siguientes características:

tc
Contiene una única función quick-check que es utilizada probar funciones con valores generados por una función generador.
gen
Contiene funciones para generar datos basados en propiedades.
prop
Provee sugar macros para definir parámetros y funciones para ser probadas por los generadores.

Más info sobre estos espacios de nombres, pueden ser consultados en clojure.test.check

Una vez identificadas los distintos tipos de funciones y macros provistos por clojure.test.check, procedemos a definir una función y una propiedad a probar para esa función.

(defn ascending?
  "clojure.core/sorted? doesn't do what we might expect, so we write our own
  function"
  [coll]
  (every? (fn [[a b]] (<= a b))
          (partition 2 1 coll)))

Lo anterior define una función que verifica que para cada para par dentro de la colección que pasamos como parámetro, esta siempre sea ordenada, i.e. exista un ordenamiento.

Una vez hecho, procedemos a escribir la propiedad:

(def property
  (prop/for-all [v (gen/vector gen/int)]
    (let [s (sort v)]
      (and (= (count v) (count s))
           (ascending? s)))))

La cuál verifica que podamos ordenar una colección y además que dicha colección se encuentra ordenada de forma ascendente. La colección usada es generada por (gen/vector gen/int), la cuál es una combinación de generadores que dice que genere un vector de enteros y lo guarde en una variable v, la cuál después se la pasamos a la forma para evaluar nuestra expresión.

Hecho lo anterior, se utiliza la función tc/quick-check, indicándole el número de muestras a generar, en este caso le decimos que 100 de la siguiente forma:

(tc/quick-check 100 property)

Hasta aquí vamos a detener este artículo introductorio, esperemos que podamos continuar con la generación de bases de datos de batería para probar nuestro código, para ello intentaremos hacer uso de las pruebas generativas de clojure.test.check.

Anuncios

Prettify-symbols-mode

Desde la versión 24.4 de emacs se incluye un nuevo modo llamado prettify-symbols-mode, el cuál esta planeado para remplazar la representación de varios identificadores/símbolos usados en los lenguajes de programación por caracteres más estéticos.

Por ejemplo, en el modo lisp (emacs-lisp también), al escribir la palabra reservada lambda, visualmente se transforma en el símbolo λ, pero que en el texto se sigue conservando la palabra escrita.

Esto permite que escribir en diversos lenguajes tenga una notación un poco más “matemática” y visualmente más estética respecto a escribirlo en otros editores.

Para activar esta características, por ejemplo, en el modo lisp hay que sobreescribir la variable lisp--prettify-symbols-alist de la siguiente forma:

(defconst lisp--prettify-symbols-alist
  '(("lambda"  . ?λ)))

Esto significa que la palabra lambda va a ser remplazada por el simbolo λ. Pero también se puede agregar más remplazos para otros modos:

(add-hook clojure-mode-hook
            (lambda ()
              (push '(">=" . ?≥) prettify-symbols-alist)))

En caso de que queramos activar por defecto prettify-symbols en la mayoría de los modos por defecto de emacs, agregamos la siguiente linea a nuestra configuración de emacs:

(global-prettify-symbols-mode +1)

Clojure Spec

Primero que nada, ¿Qué es clojure spec? Es una biblioteca de clojure que especifica la estructura de los datos, permite la validación de los mismos y puede generar datos a partir del spec.

clojure.spec (o cljs.spec) esta incluida en el core de clojure a partir de la versión 1.9 (aún en estado alpha), así que no es necesario incluir alguna otra biblioteca.

clojure.spec va a permitir que definamos predicados sobre los datos que estamos modelando, permitiendo que estos sean validados en tiempo de ejecución de acuerdo a los predicados definidos. Esto es mucho más poderoso que tener definiciones basadas en tipos.

clojure.spec además permite tener pruebas generativas para probar que las funciones y los datos que definamos funciones de acuerdo a la abstracción de características (predicados) que tenga la información. Se podría decir que clojure.spec tiene los siguientes objetivos de acuerdo a su documentación:

  • Comunicación
  • Especificación unificada en varios contextos
  • Maximizar el apalancamiento a partir del esfuerzo de la especificación
  • Minimizar la intrusión
  • Establecer e iniciar un diálogo sobre el cambio de semánticas y compatibilidad.

Lo anterior debería de automatizar lo siguiente:

  • Validación
  • Reporte de errores
  • Destructuring
  • Instrumentación
  • Generación de datos de prueba
  • Generación de pruebas generativas

Todas estas características permiten el desarrollo de software con un alto nivel de certidumbre, ya que evita la necesidad de crear pruebas unitarias (siempre es difícil establecer cuales son los casos interesantes a probar) y permite tener pruebas generativas con casos de prueba generados a partir de la abstracción de características de los datos modelados.

Conceptos de código limpio

Después de observar los cambios que se han ido realizando en los proyectos de código de mis alumnos, me puse a pensar en el concepto de código limpio el cuál para mi significa lo siguiente:

El código limpio es aquel código que es fácil de leer, fácil de entender y muestra un rendimiento óptimo.

¿Pero qué piensan otros programadores acerca del código limpio? para esto, voy a tomar algunas citas del libro Código limpio de Robert C. Martin.

Empecemos con Bjarne Stroustop, desarrollador del lenguaje de programación C++ y autor de The C++ Programming Language

Me gusta que mi código sea elegante y eficaz. La lógica debe ser directa para evitar errores ocultos, las dependencias deben de ser mínimas para facilitar el mantenimiento, el procesamiento de errores completo y sujeto a una estrategia articulada, y el rendimienot debe ser óptimo para que los usuarios no tiendan a estropear el código con optimizaciones sin sentido. El código limpio hace bien una cosa.

Grady Booch, autor de Object Oriented Analysis and Design with Applications

El código limpio es simple y directo. El código limpio se lee como un texto bien escrito. El código limpio no oculta la intención del diseñador si no que muestra nítidas abstracciones y líneas directas de control.

Big’ Dave Thomas, fundador de OTI, el padrino de la estrategia Eclipse.

El código limpio se puede leer y mejorar por parte de un programador que no sea su autor original. Tiene pruebas de unidad y aceptación. Tiene nombres con sentido. Ofrece una y no varias formas de hacer algo. Sus dependencias son mínimas, se definen de forma explícita y ofrece una API clara y mínima. El código debe ser culto en función del lenguaje, ya que no toda la información necesaria se puede expresar de forma clara en el código.

Michael Feathers, autor de Working Effectively with Legacy Code.

Podría enumerar todas las cualidades del código limpio pero hay una principal que engloba a todas ellas. El código limpio siempre parece que ha sido escrito por alguien a quien le importa. No hay nada evidente que hacer para mejorarlo. El autor del código pensó en todos los aspectos posibles y si intentamos imaginar alguna mejora, volvemos al punto de partida y sólo nos queda disfrutar del código que alguien a quien le importa realmente nos ha proporcionado.

Después de observar estas citas y algunas más, creo que las características que debe cumplir el código limpio son las siugientes:

  • Legible
  • Simple
  • Elegancia
  • Eficaz
  • Óptimo
  • Con pruebas

Seguro se pueden agregar más características para un código limpio, un buen código.

Herramientas para migraciones de bases de datos.

Existen diversos tipos de herramientas para realizar migraciones de bases de datos (Schema migration, database migration, database change management en inglés). Estas herramientas están diseñadas para administrar cambios incrementales y reversibles sobre esquemas de bases de datos relacionales.

Una migración sobre un esquema es realizada siempre que se necesite actualizar o revertir un cambio sobre un esquema de una base de datos relacional.

En este texto, mostraré las herramientas disponibles y de las más oídas para trabajar con migraciones para bases de datos.

Flyway

  • Herramienta desarrollada para interactuar con Java
  • Bases de datos soportadas:
    • Oracle
    • SQL Server
    • SQL Azure
    • DB2
    • MySQL
    • MariaDB
    • Google Cloud
    • PostgreSQL
    • Redshift
    • Otras…
  • Herramientas de Java con las que interactua
    • Maven
    • Gradle
    • Ant
  • 6 Comandos básicos
    • Migrate
    • Clean
    • Info
    • Validate
    • Baseline
    • Repair

Liquibase

  • Herramienta desarrollada para interactuar con Java
  • Bases de datos soportadas
    • MySQL
    • PostgreSQL
    • Oracle
    • SQL Server
    • Sybase
    • DB2
    • Apache Derby
    • Otras
  • Herramientas Java con las que interactua
    • Maven
    • Ant
  • Muchos comandos dependiendo de lo que se quiera hacer

Datical DB

  • Sin interacción directa con Java
  • Bases de datos soportadas
    • Oracle
    • DB2
    • MySQL
    • SQL Server
    • PostgreSQL
    • Otras
  • Muchos comandos dependiendo de lo que se quiera hacer. Similar a Liquibase.

Active Record (migrations)

  • Herramienta hecha con Ruby
  • Bases de datos soportadas
    • PostgreSQL
    • MySQL
  • Comandos basados en definir cosas en Ruby

Ruckusing-migrations

  • Herramienta hecha en PHP
  • Bases de datos soportadas
    • PostgreSQL
    • MySQL
    • Sqlite
  • Varios comandos basados en lo que se quiera hacer.

Phinx

  • Herramienta hecha en PHP
  • Bases de datos soportadas
    • MySQL
    • PostgreSQL
    • SQLite
    • SQL Server
  • Varios comandos basados en lo que se quiera hacer.

MyBatis Migrations

  • Framework de persistencia para Java.
  • Bases de datos soportadas
    • Cualquiera bajo JDBC
  • Configuración basada en SQL, Java y XML

Ragtime

  • Biblioteca para realizar migraciones de datos estructurados hecha en Clojure
  • Bases de datos soportadas
    • Cualquiera bajo JDBC
  • Configuración basada en SQL

Lobos

  • Biblioteca para realizar migraciones de bases de datos escrita en Clojure.
  • Bases de datos soportadas
    • H2
    • MySQL
    • PostgreSQL
    • SQLite
    • SQL Server
  • Configuración bajo clojure

Programación de videojuegos con pygame.

Bien, hoy programaremos un videojuego sencillo con python, utilizando un módulo que permite la creación de videojuegos en dos dimensiones cuyo nombre es “pygame”. Pygame permite desarrollar y prototipar rápidamente videojuegos en 2 dimensiones, además de ser una interfaz para SDL (Simple Direct Layer) orientada para el dibujado en 2D.

Antes de iniciar a programar nuestro primer videojuego en python, es necesario conocer como está estructurada la anatomía de un videojuego, que nos servirá como referencia para poder desarrollar nuestro videojuego en pygame.

Un videojuego se compone de la siguientes características:

  • Entrada:
    Claramente un videojuego necesita comunicarse con el usuario de alguna manera, esto será a través de un dispositivo de entrada como un joystick o un teclado.
  • Visualización:

    Esta característica tiene que ver con la capacidad de transmitir el estado interno del videojuego a una salida gráfica. Ya sea que el videojuego sea en 2D o 3D, esta característica es importante, ya que, a partir de ella será fundamental el que sea agradable visualmente al jugador. Para este pequeño tutorial será en 2D, dadas las características de programación con las que trabajamos (el módulo pygame solo trabajo con Sprites en 2 dimensiones).

  • Sonido:

    Esta característica también es muy importante, ya que el audio proporcionado al jugador le transmitirá un conjunto de sensaciones que le permitirán determinar si el juego es agradable o no (pygame proveé un conjunto de herramientas para trabajar con audio de manera sencilla).

  • Comunicaciones:

    Actualmente los juegos en línea y más los juegos sociales van ganando más adeptos dentro del mercado de los videojuegos. Normalmente los videojuegos en línea utilizan comunicación TCP/IP, lo cual permite que juguemos con otras personas en nuestra red local o que se encuentra en otros lugares del globo. Como pygame está basado como una interfaz de SDL, nos permite maniobrar con este tipo de cosas, pero este tema sale de nuestro pequeño tutorial.

  • Game loop:

    Esta característica, tal como su nombre indica, es la que realiza el ciclo del juego y se encarga de realizar las actividades antes mencionadas. En la figura siguiente se muestra de manera sencilla como es que está estructurado el “Game Loop”.

Ya que hemos analizado como es que funciona un videojuego, procederemos a realizar el videojuego prometido. El videojuego que haremos será el clásico pong, el cual realizaremos en python, pero para eso lo empezaremos a hacer en el siguiente post, en el cual ya nos dedicaremos directamente a programar en python junto con el módulo pygame.