Saltar al contenido

Blog técnico | Diferentes tipos de pruebas de unidad

Hay muchos tipos diferentes de pruebas. Hay muchos tipos diferentes de pruebas automatizadas. En este post hablaremos de las pruebas unitarias, específicamente de algunos tipos diferentes de pruebas unitarias y de cuándo podría querer usar cada una. Usaremos ejemplos en C# usando Nunit.

Código de ejemplo

Usaremos algún código de ejemplo para partes de la discusión en este post. Este código toma un nombre de usuario y una contraseña y, suponiendo que coincidan con el nombre de usuario y la contraseña almacenados, generará un JWT firmado para el usuario y lo almacenará en una cookie.

Blog técnico | Diferentes tipos de pruebas de unidad
Blog técnico | Diferentes tipos de pruebas de unidad
publicclassSignInCommand{//Constantes y variables de clase eliminadas por brevedad.publicSignInCommand(ICookieHelpercookieHelper,ITokenGeneratortokenGenerator,IUserRepositoryuserRepository,IPasswordHasher passwordHasher){this.cookieHelper=cookieHelper;this.tokenGenerator=tokenGenerator;this.userRepository=userRepository;this. passwordHasher=contraseñaHasher;}publicasyncTask<bool>Execute(stringusername,stringpassword){varuser=awaituserRepository.Load(username);if(user==null)returnfalse;if(!passwordHasher. Devolver(usuario.Salt,usuario.Hash,contraseña))returnnfalse;vartoken=tokenGenerator.Encode(nombredeusuario,usuario.NombreDeJugador);cookieHelper.SetCookie(NombreDeCookie,token);returntrue;}}

Arreglar-actuar-afirmar

La prueba de la unidad canónica es aquella en la que se quiere probar un trozo de código de una sola manera y se quiere verificar que un efecto de ese código era correcto. Dependiendo de la complejidad del código, puede ser necesaria alguna configuración antes de que el código bajo prueba pueda ser ejecutado. Este patrón de configuración de las condiciones adecuadas para una prueba, ejecutando el código bajo prueba, y luego verificando el resultado se llama a menudo el patrón arreglar-actuar-afirmar .

Usando nuestro código de ejemplo de arriba, aquí hay una prueba de unidad de arreglo-actuación para el caso en que el nombre de usuario es válido pero se suministra una contraseña incorrecta.

clase públicaAl ejecutarSignInWithIncorrectPassword:ConUnAutomocked&lt;SignInCommand,<[Prueba]publicevitarQueDebaFall(){GetMock&lt;IUserRepository Setup(x==.Load(IsAny&lt;string,&())).Returns(Task.FromResult(newUser()));GetMock&lt;IPasswordHasher,&(). Setup(x={[x]};x.DoesPasswordMatch(IsAny&lt;string(),IsAny&lt;string(),IsAny&lt;string())).Returns(false);varresult=ClassUnderTest.Execute("nombre de usuario", "contraseña incorrecta").Result;Assert.That(result,Is.False);}}

La clase base WithAnAutomocked es sólo una clase de ayuda que instancia la clase proporcionada (la clase SignInCommand en el código de arriba) e inyecta todas las dependencias del constructor para esa clase como burlas de Moq. Utiliza AutoMoq para hacer esto.

Con esta prueba de ejemplo se puede ver un fuerte patrón de arreglo-actuación-afirmación. Hay un código para establecer algún comportamiento en algunas burlas de las que depende el código que se está probando (arreglar). El código bajo prueba es entonces ejecutado (actuar), y finalmente comprobamos para asegurarnos de que el código hizo lo que se supone que debía hacer (afirmar).

Este tipo de prueba es genial cuando se quiere comprobar una situación específica o un conjunto específico de entradas. También es genial cuando quieres verificar una sola cosa sobre la ejecución de tu código.

Un acto, muchas afirmaciones

A veces, al probar un pedazo de código, queremos ejecutar el código y luego hacer afirmaciones sobre los múltiples efectos que ese código tuvo. De nuevo, podríamos crear una prueba estándar de estilo arreglar-actuar-afirmar para manejar este caso. Sólo tendríamos que añadir múltiples afirmaciones al final de nuestra prueba. Algunas personas se oponen vehementemente a tener afirmaciones múltiples en sus pruebas. Yo no me opongo del todo, pero lo considero un olor. Uno de los problemas con las afirmaciones múltiples es que cuando una prueba falla puede ser difícil determinar qué parte del código ha fallado realmente. Si las afirmaciones son independientes, pero todas pasan o fallan juntas puede ser necesario recurrir a rastros de pila o mensajes de error para determinar qué parte del código ha fallado realmente. Además, al menos en el caso de Nunit, el fallo de una sola afirmación en una prueba hará que se detenga la ejecución de la prueba. Así que un fallo al principio de una prueba puede enmascarar fallos adicionales más tarde en la prueba.

Afortunadamente, hay una solución a este problema. La solución es usar un estilo a veces llamado prueba de desarrollo impulsado por el comportamiento (BDD) o prueba de especificación .

