Así pues, en esta charla voy a contaros la historia que hay detrás de SQLancer, una herramienta que hemos diseñado y desarrollado para probar automáticamente sistemas de gestión de bases de datos. Ahora asumo que muchos de ustedes no están trabajando directamente en el desarrollo de sistemas de gestión de bases de datos, por lo que me centraré en las ideas generales que podrían aplicar en las pruebas de su proyecto. Para asegurarnos de que todos estamos en la misma página, en nuestro trabajo queríamos probar sistemas de gestión de bases de datos que esperan consultas o sentencias escritas en el lenguaje de consulta estructurado o, para abreviar, SQL. Como tal vez la mayoría de ustedes sepa, podemos utilizar este lenguaje para crear tablas, insertar datos en ellas y, a continuación, recuperar datos mediante una sentencia select. Ahora bien, en las charlas anteriores ya hemos oído hablar un poco de las pruebas, así que ¿cómo podemos escribir casos de prueba que puedan probar estos sistemas? Bueno, un enfoque sería simplemente escribirlos en SQL. Podríamos, por ejemplo, especificar el caso de prueba, como aquí tomado de la prueba de MySQL, y luego en un archivo separado especificar la salida esperada. Y aunque hay algunos frameworks que hacen este proceso un poco más fácil, yo diría que sigue siendo siempre un reto y consume mucho tiempo escribir casos de prueba manuales, sobre todo teniendo en cuenta para los sistemas tan complejos donde necesitamos una enorme cantidad de conjuntos de pruebas. Así que en nuestro trabajo nos preguntamos, ¿podemos automatizar el proceso de pruebas? Y esto nos lleva a dos retos. En primer lugar, si queremos tener un enfoque de pruebas automatizado, necesitamos un caso de prueba, y en nuestro caso esto significa que necesitamos generar una base de datos y una consulta que luego podamos validar. Así que buscando en lo que ya existe, podemos encontrar herramientas como por ejemplo SQLsmith. SQLsmith es una herramienta de generación de consultas eficaz y ampliamente utilizada que muta y genera sentencias SQL complejas que pueden causar, por ejemplo, un fallo en el sistema de base de datos que los desarrolladores pueden solucionar. Así que yo diría que esto ya es un reto resuelto, por lo que podemos utilizar este enfoque de generación aleatoria u otro para generar los casos de prueba. Ahora bien, lo que seguía siendo un reto abierto es el llamado problema del oráculo de prueba, digamos que queremos validar el resultado de la consulta. Entonces, ¿qué es un oráculo de pruebas? Un oráculo de prueba es básicamente el mecanismo que determina si una prueba ha pasado o ha fallado, y para las pruebas de regresión para escribir casos de prueba manuales, nosotros típicamente como desarrolladores o desarrolladoras somos - o proporcionamos el oráculo de prueba. Pero esto es a menudo difícil, incluso para los casos de prueba escritos manualmente, y quiero ilustrar esto basado en un ejemplo concreto. Puedes ver aquí un caso de prueba que realmente nos permitió encontrar y reportar un error en MySQL. Puedes ver aquí que tenemos dos tablas t0 y t1, cada una de las cuales contiene un valor de punto flotante, concretamente un cero positivo y un cero negativo. Y luego instruimos a MySQL para que obtenga el producto cruzado de todos los registros de estas dos tablas donde la columna se evalúa con el mismo valor. Ahora, ¿cuál es la salida esperada aquí? Yo diría que es discutible si el predicado aquí debería evaluarse como verdadero o falso porque podríamos, por ejemplo, mirar la representación binaria de estos dos números de punto flotante para darnos cuenta de que el bit de signo difiere. Y en base a esto tendría sentido que el operador de igualdad evaluara a falso y MySQL para este caso devolvería un resultado vacío. Pero la mayoría de los lenguajes de programación como Java, C, etc., definen la semántica de que este operador de igualdad debe evaluarse como verdadero para - para estos valores. Y en este caso MySQL obtendría una fila. Así que en este punto de una charla tienes que confiar en mí, en realidad se espera que MySQL obtenga el registro. Pero cuando probamos la última versión, todavía no lo hizo. Informamos de esto como un error a los desarrolladores que luego lo arreglaron para la próxima versión de MySQL. Pero el punto aquí es que pudimos encontrar este error sin tener una idea de si un predicado debe evaluarse como verdadero o no y si cualquier resultado o cualquier registro debe ser obtenido. Básicamente teníamos este oráculo de prueba que nos decía que el resultado devuelto era correcto. Ahora hemos desarrollado un par de oráculos de prueba, el que voy a presentar hoy se denomina partición lógica o TLB corto. Y si tuviera que explicar el enfoque en media frase diría básicamente que la idea es probar el sistema de gestión de bases de datos contra sí mismo. La idea es bastante generosa y quiero explicarla basándome en una analogía. Si alguna vez nos reunimos en persona es muy probable que sea en la cafetería porque me gusta beber mucho café. Y supongamos también que hay un bol con frutas que contiene mandarinas y también clementinas. La verdad es que se me da muy mal distinguir estos cítricos, todos me parecen iguales, así que esto es lo que te digo para empezar una conversación. Y es posible que respondas que puedes distinguir claramente la diferencia. Así que te reto a que me lo demuestres, pero ¿cómo puedo poner a prueba tu entendimiento de las mandarinas frente a las clementinas sin tener yo mismo este entendimiento? Bueno, la estrategia que aplicaría sería la siguiente: primero te pediría que por favor me trajeras todas las clementinas y tú irías a buscar quizás dos de las frutas. Luego las pondría de nuevo en el recipiente, las mezclaría y luego te pediría que me trajeras todos los alimentos que no sean clementinas. Traerías la manzana que podría reconocer, junto con las otras frutas de aspecto anaranjado. Y creo que ya algunos de ustedes ven a dónde va esto porque primero me trajiste dos frutas y luego me trajiste cuatro frutas, así que en total seis frutas, pero sólo hay cinco frutas en el tazón, lo que significa que probablemente clasificaste una fruta como mandarina y clementina. Entonces, la idea de alto nivel aquí es que cada objeto en el universo y también en el bol es una clementina o no y usamos esta idea para básicamente dividir todos los objetos en el bol. Por supuesto, siempre se puede decir - siempre se puede clasificar consistentemente mandarinas como clementinas y al revés, lo que significa que no podemos detectar todos los errores, pero tampoco podemos probar - casos de prueba, por lo que esta es una limitación con la que tenemos que vivir. ¿Cómo aplicamos ahora esto a SQL? Ahora tenemos cualquier tipo de predicado dado P y una fila R en lugar de una fruta, pero exactamente uno de los siguientes debe mantener el predicado evaluado a verdadero, no el predicado o es el verdadero lo que significa que se evalúa a falso, y luego, ya que en SQL también tenemos que lidiar con los valores nulos, tenemos que lidiar con el caso en que P podría evaluar a null. Y basándonos en esto, ahora podemos tomar cualquier tipo de datos en nuestra tabla - cualquier tipo de resultado intermedio - y dividirlo en tres partes: entonces hacemos las del predicado en las que es verdadero, aquellas en las que se evalúa como falso, y aquellas en las que se evalúa como nulo. ¿Cómo nos permitió ahora detectar el error del ejemplo motivador? Bueno, primero generamos esta consulta que básicamente corresponde a contar el número de frutas en el recipiente. Así que simplemente buscamos el producto cruzado de todos los valores de estas dos tablas. Y ahí MySQL devolvió un único registro. Luego generamos estas tres consultas más complejas basadas en este predicado aleatorio P. Puedes ver aquí que tenemos la versión no negada, la versión negada y esta versión nula. En general, esperamos que esto debería calcular el mismo resultado. Sin embargo, MySQL devolvió aquí un conjunto de resultados vacío, lo que nos permitió encontrar e informar del error. La técnica es en realidad bastante versátil en el sentido de que podemos probar varios tipos diferentes de características SQL, aquí sólo se centra en las cláusulas where. Así que básicamente teníamos este enfoque de generación aleatoria para abordar este problema de generación de casos de prueba y ahora proponemos el particionamiento lógico ternario para abordar este problema de oráculo de prueba. ¿La siguiente pregunta natural es si una técnica tan sencilla puede ser eficaz? Pues bien, he utilizado la respuesta SQL TLB como uno de estos enfoques que propusimos para encontrar más de 450 errores únicos y previamente desconocidos en los sistemas de gestión de bases de datos de Baidu de los que todos han oído hablar o conocen. ¿Y qué deberías sacar de esta charla? Bueno, aunque la técnica específica de partición de datos funciona principalmente para los sistemas orientados a datos, se basa en realidad en una técnica más genérica, más general. Es decir, si miramos esto desde un nivel abstracto, básicamente podemos darnos cuenta de que teníamos un caso de prueba que ejecutamos para obtener un resultado y en base a esto derivamos un nuevo caso de prueba para el que podríamos proporcionar un oráculo de prueba. Y esto hace la prueba metamórfica. Ahora, lo que puedes hacer es, por supuesto, intentar idear una técnica de pruebas metamórficas por ti mismo, pero una alternativa también sería comprobar si hay algún enfoque ya existente, por ejemplo, buscando en Google Scholar. Y con eso, voy a resumir. Las conclusiones son que escribir manualmente los casos de prueba requiere mucho tiempo y un conocimiento detallado del dominio, y es posible que te enfrentes a un problema similar en tu trabajo. Así que lo que proponemos es combinar la generación aleatoria de casos de prueba con las pruebas metamórficas, que han resultado ser un complemento eficaz de los casos de prueba escritos manualmente. Con esto, gracias por escuchar.