<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Pedro Rojas Reyes</title>
    <description>The latest articles on Forem by Pedro Rojas Reyes (@monoforms).</description>
    <link>https://forem.com/monoforms</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700476%2Fdd0ae2ab-760e-46d3-8993-587075241fdf.jpg</url>
      <title>Forem: Pedro Rojas Reyes</title>
      <link>https://forem.com/monoforms</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/monoforms"/>
    <language>en</language>
    <item>
      <title>De C# a Go: Logrando Compatibilidad en Codificación AES y Base64</title>
      <dc:creator>Pedro Rojas Reyes</dc:creator>
      <pubDate>Tue, 05 Nov 2024 19:12:48 +0000</pubDate>
      <link>https://forem.com/monoforms/de-c-a-go-logrando-compatibilidad-en-codificacion-aes-y-base64-43di</link>
      <guid>https://forem.com/monoforms/de-c-a-go-logrando-compatibilidad-en-codificacion-aes-y-base64-43di</guid>
      <description>&lt;p&gt;Hace un par de semanas me enfrenté a un problema interesante: tenía que migrar un algoritmo de cifrado AES de C# a Go. En la implementación en Go ya teníamos un algoritmo de cifrado AES, pero no era compatible con la implementación en C#, y varias pruebas fallaban porque los resultados no coincidían, principalmente en el último carácter.&lt;/p&gt;

&lt;p&gt;El problema era que no tenía el código fuente de la implementación en C#, solo el binario, una DLL que se usaba en el proyecto de .NET.&lt;/p&gt;

&lt;p&gt;Intenté obtener el código fuente de la implementación en C#, pero no tuve éxito. Al ser un proyecto antiguo, no había documentación disponible. Afortunadamente, mi jefe fue quien desarrolló esta implementación, pero no recordaba los detalles exactos. Sin embargo, sí sabía que al final del proceso de cifrado AES se utilizaba una función para codificar en base64.&lt;/p&gt;

&lt;p&gt;Con esta pista, abrí el proyecto en .NET e instalé la extensión de JetBrains para descompilar el código fuente, y obtuve el código de la librería que se usaba para cifrar la información.&lt;/p&gt;

&lt;p&gt;Finalmente, descubrí que el problema no residía en el algoritmo de cifrado AES, sino en la codificación en base64.&lt;/p&gt;

&lt;p&gt;En el código de C#, al final del proceso de cifrado AES, se usaba la siguiente función para la codificación en base64: &lt;code&gt;HttpServerUtility.UrlTokenEncode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La función &lt;code&gt;UrlTokenEncode&lt;/code&gt; es una función de .NET que codifica una matriz de bytes en una cadena de texto base64 para su transmisión en una URL. Esta función realiza tres acciones clave que explican la diferencia en los resultados:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Codificación en Base64 segura para URLs (URL-safe)&lt;/strong&gt;: usa una variante de Base64 que es apta para URLs, reemplazando los caracteres &lt;code&gt;+&lt;/code&gt; por &lt;code&gt;-&lt;/code&gt; y &lt;code&gt;/&lt;/code&gt; por &lt;code&gt;_&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Eliminación de caracteres de relleno&lt;/strong&gt;: la función elimina los caracteres de relleno &lt;code&gt;=&lt;/code&gt; que se suelen utilizar en la codificación estándar de Base64.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adición de un número al final&lt;/strong&gt;: la función añade un número al final de la cadena para indicar cuántos caracteres de relleno se eliminaron.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Todo esto lo descubrí gracias a ChatGPT, no porque sea un experto en base64. Con esta información, pude modificar la implementación en Go para que fuera compatible con la de C#.&lt;/p&gt;

&lt;p&gt;En Go, después de cifrar la información con AES, la codificación en base64 se realiza de la siguiente manera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawURLEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y, finalmente, se añade el número de caracteres de relleno eliminados al final de la cadena:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Calcular el número de caracteres de relleno (`=`) que se habrían añadido&lt;/span&gt;
&lt;span class="n"&gt;paddingCount&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;