clase públicaAl ejecutarValidSignIn:WithAnAutomocked&lt;SignInCommand &gt;{privateboolresult;privatestringtokenValue="valor simbólico"; [SetUp]publicvoidSetUp(){varuser=newUser{Nombredeusuario="nombredeusuario",PlayerName="Nombredeusuario"};GetMock&lt;IUserRepository &gt;(). Setup(x=;x.Load("nombredeusuario")).Returns(Task.FromResult(user));GetMock&lt;ITokenGenerator&gt;().Setup(x=;x.Encode("nombredeusuario", "NombredeJugador")). Returns(tokenValue);GetMock&lt;IPasswordHashergdomgdom;().Setup(x===;x.DoesPasswordMatch(IsAny&lt;string Result;} [Test]publicvoidShouldSucceed()=.Assert.That(result,Is.True); [Test]publicvoidShouldSetCookie()=.GetMock&lt;ICookieHelper;}.Verify(x=.SetCookie(SignInCommand.CookieName,tokenValue));}

En este ejemplo volvemos al ejemplo del SignInCommand. Pero en este caso estamos probando el camino en el que el inicio de sesión fue exitoso. Con este código en particular queremos asegurarnos de que se devuelva el resultado correcto y de que la cookie fue establecida. Estos dos resultados podrían fácilmente tener éxito o fallar independientemente el uno del otro. Por lo tanto, ponerlos como dos afirmaciones en la misma prueba no tiene sentido por las razones mencionadas anteriormente.

Este tipo de prueba es genial cuando hay múltiples efectos que puede tener un pedazo de código que puede tener éxito o fracasar independientemente. En este ejemplo en particular sólo hay dos afirmaciones de este tipo, pero puedes imaginar que piezas más complejas de código podrían tener muchas más. Este ejemplo en particular sólo utiliza Nunit para obtener este estilo de prueba, pero también hay marcos de prueba orientados específicamente a este tipo de prueba. Algunos ejemplos en .NET incluyen Especificaciones de la máquina y Especificaciones de la unidad.

Casos de prueba

A veces tenemos un código en el que queremos probar un montón de diferentes entradas que se combinan con un montón de diferentes salidas. Dado que nuestro código de ejemplo anterior para el SignInCommand no se presta demasiado bien a este tipo de prueba, vamos a utilizar un ejemplo diferente para este tipo de prueba de unidad. Imaginemos que queremos probar un código que toma un entero y devuelve ese valor entero convertido a un número romano. Para este tipo de código algorítmico querremos proporcionar muchas entradas diferentes para probar todas las diferentes conversiones de números romanos y verificar que obtenemos la salida correcta. Podríamos escribir una nueva prueba de estilo arreglar-actuar-afirmar para cada conjunto de entrada y salida que queramos probar. Pero si lo hiciéramos, terminaríamos con un montón de código duplicado. Y deberías asegurarte de que tu código de prueba es tan mantenible como sea posible, como con tu código de producción.

Afortunadamente, también es posible escribir pruebas de unidad para este tipo de escenarios. Nunit tiene una característica específica para este tipo de pruebas. Si la biblioteca de pruebas unitarias de su elección no tiene tal característica, siempre puede crear algún tipo de colección para almacenar sus pares de entrada-salida y luego iterar sobre esa colección. No es tan hábil como cuando la capacidad está incorporada en su biblioteca de pruebas unitarias, pero aún así puede eliminar el código duplicado y permitirle añadir nuevos casos de prueba rápidamente.

public classRomanNumeralConversionTests{ [TestCase(0, "")] [TestCase(1, "I")] [TestCase(2, "II")] [TestCase(4, "IV")] [TestCase(5, "V")] [TestCase(6, "VI")] [TestCase(10, "X")] [TestCase(9, "IX")] [TestCase(40, "XL")] [TestCase(50, "L")] [TestCase(90, "XC")] [TestCase(100, "C")] [TestCase(400, "CD")] [TestCase(500, "D")] [TestCase(900, "CM")] [TestCase(1000, "M")]publicvoidConvertTests(intarabic,stringroman){Afirmar. Eso (árabe a romano. Convertir (árabe), es igual a (romano)).

En este ejemplo se puede ver que si tuviéramos diferentes pruebas de estilo arreglar-actuar-afirmar para cada combinación de entrada y salida habría mucho código duplicado. Y cada vez que quisiéramos añadir un caso de prueba adicional, implicaría duplicar más código. Usando el atributo TestCase de Nunit, podemos eliminar mucha duplicación y facilitar la adición de casos de prueba en el futuro. También podemos indicar más claramente qué es lo que estamos tratando de probar. No estamos tratando de probar que 1 se convierte en I necesariamente. Realmente estamos tratando de probar que todo el proceso de conversión funciona como se espera. Poner todas nuestras pruebas relacionadas con la conversión juntas usando casos de prueba como este nos permite mostrar mejor nuestra intención.

¿Qué tipo de prueba de unidad debe usar?

Los tres tipos de pruebas unitarias descritas anteriormente son cada una de ellas grandes en su propio contexto. Si estás probando un código que tiene múltiples efectos que pueden tener éxito o fallar de forma independiente, una prueba de especificación podría ser una gran opción. Si estás probando código en el que quieres asegurarte de que los múltiples conjuntos de entrada y salida son correctos, una prueba de estilo podría ser una gran opción. Y en caso de duda, una prueba de tipo arreglado-actuar-afirmar es un gran lugar para empezar.

Recuerda que el código de prueba también es un código. Debe ser elaborado con cuidado, al igual que el código de producción. Debe eliminarse la duplicación, debe ser refactorizado para aumentar su mantenimiento, y debe evolucionar con el tiempo. Esperemos que los ejemplos dados aquí le den algunas ideas para ayudar a que su código de prueba sea aún mejor de lo que ya es.

Categorías: technicalTags: testing, c-sharp