<?xml version="1.0" encoding="utf-8"?>
<page xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its" type="topic" id="unit-testing" xml:lang="es">

  <info>
    <link type="guide" xref="index#general-guidelines"/>

    <credit type="author copyright">
      <name>Philip Withnall</name>
      <email its:translate="no">philip.withnall@collabora.co.uk</email>
      <years>2015</years>
    </credit>

    <include xmlns="http://www.w3.org/2001/XInclude" href="cc-by-sa-3-0.xml"/>

    <desc>Diseñar software para ser probado y escribir pruebas unitarias para él</desc>
  
    <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
      <mal:name>Daniel Mustieles</mal:name>
      <mal:email>daniel.mustieles@gmail.com</mal:email>
      <mal:years>2016-2020</mal:years>
    </mal:credit>
  
    <mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
      <mal:name>Javier Mazorra</mal:name>
      <mal:email>mazi.debian@gmail.com</mal:email>
      <mal:years>2016, 2020</mal:years>
    </mal:credit>
  </info>

  <title>Pruebas unitarias</title>

  <synopsis>
    <title>Resumen</title>

    <p>Las pruebas unitarias deben ser el método principal para probar la mayor parte del código escrito, porque una prueba unitaria puede escribirse una vez y ejecutarse muchas veces; las pruebas manuales deben planificarse una vez y luego ejecutarse manualmente cada vez.</p>

    <p>El desarrollo de las pruebas unitarias comienza con la arquitectura y el diseño de la API del código que se va a probar: el código debe diseñarse para que se pueda probar fácilmente, o será potencialmente muy difícil de probar.</p>

    <list>
      <item><p>Escriba pruebas unitarias para que sean lo más pequeñas posible, pero no más pequeñas. (<link xref="#writing-unit-tests"/>)</p></item>
      <item><p>Utilice herramientas de cobertura de código para escribir pruebas para obtener una cobertura de código alta. (<link xref="#writing-unit-tests"/>)</p></item>
      <item><p>Ejecute todas las pruebas unitarias en Valgrind para comprobar si hay fugas y otros problemas. (<link xref="#leak-checking"/>)</p></item>
      <item><p>Utilice herramientas apropiadas para generar pruebas unitarias automáticamente donde sea posible. (<link xref="#test-generation"/>)</p></item>
      <item><p>Diseñe el código para ser comprobable desde el principio. (<link xref="#writing-testable-code"/>)</p></item>
    </list>
  </synopsis>

  <section id="writing-unit-tests">
    <title>Escribir pruebas unitarias</title>

    <p>Las pruebas unitarias deben escribirse junto con la observación de la  <link xref="tooling#gcov-and-lcov">información de cobertura obtenida de la ejecución de las pruebas</link>. Por lo general, esto significa escribir un conjunto inicial de pruebas unitarias, ejecutarlas para obtener datos de cobertura, luego volver a trabajar y expandirlas para aumentar los niveles de cobertura del código. La cobertura debe aumentarse primero asegurándose de que todas las funciones están cubiertas (al menos en parte) y luego asegurándose de que todas las líneas de código están cubiertas. Al cubrir las funciones primero, los problemas de la API que evitarán la realización de pruebas efectivas se pueden encontrar rápidamente. Normalmente, se manifiestan como funciones internas a las que no se puede llamar fácilmente desde las pruebas unitarias. Habitualmente, se debe apuntar a niveles de cobertura superiores al 90%; no pruebe sólo los casos cubiertos por los requisitos del proyecto, pruebe todo.</p>

    <p>Al igual que con <link xref="version-control">git commits</link>,  cada prueba unitaria debería ser “tan pequeña como sea posible, pero no más pequeña”, probando una sola API o comportamiento. Cada caso de prueba debe ser capaz de ejecutarse individualmente, sin depender del estado de otros casos de prueba. Esto es importante para permitir la depuración de una prueba errónea sin tener que recorrer también todos los demás códigos de prueba. También significa que una única prueba errónea se puede rastrear fácilmente a una API específica, en lugar de un mensaje genérico de “las pruebas unitarias fallaron en algún lugar”.</p>

    <p>GLib tiene soporte para pruebas unitarias con su <link href="https://developer.gnome.org/glib/stable/glib-Testing.html">GTest framework</link>, que permite organizar las pruebas en grupos y jerarquías. Esto significa que los grupos de pruebas relacionadas también se pueden ejecutar juntos para una mejor depuración, ejecutando el binario de prueba con el argumento <cmd>-p</cmd> : <cmd>./test-suite-name -p /path/to/test/group</cmd>.</p>
  </section>

  <section id="installed-tests">
    <title>Pruebas instaladas</title>

    <p>Todas las pruebas unitarias deberían instalarse en todo el sistema, siguiendo el <link href="https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests">estándar de pruebas instaladas</link>.</p>

    <p>Mediante la instalación de pruebas unitarias, la integración continua (IC) se hace más fácil, ya que las pruebas para un proyecto se pueden volver a ejecutar después de cambios en otros proyectos en el entorno IC, probando así las interfaces entre módulos. Esto es útil para un conjunto de proyecto altamente acoplados como GNOME.</p>

    <p>Para añadir compatibilidad con las pruebas instaladas, añada lo siguiente a <file>configure.ac</file>:</p>
    <code># Installed tests