&lt;span class="c"&gt;// Añadir el conteo de relleno al final de la cadena codificada (como hace UrlTokenEncode de C#)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;paddingCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddingCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En la línea &lt;code&gt;paddingCount := (4 - len(paddedBytes)%3) % 4&lt;/code&gt;, se calcula el número de caracteres de relleno (&lt;code&gt;=&lt;/code&gt;) que luego se añaden al final de la cadena codificada en base64:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;len(paddedBytes)%3&lt;/code&gt; calcula la cantidad de bytes que no se han codificado en base64.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(4 - len(paddedBytes)%3) % 4&lt;/code&gt; calcula cuántos caracteres de relleno faltan para que la longitud sea divisible por 4. Si no se necesita relleno, el resultado es 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En resumen, el problema no era el algoritmo de cifrado AES, sino la codificación en base64. Gracias a la información obtenida de ChatGPT, pude modificar la implementación en Go para que fuera compatible con la de C#. En este caso, el uso de ChatGPT fue muy útil, ya que me ahorró un montón de tiempo y dolores de cabeza; eso sí, tuve que ir ajustando cada respuesta hasta igualar los resultados de ambas implementaciones.&lt;/p&gt;

</description>
      <category>go</category>
      <category>csharp</category>
      <category>aes</category>
      <category>base64</category>
    </item>
    <item>
      <title>¿Quieres aprender a convertir JSON a Go en minutos?</title>
      <dc:creator>Pedro Rojas Reyes</dc:creator>
      <pubDate>Fri, 11 Oct 2024 20:43:59 +0000</pubDate>
      <link>https://forem.com/monoforms/quieres-aprender-a-convertir-json-a-go-en-minutos-431k</link>
      <guid>https://forem.com/monoforms/quieres-aprender-a-convertir-json-a-go-en-minutos-431k</guid>
      <description>&lt;p&gt;¿Sabías que convertir datos de JSON a estructuras en Go puede acelerar y optimizar el desarrollo de tus aplicaciones? Si alguna vez te has encontrado manejando grandes cantidades de datos, o si quieres mejorar la manera en que tu aplicación trabaja con información estructurada, este post es para ti.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Por qué es útil esta transformación?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tipado fuerte y seguridad:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Evita errores&lt;/strong&gt;: Al asignar tipos específicos a los campos de las estructuras, el compilador de Go puede detectar errores de tipo en tiempo de compilación, lo que ayuda a prevenir bugs en tiempo de ejecución.&lt;br&gt;
&lt;strong&gt;Mejora la legibilidad&lt;/strong&gt;: El código se vuelve más claro y fácil de entender al utilizar nombres de campos descriptivos y tipos definidos.&lt;br&gt;
&lt;strong&gt;Facilita el mantenimiento&lt;/strong&gt;: Al tener una representación clara de los datos, es más sencillo modificar y extender el código a lo largo del tiempo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manipulación de datos eficiente:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Acceso directo a campos&lt;/strong&gt;: Una vez que los datos están en una estructura, puedes acceder a los campos de forma directa utilizando la notación de punto (.), lo que facilita la extracción y modificación de la información.&lt;br&gt;
&lt;strong&gt;Iteración sobre datos&lt;/strong&gt;: Puedes recorrer los elementos de una estructura utilizando bucles &lt;code&gt;for&lt;/code&gt; y realizar operaciones sobre ellos de manera eficiente.&lt;br&gt;
&lt;strong&gt;Validación de datos&lt;/strong&gt;: Puedes implementar lógica de validación personalizada para asegurarte de que los datos recibidos sean válidos antes de procesarlos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integración con otras partes del código:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Paso de datos a funciones&lt;/strong&gt;: Puedes pasar estructuras como argumentos a funciones, lo que permite reutilizar código y modularizar tu aplicación.&lt;br&gt;
&lt;strong&gt;Almacenamiento de datos&lt;/strong&gt;: Puedes almacenar estructuras en bases de datos, cachés o cualquier otro sistema de almacenamiento que soporte tipos de datos estructurados.&lt;br&gt;
&lt;strong&gt;Serialización a otros formatos&lt;/strong&gt;: Puedes convertir las estructuras de Go a otros formatos como XML, YAML o binario, si es necesario.&lt;/p&gt;

