Usando C #, necesito una clase llamada User
que tenga un nombre de usuario, contraseña, bandera activa, nombre, apellido, nombre completo, etc.
Debe haber métodos para autenticar y guardar un usuario. ¿Acabo de escribir una prueba para los métodos? ¿Y debo preocuparme por probar las propiedades ya que son getter y setters de .Net?
c#
unit-testing
tdd
nbro
fuente
fuente
Respuestas:
Muchas buenas respuestas a esto también están en mi pregunta: " Principio de TDD - ¿Desafíos? ¿Soluciones? ¿Recomendaciones? "
¿Puedo recomendar también echar un vistazo a mi publicación de blog (que fue inspirada en parte por mi pregunta), tengo algunos buenos comentarios al respecto. A saber:
Espero que esto signifique que podamos pasar de "captadores y setters" :)
fuente
Prueba tu código, no el idioma.
Una prueba unitaria como:
solo es útil si está escribiendo un compilador y existe una probabilidad distinta de cero de que su
instanceof
método no funcione.No pruebe cosas en las que pueda confiar en el idioma para hacer cumplir. En su caso, me enfocaría en sus métodos de autenticación y guardado, y escribiría pruebas que se aseguraran de que pudieran manejar los valores nulos en cualquiera o todos esos campos con gracia.
fuente
Esto me llevó a las pruebas unitarias y me hizo muy feliz
Acabamos de comenzar a hacer pruebas unitarias. Durante mucho tiempo supe que sería bueno comenzar a hacerlo, pero no tenía idea de cómo comenzar y, lo que es más importante, qué probar.
Luego tuvimos que volver a escribir un código importante en nuestro programa de contabilidad. Esta parte fue muy compleja ya que involucraba muchos escenarios diferentes. La parte de la que estoy hablando es un método para pagar facturas de ventas y / o compras ya ingresadas en el sistema de contabilidad.
Simplemente no sabía cómo comenzar a codificarlo, ya que había muchas opciones de pago diferentes. Una factura podría ser de $ 100 pero el cliente solo transfirió $ 99. Tal vez ha enviado facturas de ventas a un cliente pero también ha comprado a ese cliente. Entonces lo vendiste por $ 300 pero lo compraste por $ 100. Puede esperar que su cliente le pague $ 200 para liquidar el saldo. ¿Y qué pasa si vendió por $ 500 pero el cliente le paga solo $ 250?
Así que tuve que resolver un problema muy complejo con muchas posibilidades de que un escenario funcionaría perfectamente pero estaría mal en otro tipo de combinación de factura / pago.
Aquí es donde las pruebas unitarias vinieron al rescate.
Comencé a escribir (dentro del código de prueba) un método para crear una lista de facturas, tanto para ventas como para compras. Luego escribí un segundo método para crear el pago real. Normalmente, un usuario ingresaría esa información a través de una interfaz de usuario.
Luego creé el primer TestMethod, probando un pago muy simple de una sola factura sin ningún descuento de pago. Toda la acción en el sistema ocurriría cuando un pago bancario se guardara en la base de datos. Como puede ver, creé una factura, creé un pago (una transacción bancaria) y guardé la transacción en el disco. En mis afirmaciones pongo lo que deberían ser los números correctos que terminan en la transacción del Banco y en la Factura vinculada. Verifico la cantidad de pagos, los montos de los pagos, el monto del descuento y el saldo de la factura después de la transacción.
Después de ejecutar la prueba, iría a la base de datos y verificaría si lo que esperaba estaba allí.
Después de escribir la prueba, comencé a codificar el método de pago (parte de la clase BankHeader). En la codificación solo me molesté con el código para pasar la primera prueba. Todavía no pensaba en los otros escenarios más complejos.
Ejecuté la primera prueba, arreglé un pequeño error hasta que mi prueba pasara.
Luego comencé a escribir la segunda prueba, esta vez trabajando con un descuento de pago. Después de escribir la prueba, modifiqué el método de pago para admitir descuentos.
Mientras probaba la corrección con un descuento de pago, también probé el pago simple. Ambas pruebas deberían pasar, por supuesto.
Luego me abrí camino hacia los escenarios más complejos.
1) Piensa en un nuevo escenario
2) Escribe una prueba para ese escenario
3) Ejecute esa prueba única para ver si pasaría
4) Si no fuera así, depuraría y modificaría el código hasta que pasara.
5) Mientras modificaba el código seguí ejecutando todas las pruebas
Así es como logré crear mi método de pago muy complejo. Sin pruebas unitarias no sabía cómo comenzar a codificar, el problema parecía abrumador. Con las pruebas, podría comenzar con un método simple y extenderlo paso a paso con la seguridad de que los escenarios más simples seguirían funcionando.
Estoy seguro de que el uso de las pruebas unitarias me ahorró unos días (o semanas) de codificación y garantiza más o menos la exactitud de mi método.
Si luego pienso en un nuevo escenario, puedo agregarlo a las pruebas para ver si funciona o no. Si no, puedo modificar el código pero aún así estar seguro de que los otros escenarios todavía funcionan correctamente. Esto ahorrará días y días en la fase de mantenimiento y corrección de errores.
Sí, incluso el código probado puede tener errores si un usuario hace cosas en las que no pensó o le impidió hacer
A continuación se detallan algunas de las pruebas que creé para probar mi método de pago.
fuente
Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
Si realmente son triviales, entonces no te molestes en probar. Por ejemplo, si se implementan así;
Si, por otro lado, está haciendo algo inteligente (como cifrar y descifrar la contraseña en el captador / configurador), entonces pruébelo.
fuente
La regla es que tienes que probar cada pieza de lógica que escribes. Si implementó alguna funcionalidad específica en los captadores y definidores, creo que vale la pena probarlos. Si solo asignan valores a algunos campos privados, no se moleste.
fuente
Esta pregunta parece ser una pregunta de dónde se dibuja la línea sobre qué métodos se prueban y cuáles no.
Los establecedores y captadores para la asignación de valor se han creado teniendo en cuenta la coherencia y el crecimiento futuro, y previendo que en algún momento el configurador / captador puede evolucionar hacia operaciones más complejas. Tendría sentido poner en práctica pruebas unitarias de esos métodos, también en aras de la coherencia y el crecimiento futuro.
El objetivo principal es la confiabilidad del código, especialmente durante los cambios para agregar funcionalidad adicional. No estoy al tanto de que alguien haya sido despedido por incluir setters / getters en la metodología de prueba, pero estoy seguro de que existen personas que desearon haber probado métodos que lo último que sabían o pueden recordar eran simples set / get wrappers, pero eso no fue así. Ya no es el caso.
Quizás otro miembro del equipo amplió los métodos set / get para incluir la lógica que ahora necesita ser probada pero que luego no creó las pruebas. Pero ahora su código está llamando a estos métodos y no sabe que han cambiado y necesitan pruebas exhaustivas, y las pruebas que realiza en desarrollo y control de calidad no desencadenan el defecto, pero los datos comerciales reales el primer día del lanzamiento sí desencadenarlo.
Los dos compañeros de equipo ahora debatirán sobre quién dejó caer la pelota y no pudo realizar pruebas unitarias cuando el conjunto / se transforma para incluir una lógica que puede fallar pero no está cubierta por una prueba unitaria. El compañero de equipo que originalmente escribió el set / gets tendrá más facilidad para salir de esta limpieza si las pruebas se implementaron desde el primer día en el set / gets simple.
Mi opinión es que unos minutos de tiempo "perdido" que cubre TODOS los métodos con pruebas unitarias, incluso los triviales, pueden ahorrar días de dolor de cabeza en el futuro y la pérdida de dinero / reputación del negocio y la pérdida del trabajo de alguien.
Y el hecho de que usted envolvió métodos triviales con pruebas unitarias podría ser visto por ese compañero de equipo junior cuando cambian los métodos triviales por otros no triviales y les pide que actualicen la prueba, y ahora nadie está en problemas porque el defecto estaba contenido de alcanzar la producción.
La forma en que codificamos y la disciplina que se puede ver en nuestro código pueden ayudar a otros.
fuente
Otra respuesta canónica. Esto, creo, de Ron Jeffries:
fuente
Probar el código repetitivo es una pérdida de tiempo, pero como dice Slavo, si agrega un efecto secundario a sus captadores / establecedores, entonces debe escribir una prueba para acompañar esa funcionalidad.
Si está realizando un desarrollo basado en pruebas, primero debe escribir el contrato (por ejemplo, la interfaz) y luego escribir las pruebas para ejercer esa interfaz que documenta los resultados / comportamiento esperados. Luego escriba sus propios métodos, sin tocar el código en sus pruebas unitarias. Finalmente, tome una herramienta de cobertura de código y asegúrese de que sus pruebas ejerciten todas las rutas lógicas en su código.
fuente
Los códigos realmente triviales como getters y setters que no tienen un comportamiento adicional que establecer un campo privado son excesivos para probar. En 3.0 C # incluso tiene algo de azúcar sintáctica donde el compilador se encarga del campo privado para que no tenga que programar eso.
Normalmente escribo muchas pruebas muy simples para verificar el comportamiento que espero de mis clases. Incluso si es algo simple como sumar dos números. Cambio mucho entre escribir una prueba simple y escribir algunas líneas de código. La razón de esto es que luego puedo cambiar el código sin tener miedo de romper cosas en las que no pensé.
fuente
Deberías probar todo. En este momento tienes captadores y establecedores, pero algún día podrías cambiarlos un poco, tal vez para hacer la validación o algo más. Las pruebas que escriba hoy se usarán mañana para asegurarse de que todo siga funcionando como de costumbre. Cuando escriba prueba, debe olvidar consideraciones como "ahora es trivial". En un contexto ágil o basado en pruebas, debe probar asumiendo una futura refactorización. Además, ¿trataste de poner valores realmente extraños como cadenas extremadamente largas u otro contenido "malo"? Bueno, deberías ... nunca asumir lo mal que se puede abusar de tu código en el futuro.
En general, encuentro que escribir extensas pruebas de usuario es, por un lado, agotador. Por otro lado, aunque siempre le brinda información invaluable sobre cómo debería funcionar su aplicación y lo ayuda a descartar suposiciones fáciles (y falsas) (como: el nombre de usuario siempre tendrá menos de 1000 caracteres de longitud).
fuente
Para los módulos simples que pueden terminar en un kit de herramientas, o en un tipo de proyecto de código abierto, debe probar tanto como sea posible, incluidos los captadores y establecedores triviales. Lo que debe tener en cuenta es que generar una prueba unitaria a medida que escribe un módulo en particular es bastante simple y directo. Agregar getters y setters es un código mínimo y se puede manejar sin pensarlo mucho. Sin embargo, una vez que su código se coloca en un sistema más grande, este esfuerzo adicional puede protegerlo contra cambios en el sistema subyacente, como los cambios de tipo en una clase base. Probar todo es la mejor manera de tener una regresión completa.
fuente
No está de más escribir pruebas unitarias para sus captadores y establecedores. En este momento, es posible que solo estén haciendo get / sets de campo bajo el capó, pero en el futuro es posible que tenga una lógica de validación o dependencias entre propiedades que deben probarse. Es más fácil escribirlo ahora mientras lo piensas y luego recordar actualizarlo si llega ese momento.
fuente
en general, cuando un método solo se define para ciertos valores, pruebe los valores dentro y fuera del límite de lo que es aceptable. En otras palabras, asegúrese de que su método haga lo que se supone que debe hacer, pero nada más . Esto es importante, porque cuando vas a fallar, quieres fallar temprano.
En las jerarquías de herencia, asegúrese de probar el cumplimiento de LSP .
Probar getters y setters predeterminados no me parece muy útil, a menos que esté planeando hacer alguna validación más adelante.
fuente
bueno, si crees que puede romperse, escribe una prueba para ello. Por lo general, no pruebo setter / getter, pero digamos que haces uno para User.Name, que concatenan el nombre y el apellido, escribiría una prueba para que si alguien cambia el orden de apellido y nombre, al menos lo sabría él cambió algo que fue probado.
fuente
La respuesta canónica es "prueba cualquier cosa que pueda romperse". Si está seguro de que las propiedades no se romperán, no las pruebe.
Y una vez que se descubre que algo se ha roto (encuentra un error), obviamente significa que necesita probarlo. Escriba una prueba para reproducir el error, vea cómo falla, luego corrija el error, luego vea la prueba pasar.
fuente
Según entiendo las pruebas unitarias en el contexto del desarrollo ágil, Mike, sí, debe probar los captadores y establecedores (suponiendo que sean públicamente visibles). Todo el concepto de pruebas unitarias es probar la unidad de software, que es una clase en este caso, como una caja negra . Dado que los captadores y establecedores son visibles desde el exterior, debe probarlos junto con Autenticar y Guardar.
fuente
Si los métodos Autenticar y Guardar utilizan las propiedades, sus pruebas tocarán indirectamente las propiedades. Siempre y cuando las propiedades solo proporcionen acceso a los datos, entonces no deberían ser necesarias pruebas explícitas (a menos que esté buscando una cobertura del 100%).
fuente
Probaría tus captadores y setters. Dependiendo de quién está escribiendo el código, algunas personas cambian el significado de los métodos getter / setter. He visto la inicialización de variables y otras validaciones como parte de los métodos getter. Para probar este tipo de cosas, querrás pruebas unitarias que cubran ese código explícitamente.
fuente
Personalmente, "probaría cualquier cosa que pueda romperse" y el getter simple (o incluso las mejores propiedades automáticas) no se romperán. Nunca me ha fallado una simple declaración de retorno y, por lo tanto, nunca he tenido una prueba para ellos. Si los captadores tienen cálculos dentro de ellos o alguna otra forma de declaraciones, ciertamente agregaría pruebas para ellos.
Personalmente, uso Moq como un marco de objeto simulado y luego verifico que mi objeto llama a los objetos circundantes como debería.
fuente
Debe cubrir la ejecución de cada método de la clase con UT y verificar el valor de retorno del método. Esto incluye getters y setters, especialmente en el caso de que los miembros (propiedades) sean clases complejas, lo que requiere una gran asignación de memoria durante su inicialización. Llame al setter con una cadena muy grande, por ejemplo (o algo con símbolos griegos) y verifique que el resultado sea correcto (no truncado, la codificación es buena, etc.)
En el caso de enteros simples que también se aplican, ¿qué sucede si pasa mucho tiempo en lugar de un entero? Esa es la razón por la que escribes UT para :)
fuente
No probaría la configuración real de las propiedades. Me preocuparía más cómo esas propiedades son pobladas por el consumidor y con qué las pueblan. Con cualquier prueba, debe sopesar los riesgos con el tiempo / costo de la prueba.
fuente
Debe probar "cada bloque de código no trivial" utilizando pruebas unitarias en la medida de lo posible.
Si sus propiedades son triviales y es poco probable que alguien introduzca un error en ellas, entonces debería ser seguro no probarlas en la unidad.
Sus métodos Authenticate () y Save () parecen buenos candidatos para las pruebas.
fuente
Idealmente, habrías hecho tus pruebas unitarias mientras escribías la clase. Así es como debes hacerlo cuando utilizas Test Driven Development. Agrega las pruebas a medida que implementa cada punto de función, asegurándose de cubrir también los casos límite con la prueba.
Escribir las pruebas después es mucho más doloroso, pero factible.
Esto es lo que haría en tu posición:
Esto debería darle un buen conjunto de pruebas unitarias que funcionarán como un buen amortiguador contra las regresiones.
El único problema con este enfoque es que el código debe diseñarse para ser comprobable de esta manera. Si cometió algún error de acoplamiento desde el principio, no podrá obtener una alta cobertura muy fácilmente.
Es por eso que es realmente importante escribir las pruebas antes de escribir el código. Te obliga a escribir código que está débilmente acoplado.
fuente
No pruebe el código que funciona obviamente (repetitivo). Por lo tanto, si sus establecedores y captadores son simplemente "propertyvalue = value" y "return propertyvalue", no tiene sentido probarlo.
fuente
Incluso get / set puede tener consecuencias extrañas, dependiendo de cómo se hayan implementado, por lo que deben tratarse como métodos.
Cada prueba de estos necesitará especificar conjuntos de parámetros para las propiedades, definiendo propiedades aceptables e inaceptables para asegurar que las llamadas regresen / fallen de la manera esperada.
También debe conocer las trampas de seguridad, como un ejemplo de inyección SQL, y probarlas.
Entonces sí, debe preocuparse por probar las propiedades.
fuente
Creo que es una tontería probar getters y setters cuando solo hacen una operación simple. Personalmente, no escribo pruebas unitarias complejas para cubrir ningún patrón de uso. Intento escribir suficientes pruebas para asegurarme de que he manejado el comportamiento de ejecución normal y la mayor cantidad de casos de error que se me ocurren. Escribiré más pruebas unitarias como respuesta a los informes de errores. Utilizo la prueba unitaria para asegurar que el código cumpla con los requisitos y para facilitar futuras modificaciones. Me siento mucho más dispuesto a cambiar el código cuando sé que si rompo algo, una prueba fallará.
fuente
Escribiría una prueba para cualquier cosa para la que esté escribiendo código que sea comprobable fuera de la interfaz GUI.
Por lo general, cualquier lógica que escriba que tenga alguna lógica de negocios la coloco dentro de otro nivel o capa de lógica de negocios.
Luego, escribir pruebas para cualquier cosa que haga algo es fácil de hacer.
Primer paso, escriba una prueba de unidad para cada método público en su "Capa de lógica de negocios".
Si tuviera una clase como esta:
Lo primero que haría antes de escribir cualquier código sabiendo que tenía que realizar estas acciones sería comenzar a escribir pruebas unitarias.
Escriba sus pruebas para validar el código que ha escrito para hacer algo. Si itera sobre una colección de cosas y cambia algo sobre cada una de ellas, escriba una prueba que haga lo mismo y afirme que realmente sucedió.
Hay muchos otros enfoques que puede tomar, a saber, Behavoir Driven Development (BDD), que es más complicado y no es un gran lugar para comenzar con sus habilidades de prueba de unidad.
Por lo tanto, la moraleja de la historia es: pruebe cualquier cosa que haga cualquier cosa que le preocupe, mantenga las pruebas unitarias probando cosas específicas de tamaño pequeño, muchas pruebas son buenas.
Mantenga su lógica empresarial fuera de la capa de interfaz de usuario para que pueda escribir fácilmente pruebas para ellos, y estará bien.
Recomiendo TestDriven.Net o ReSharper, ya que ambos se integran fácilmente en Visual Studio.
fuente
Recomendaría escribir múltiples pruebas para sus métodos de autenticación y guardado. Además del caso de éxito (donde se proporcionan todos los parámetros, todo está escrito correctamente, etc.), es bueno tener pruebas para varios casos de falla (parámetros incorrectos o faltantes, conexiones de base de datos no disponibles si corresponde, etc.). Recomiendo pruebas de unidad pragmáticas en C # con NUnit como referencia.
Como han dicho otros, las pruebas unitarias para captadores y establecedores son excesivas, a menos que haya lógica condicional en sus captadores y establecedores.
fuente
Si bien es posible adivinar correctamente dónde debe probarse su código, generalmente creo que necesita métricas para respaldar esta suposición. Las pruebas unitarias, en mi opinión, van de la mano con las métricas de cobertura de código.
Código con muchas pruebas pero una pequeña cobertura no ha sido bien probada. Dicho esto, el código con una cobertura del 100% pero que no prueba los casos de errores y límites tampoco es excelente.
Desea un equilibrio entre una alta cobertura (90% mínimo) y datos de entrada variables.
¡Recuerde hacer una prueba de "basura adentro"!
Además, una prueba unitaria no es una prueba unitaria a menos que verifique si hay una falla. ¡Las pruebas unitarias que no tienen afirmaciones o están marcadas con excepciones conocidas simplemente probarán que el código no muere cuando se ejecuta!
¡Necesita diseñar sus pruebas para que siempre reporten fallas o datos inesperados / no deseados!
fuente
Mejora nuestro código ... ¡punto!
Una cosa que los desarrolladores de software olvidamos cuando hacemos un desarrollo basado en pruebas es el propósito de nuestras acciones. Si se escribe una prueba unitaria después de que el código de producción ya está en su lugar, el valor de la prueba disminuye (pero no se pierde por completo).
En el verdadero espíritu de las pruebas unitarias, estas pruebas no están principalmente allí para "probar" más de nuestro código; o para obtener una cobertura de código 90% -100% mejor. Estos son todos los beneficios adicionales de escribir las pruebas primero. La gran recompensa es que nuestro código de producción final se escribirá mucho mejor debido al proceso natural de TDD.
Para ayudar a comunicar mejor esta idea, lo siguiente puede ser útil en la lectura:
La teoría defectuosa de las pruebas unitarias
Desarrollo de software con propósito
Si creemos que el acto de escribir más pruebas unitarias es lo que nos ayuda a obtener un producto de mayor calidad, entonces podemos estar sufriendo de un Cult Cargo de Desarrollo Impulsado por Pruebas.
fuente