AC_ARG_ENABLE([modular_tests],
              AS_HELP_STRING([--disable-modular-tests],
                             [Disable build of test programs (default: no)]),,
              [enable_modular_tests=yes])
AC_ARG_ENABLE([installed_tests],
              AS_HELP_STRING([--enable-installed-tests],
                             [Install test programs (default: no)]),,
              [enable_installed_tests=no])
AM_CONDITIONAL([BUILD_MODULAR_TESTS],
               [test "$enable_modular_tests" = "yes" ||
                test "$enable_installed_tests" = "yes"])
AM_CONDITIONAL([BUILDOPT_INSTALL_TESTS],[test "$enable_installed_tests" = "yes"])</code>

    <p>En el <file>tests/Makefile.am</file>:</p>
    <code>insttestdir = $(libexecdir)/installed-tests/[project]

all_test_programs = \
	test-program1 \
	test-program2 \
	test-program3 \
	$(NULL)
if BUILD_MODULAR_TESTS
TESTS = $(all_test_programs)
noinst_PROGRAMS = $(TESTS)
endif

if BUILDOPT_INSTALL_TESTS
insttest_PROGRAMS = $(all_test_programs)

testmetadir = $(datadir)/installed-tests/[project]
testmeta_DATA = $(all_test_programs:=.test)

testdatadir = $(insttestdir)
testdata_DATA = $(test_files)

testdata_SCRIPTS = $(test_script_files)
endif

EXTRA_DIST = $(test_files)

%.test: % Makefile
	$(AM_V_GEN) (echo '[Test]' &gt; $@.tmp; \
	echo 'Type=session' &gt;&gt; $@.tmp; \
	echo 'Exec=$(insttestdir)/$&lt;' &gt;&gt; $@.tmp; \
	mv $@.tmp $@)</code>
  </section>

  <section id="leak-checking">
    <title>Comprobación de fugas</title>

    <p>Una vez se han escrito pruebas unitarias con alta cobertura de código, se pueden ejecutar bajo varias herramientas de análisis dinámico, como <link xref="tooling#valgrind">Valgrind</link> para comprobar si hay fugas, errores de ejecución de hilos, problemas de asignación, etc. en toda la base del código. Cuanto mayor sea la cobertura del código de las pruebas unitarias, más fiables serás los resultados de Valgrind. Véase <link xref="tooling"/> para más información, incluyendo las instrucciones de integración de sistemas de compilación.</p>

    <p>Críticamente, esto significa que las pruebas unitarias no deberían perder memoria u otros recursos, y de manera similar no deberían tener problemas de ejecución de hilos. Todos estos problemas serían efectivamente falsos positivos en el análisis del código real del proyecto. (Los falsos positivos que deben corregirse arreglando las pruebas unitarias).</p>
  </section>

  <section id="test-generation">
    <title>Generación de pruebas</title>

    <p>Ciertos tipos de código son bastante repetitivos y requieren muchas pruebas unitarias para obtener una buena cobertura; pero son apropiados para la <link href="https://en.wikipedia.org/wiki/Test_data_generation">generación de datos de prueba</link>, donde se utiliza una herramienta para generar automáticamente vectores de prueba para el código. Esto puede reducir drásticamente el tiempo necesario para escribir pruebas unitarias para el código en esto dominios específicos.</p>

    <section id="json">
      <title>JSON</title>

      <p>Un ejemplo de dominio dispuesto para probar la generación de datos es el análisis, donde los datos que se van a analizar deben seguir un esquema estricto — este es el caso de los documentos XML y JSON. Para JSON una herramienta como <link href="http://people.collabora.com/~pwith/walbottle/">Walbottle</link> se puede utilizar para generar vectores de prueba para todo tipo de entradas válidas y no válidas de acuerdo con el esquema.</p>

      <p>Cada tipo de documento JSON debe tener un <link href="http://json-schema.org/">esquema JSON</link> definido para él, que luego puede  pasarse a Walbottle para generar vectores de prueba:</p>
      <code mime="application/x-shellscript">