&lt;p&gt;Para ver un ejemplo práctico, puedes ver el siguiente vídeo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/P99WHa8tckA?si=Ac821JGSresX6AVf" rel="noopener noreferrer"&gt;JSON a Go: Aprende a Parsear JSON en Go Rápidamente&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/watch?si=Ac821JGSresX6AVf&amp;amp;v=P99WHa8tckA&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Así que, si te dedicas al desarrollo o buscas optimizar tu aplicación, no subestimes lo poderoso que puede ser manejar bien tus datos. Hasta la próxima.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>productivity</category>
      <category>json</category>
    </item>
    <item>
      <title>Testing en Go, 2ª parte</title>
      <dc:creator>Pedro Rojas Reyes</dc:creator>
      <pubDate>Fri, 19 Apr 2024 23:15:09 +0000</pubDate>
      <link>https://forem.com/monoforms/testing-en-go-2a-parte-3gkp</link>
      <guid>https://forem.com/monoforms/testing-en-go-2a-parte-3gkp</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkllr7sq8wugtknfnme11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkllr7sq8wugtknfnme11.png" alt="Image description" width="592" height="644"&gt;&lt;/a&gt;&lt;br&gt;
Hoy les traigo la segunda parte de un tema que comenzamos a explorar en Medium. Si desean revisar la primera parte, les dejo el enlace &lt;a href="https://medium.com/@sirpyerre/testing-b%C3%A1sico-en-go-d25680e5101c"&gt;aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No tenía planeado escribir esta segunda parte inicialmente, pero me topé con un artículo que encontré muy interesante y que me inspiró a profundizar más en el tema. En esta continuación, nos adentraremos en los tipos de pruebas, las tablas de pruebas y los subtests en el contexto de Go.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tipos de entradas de pruebas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test positivos:&lt;/strong&gt; Se prueban los casos en los que se espera que la función se ejecute correctamente y devuelva el resultado esperado. Este tipo de prueba se asegura de que la aplicación se comporte como se espera en condiciones normales. Los test positivos cubren lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Casos en los que las entradas son válidas.&lt;/li&gt;
&lt;li&gt;Cómo se comporta la prueba en escenarios esperados.&lt;/li&gt;
&lt;li&gt;Casos en los que se satisfacen los requisitos del sistema.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test negativos:&lt;/strong&gt; Se prueban los casos en los que se espera que la función falle o devuelva un resultado inesperado. Este tipo de prueba se asegura de que la aplicación se comporte correctamente con datos inválidos. Los test negativos cubren lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Casos en los que las entradas son inválidas.&lt;/li&gt;
&lt;li&gt;Cómo se comporta la prueba en escenarios inesperados.&lt;/li&gt;
&lt;li&gt;Cómo se comporta la prueba en escenarios fuera de los requisitos del sistema.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ambos tipos de pruebas son igual de importantes para sistemas en producción. El manejo de errores es importante ya que se busca que los usuarios reciban una respuesta adecuada en caso de que algo falle, así como también se busca que el sistema se recupere exitosamente en caso de interrupciones o ralentizaciones.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tablas de pruebas (table-driven)
&lt;/h2&gt;

&lt;p&gt;Las tablas de pruebas son una forma de realizar pruebas en Go de manera más eficiente y ordenada. En lugar de escribir una prueba para cada caso, podemos definir una tabla con los casos de prueba y luego iterar sobre ella para ejecutar las pruebas.&lt;/p&gt;

&lt;p&gt;Una tabla de test es como un test básico, excepto que mantiene una tabla de diferentes valores y resultados esperados. Los diferentes valores son iterados y se ejecuta el test para cada uno de ellos. Con cada iteración, el resultado es verificado con el resultado esperado. Esto ayuda a aprovechar una única función de prueba para probar un conjunto de diferentes valores y condiciones.&lt;/p&gt;

&lt;p&gt;Retomando el ejemplo de la división que vimos en la primera parte, podemos definir una tabla de pruebas de la siguiente manera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"testing"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestDivide2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;testTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dividend&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="n"&gt;divisor&lt;/span&gt;  &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
        &lt;span class="n"&gt;want&lt;/span&gt;  &lt;span class="n"&gt;any&lt;/span&gt;  
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c"&gt;// División por cero&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;testTable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dividend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;divisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Se esperaba un error al dividir por cero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Se esperaba %d pero se obtuvo %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este caso, definimos una tabla de pruebas con los casos  que queremos probar. Luego, iteramos sobre la tabla y ejecutamos la prueba para cada caso.&lt;br&gt;
En los tres primeros casos, esperamos que la división sea exitosa y que el resultado sea el esperado. En el último caso, esperamos que la división falle y que se genere un error.&lt;/p&gt;

&lt;p&gt;Al correr los tests vemos lo siguiente:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TUUWSOnp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dtbpucouh/image/upload/v1713239435/divide-test-2024-04-15_21-50_a2qlx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TUUWSOnp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/dtbpucouh/image/upload/v1713239435/divide-test-2024-04-15_21-50_a2qlx6.png" alt="Resultado de pruebas" title="Tabla de pruebas" width="691" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La desventaja de las tablas de pruebas es que si una prueba falla, no se ejecutan las pruebas restantes. Esto puede ser un problema si queremos ver todos los resultados de las pruebas, incluso si algunas fallan. Otra desventaja es que a medida que vas agregando más casos de prueba, la tabla de pruebas puede volverse difícil de leer y mantener. Para resolver esto, puedes dividir la tabla de pruebas en otras tablas más pequeñas o usar subtests.&lt;/p&gt;

&lt;p&gt;Raramente he usado tablas de pruebas en mis proyectos. Recuerdo que recientemente las usé para probar casos similares donde esperaba resultados de error diferentes, pero en general no las uso mucho. &lt;/p&gt;

&lt;p&gt;Creo que la importancia de las tablas de test radica en poder probar diferentes escenarios con una sola función de test, lo cual puede ser útil en ciertos casos.&lt;/p&gt;
&lt;h2&gt;
  
  
  Subtests
&lt;/h2&gt;

&lt;p&gt;Este es mi tipo de pruebas favorito; los subtests son pruebas anidadas dentro de otra función de prueba. Los subtests son útiles cuando quieres probar diferentes escenarios o condiciones dentro de una sola función. Los subtests se ejecutan de forma independiente y si uno falla, los demás continúan ejecutándose.&lt;/p&gt;

&lt;p&gt;Esta fue una característica que se introdujo en Go 1.7. La introducción de subtests permitió un mejor manejo de errores, un control detallado de pruebas a ejecutar desde la línea de comandos y a menudo da como resultado un código más limpio y más fácil de mantener.&lt;/p&gt;

&lt;p&gt;El &lt;code&gt;testing.T&lt;/code&gt; tiene un método llamado &lt;code&gt;Run&lt;/code&gt; que se utiliza para crear subtests. El método &lt;code&gt;Run&lt;/code&gt; toma dos parámetros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; es el nombre del subtest.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;f&lt;/code&gt; es la función de prueba que se ejecutará como subtest y recibe un &lt;code&gt;*testing.T&lt;/code&gt; como argumento.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez dentro del método &lt;code&gt;Run&lt;/code&gt;, el motor de pruebas ejecutará la función de prueba y si falla, el subtest fallará. Si la función de prueba no falla, el subtest se considera exitoso. Esto nos permite establecer una estructura jerárquica de pruebas, cada una con su propio ámbito. Este enfoque nos permite construir jerarquías de pruebas en múltiples niveles según sea necesario.&lt;/p&gt;