json-schema-generate --valid-only schema.json
json-schema-generate --invalid-only schema.json</code>

      <p>Estos vectores de prueba pueden usarse al código que se está probando en sus pruebas unitarias. Las instancias JSON generadas por <cmd>—valid-only</cmd> se deben aceptar; las de <cmd>—invalid-only</cmd> se deben rechazar.</p>
    </section>
  </section>

  <section id="writing-testable-code">
    <title>Escribir código comprobable</title>

    <p>El código se debe escribir con la capacidad de prueba en mente desde la fase de diseño, ya que afecta al diseño y a la arquitectura de la API de manera fundamental. Algunos principios clave:</p>
    <list>
      <item><p>No utilice el estado global. Los objetos Singleton generalmente son una mala idea, ya que no pueden instanciarse por separado ni controlarse en las pruebas unitarias.</p></item>
      <item><p>Separe el uso del estado externo, como bases de datos, redes o el sistema de archivos. Las pruebas unitarias pueden reemplazar los accesos al estado externo con objetos simulados. Un enfoque común a esto es usar la inyección de dependencias para pasar un objeto contenedor del sistema de archivos al código que se está probando. Por ejemplo, una clase no debería cargar una base de datos global (desde una ubicación fija en el sistema de archivos) porque las pruebas unitarias podrían sobrescribir la copia de la base de datos del sistema en ejecución y nunca podrían ejecutarse en paralelo. Se les debe pasar un objeto que proporcione una interfaz a la base de datos: en un sistema en producción, esto sería una envoltura delgada alrededor de la API de la base de datos; para las pruebas, sería un objeto simulado que verifica las solicitudes que recibe y devuelve respuestas codificadas para varias pruebas.</p></item>
      <item><p>Exponga las funciones de utilidad donde puedan ser generalmente útiles.</p></item>
      <item><p>Divida los proyectos en colecciones de pequeñas bibliotecas privadas que luego se unen con una cantidad mínima de código de pegamento en el ejecutable general. Cada una se puede probar por separado.</p></item>
    </list>
  </section>

  <section id="external-links">
    <title>Enlaces externos</title>

    <p>El tema de la comprobabilidad se cubre en los siguientes artículos:</p>
    <list>
      <item><p><link href="http://msdn.microsoft.com/en-us/magazine/dd263069.aspx">Diseño para la comprobabilidad</link></p></item>
      <item><p><link href="https://en.wikipedia.org/wiki/Software_testability">Verificación de software</link></p></item>
      <item><p><link href="https://en.wikipedia.org/wiki/Dependency_injection">Inyección de dependencias</link></p></item>
      <item><p><link href="http://c2.com/cgi/wiki?SoftwareDesignForTesting">Diseño de software para pruebas</link></p></item>
    </list>
  </section>
</page>