&lt;p&gt;Antes de pasar al ejemplo, hay un tip que tomé de un blog que compartieron en el trabajo y que me pareció interesante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usar &lt;code&gt;.Parallel()&lt;/code&gt; para tests y subtests que no dependen entre sí. Esto permite que los tests se ejecuten en paralelo, lo que puede reducir el tiempo de ejecución de las pruebas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retomando el ejemplo de la división, podemos definir subtests de la siguiente manera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestDivideWithSubTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;actAssert&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Se esperaba %d pero se obtuvo %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"positive input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="n"&gt;actAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"negative input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;actAssert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"undefined result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Se esperaba un error al dividir por cero"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este caso, definimos tres subtests dentro de la función de prueba &lt;code&gt;TestDivideWithSubTests&lt;/code&gt;. Cada subtest prueba un escenario diferente y se ejecuta de forma independiente. Aquí he creado una función &lt;code&gt;actAssert&lt;/code&gt; que se encarga de realizar la división y verificar el resultado. Luego, cada subtest llama a esta función con los valores de entrada y el resultado esperado. Estos son los casos que cubre cada subtest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;positive input&lt;/code&gt;: prueba la división con valores positivos.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;negative input&lt;/code&gt;: prueba la división con valores negativos.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;undefined result&lt;/code&gt;: prueba la división por cero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Al correr el test, vemos lo siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; TestDivideWithSubTests &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="o"&gt;===&lt;/span&gt; RUN   TestDivideWithSubTests
&lt;span class="o"&gt;===&lt;/span&gt; PAUSE TestDivideWithSubTests
&lt;span class="o"&gt;===&lt;/span&gt; CONT  TestDivideWithSubTests
&lt;span class="o"&gt;===&lt;/span&gt; RUN   TestDivideWithSubTests/positive_input
&lt;span class="o"&gt;===&lt;/span&gt; PAUSE TestDivideWithSubTests/positive_input
&lt;span class="o"&gt;===&lt;/span&gt; RUN   TestDivideWithSubTests/negative_input
&lt;span class="o"&gt;===&lt;/span&gt; PAUSE TestDivideWithSubTests/negative_input
&lt;span class="o"&gt;===&lt;/span&gt; RUN   TestDivideWithSubTests/undefined_result
&lt;span class="o"&gt;===&lt;/span&gt; PAUSE TestDivideWithSubTests/undefined_result
&lt;span class="o"&gt;===&lt;/span&gt; CONT  TestDivideWithSubTests/positive_input
&lt;span class="o"&gt;===&lt;/span&gt; CONT  TestDivideWithSubTests/negative_input
&lt;span class="o"&gt;===&lt;/span&gt; CONT  TestDivideWithSubTests/undefined_result
&lt;span class="nt"&gt;---&lt;/span&gt; PASS: TestDivideWithSubTests &lt;span class="o"&gt;(&lt;/span&gt;0.00s&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nt"&gt;---&lt;/span&gt; PASS: TestDivideWithSubTests/positive_input &lt;span class="o"&gt;(&lt;/span&gt;0.00s&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nt"&gt;---&lt;/span&gt; PASS: TestDivideWithSubTests/negative_input &lt;span class="o"&gt;(&lt;/span&gt;0.00s&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nt"&gt;---&lt;/span&gt; PASS: TestDivideWithSubTests/undefined_result &lt;span class="o"&gt;(&lt;/span&gt;0.00s&lt;span class="o"&gt;)&lt;/span&gt;
PASS
ok      github.com/Sirpyerre/listingposts_test/divide   0.001s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cuando ves los mensajes "PAUSE" y "CONT" en la salida de las pruebas de Go, eso indica que se están ejecutando pruebas en paralelo y Go está pausando y continuando la ejecución de las mismas. &lt;/p&gt;

&lt;p&gt;Cuando ejecutas pruebas en paralelo, Go organiza la ejecución de las mismas de manera eficiente, pausando y continuando según sea necesario para garantizar que todas las pruebas se completen correctamente. Esto puede ayudar a optimizar el tiempo de ejecución, especialmente en sistemas con múltiples núcleos de CPU disponibles.&lt;/p&gt;

&lt;p&gt;Cuando conocí los subtests por primera vez, me gustó mucho cómo se organizaban las pruebas y que además, con el IDE que uso (GoLand), puedo ver los subtests y ejecutarlos de manera independiente sin tener que ejecutar todo el test.&lt;/p&gt;




&lt;p&gt;Bueno, es todo por esta segunda parte. Espero que les haya gustado y que les haya sido útil. Aún hay más cosas de las que podemos hablar sobre las pruebas en Go, como el paquete &lt;code&gt;testify&lt;/code&gt; que es muy popular. Con este paquete podemos hacer pruebas de mocks, assertions, etc. Pero eso lo dejaremos para otra ocasión.&lt;/p&gt;

&lt;p&gt;¡Me encantaría escucharte! ¿Qué te pareció este artículo? ¿Has tenido experiencias interesantes con pruebas en Go que te gustaría compartir? ¿O quizás tienes alguna sugerencia o mejora para este contenido? ¡Déjame un comentario abajo y hablemos! &lt;/p&gt;

&lt;p&gt;¡Hasta la próxima y happy testing! 🧪🧪🧪&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Simion, A. (2019). Test-Driven Development in Go. Pragmatic Bookshelf.&lt;/p&gt;

&lt;p&gt;Kennedy, W., Ketelsen, B., &amp;amp; St. Martin, E. (2015). Go in Action. Manning Publications.&lt;/p&gt;

&lt;p&gt;The Go Authors. (2019, 17 de septiembre). Using Subtests and Sub-benchmarks. Recuperado de &lt;a href="https://go.dev/blog/subtests"&gt;https://go.dev/blog/subtests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How I write tests in Go. [Blog post]. Recuperado de &lt;a href="https://blog.verygoodsoftwarenotvirus.ru/posts/testing-in-go/index.html"&gt;https://blog.verygoodsoftwarenotvirus.ru/posts/testing-in-go/index.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>unittest</category>
      <category>tabletest</category>
      <category>subtest</category>
    </item>
  </channel>
</rss>
