¿Deben escribirse pruebas unitarias para captadores y colocadores?

141

¿Se supone que debemos escribir pruebas para nuestros captadores y establecedores o es excesivo?

Hector421
fuente
No lo creo. No debe escribir un caso de prueba para getter / setter.
Harry Joy
1
¿Asumo que te refieres a Java? Esta es una pregunta particularmente aguda para Java, mucho menos para lenguajes más modernos.
skaffman 01 de
@skaffman ¿Qué idiomas modernos no tienen propiedades? Claro, los lenguajes como Java requieren que sean cuerpos de métodos completos, pero eso no lo hace lógicamente diferente de decir C #.
Claus Jørgensen
2
@Claus: No dijo propiedades, dijo captadores y establecedores. En java los escribes manualmente, en otros idiomas obtienes un mejor soporte.
skaffman 01 de
2
Uno podría preguntarse por qué tienen getters y setters .
Raedwald

Respuestas:

180

Yo diría que no.

@Will dijo que deberías apuntar a una cobertura de código del 100%, pero en mi opinión es una distracción peligrosa. Puede escribir pruebas unitarias que tengan una cobertura del 100% y, sin embargo, no probar absolutamente nada.

Las pruebas unitarias están ahí para probar el comportamiento de su código, de manera expresiva y significativa, y los captadores / establecedores son solo un medio para un fin. Si las pruebas usan getters / setters para lograr su objetivo de probar la funcionalidad "real", entonces eso es lo suficientemente bueno.

Si, por otro lado, sus captadores y establecedores hacen más que simplemente obtener y configurar (es decir, son métodos propiamente complejos), entonces sí, deberían ser probados. Pero no escriba un caso de prueba de unidad solo para probar un captador o colocadores, eso es una pérdida de tiempo.

skaffman
fuente
77
Apuntar a una cobertura del código del 100% es una tontería. Terminará haciendo cobertura de código que no está expuesto al público, y / o código sin complejidad. Es mejor dejar estas cosas para la autogeneración, pero incluso las pruebas autogeneradas son bastante inútiles. Es mejor cambiar el enfoque a pruebas importantes, como las pruebas de integración.
Claus Jørgensen
77
@Claus: Estoy de acuerdo, excepto por un poco sobre centrarme en las pruebas de integración. Las pruebas unitarias son críticas para un buen diseño y complementan las pruebas de integración.
skaffman 01 de
55
Creo que una cobertura de código del 100% cuando se ejecuta todo el conjunto de pruebas es un buen objetivo. Los captadores de propiedades y otros códigos se ejecutan como parte de pruebas más grandes. Las últimas piezas a cubrir son probablemente el manejo de errores. Y si el manejo de errores no está cubierto por las pruebas unitarias, nunca lo estará. ¿Realmente desea enviar un producto que contenga código que nunca se haya ejecutado?
Anders Abel
Tendería a estar en desacuerdo con este consejo. Sí, getters y setters son simples, pero también son sorprendentemente fáciles de estropear. Unas pocas líneas de pruebas simples y sabes que están funcionando y continúan funcionando.
Charles
Es posible que esté probando configuradores innecesarios que usted (o una herramienta) creó solo para tener un POJO 'redondeado'. Es posible que, por ejemplo, utilice Gson.fromJson para "inflar" POJOS (no se necesitan configuradores). En este caso, mi elección es eliminar los setters no utilizados.
Alberto Gaona
36

Roy Osherove en su famoso libro 'The Art Of Unit Testing' dice:

Las propiedades (getters / setters en Java) son buenos ejemplos de código que generalmente no contiene ninguna lógica y no requiere pruebas. Pero tenga cuidado: una vez que agregue cualquier cheque dentro de la propiedad, querrá asegurarse de que la lógica se esté probando.

azhidkov
fuente
1
Teniendo en cuenta el poco tiempo que lleva probarlos y la probabilidad de que se agreguen comportamientos, no veo por qué no. Sospecho que si se presiona, podría responder "bueno, supongo que por qué no".
Trineo el
1
Los primeros libros importantes a menudo tienen malos consejos. He visto muchos casos en los que las personas arrojan captadores y colocadores rápidamente y cometen errores porque están cortando y pegando u olvidan esto. o esto-> o algo así. Son fáciles de probar y ciertamente deberían ser probados.
Charles
35

Un rotundo SÍ con TDD


Nota : Esta respuesta sigue recibiendo votos positivos, aunque potencialmente es un mal consejo. Para entender por qué, eche un vistazo a su hermana pequeña a continuación.


Muy controvertido, pero diría que a cualquiera que responda 'no' a esta pregunta le falta un concepto fundamental de TDD.

Para mí, la respuesta es un rotundo si sigues TDD. Si no lo eres, entonces no es una respuesta plausible.

El DDD en TDD

TDD es a menudo citado por tener los principales beneficios.

  • Defensa
    • Asegurar que el código puede cambiar pero no su comportamiento .
    • Esto permite la práctica siempre tan importante de refactorización .
    • Ganas este TDD o no.
  • Diseño
    • Usted especifica qué debe hacer algo, cómo debe comportarse antes de implementarlo .
    • Esto a menudo significa decisiones de implementación más informadas .
  • Documentación
    • El conjunto de pruebas debe servir como la documentación de especificaciones (requisitos).
    • El uso de pruebas para tal propósito significa que la documentación y la implementación siempre están en un estado consistente; un cambio en uno significa un cambio en otro. Compare con los requisitos de mantenimiento y el diseño en un documento de Word separado.

Separar la responsabilidad de la implementación

Como programadores, es terriblemente tentador pensar en los atributos como algo significativo y en captar y establecer como una especie de sobrecarga.

Pero los atributos son un detalle de implementación, mientras que los establecedores y captadores son la interfaz contractual que realmente hace que los programas funcionen.

Es mucho más importante deletrear que un objeto debe:

Permitir a sus clientes cambiar su estado.

y

Permitir a sus clientes consultar su estado

entonces cómo se almacena realmente este estado (para el cual un atributo es la forma más común, pero no la única).

Una prueba como

(The Painter class) should store the provided colour

Es importante para la parte de documentación de TDD.

El hecho de que la implementación final es trivial (atributo) y no conlleva ningún beneficio de defensa debe ser desconocido para usted cuando escriba la prueba.

La falta de ingeniería de ida y vuelta ...

Uno de los problemas clave en el mundo del desarrollo del sistema es la falta de ingeniería de ida y vuelta 1 : el proceso de desarrollo de un sistema está fragmentado en subprocesos desarticulados cuyos artefactos (documentación, código) a menudo son inconsistentes.

1 Brodie, Michael L. "John Mylopoulos: cosiendo semillas del modelado conceptual". Modelado conceptual: fundamentos y aplicaciones. Springer Berlin Heidelberg, 2009. 1-9.

... y cómo lo resuelve TDD

Es la parte de documentación de TDD que asegura que las especificaciones del sistema y su código sean siempre consistentes.

Diseñe primero, implemente después

Dentro de TDD, primero escribimos la prueba de aceptación fallida, solo luego escribimos el código que les permitió pasar.

Dentro del BDD de nivel superior, primero escribimos escenarios y luego los hacemos pasar.

¿Por qué debería excluir setters y getter?

En teoría, es perfectamente posible dentro de TDD que una persona escriba la prueba y otra implemente el código que la hace pasar.

Entonces pregúntate a ti mismo:

En caso de que la persona que escribe las pruebas para una clase mencione captadores y colocadores.

Como getters y setters son una interfaz pública para una clase, la respuesta es obviamente , o no habrá forma de establecer o consultar el estado de un objeto. Sin embargo , la forma de hacerlo no es necesariamente probando cada método de forma aislada, consulte mi otra respuesta para obtener más información.

Obviamente, si escribe el código primero, la respuesta puede no ser tan clara.

Izhaki
fuente
2
Esa es solo una cara de la moneda. Piense en las cosas que podría haber hecho en lugar de probar solo por probar. Como dijo Kent Beck, le pagan por el código de trabajo, no por las pruebas de trabajo.
Georgii Oleinikov
@GeorgiiOleinikov ¿Has leído mi otra respuesta a continuación? Está más o menos en línea con su punto de vista.
Izhaki
21

tl; dr: , deberías , y con OpenPojo es trivial.

  1. Debería hacer alguna validación en sus captadores y definidores, por lo que debería probar eso. Por ejemplo, setMom(Person p)no debe permitir establecer a nadie más joven que ellos como su madre.

  2. Incluso si no está haciendo nada de eso ahora, lo más probable es que lo haga en el futuro, entonces esto será bueno para el análisis de regresión. Si desea permitir que las madres se establezcan null, debe hacerse una prueba para eso si alguien cambia eso más adelante, esto reforzará sus suposiciones.

  3. Un error común es void setFoo( Object foo ){ foo = foo; }dónde debería estar void setFoo( Object foo ){ this.foo = foo; }. (En el primer caso, lo fooque se está escribiendo es el parámetro, no el foocampo en el objeto ).

  4. Si está devolviendo una matriz o colección, debe probar si el getter realizará copias defensivas de los datos pasados ​​al setter antes de regresar.

  5. De lo contrario, si tiene los setters / getters más básicos, entonces la prueba unitaria de ellos agregará unos 10 minutos como máximo por objeto, entonces, ¿cuál es la pérdida? Si agrega comportamiento, ya tiene una prueba de esqueleto y obtiene esta prueba de regresión de forma gratuita. Si está utilizando Java, no tiene excusa ya que hay OpenPojo . Hay un conjunto de reglas existente que puede habilitar y luego escanear todo su proyecto con ellas para asegurarse de que se apliquen de manera consistente dentro de su código.

De sus ejemplos :

final PojoValidator pojoValidator = new PojoValidator();

//create rules
pojoValidator.addRule( new NoPublicFieldsRule  () );
pojoValidator.addRule( new NoPrimitivesRule    () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );

//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester            () );
pojoValidator.addTester( new GetterTester            () );

//test all the classes
for(  PojoClass  pojoClass :  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  )
    pojoValidator.runValidation( pojoClass );
Trineo
fuente
11
Sé que esto es antiguo ahora, pero: ¿DEBERÍAS estar validando tus captadores y establecedores? Tenía la impresión de que setMom debería hacer lo que dice. Si está validando, ¿no debería ser validateAndSetMom? O mejor, ¿no debería existir el código de validación EN OTRA PARTE que un simple objeto? ¿Que me estoy perdiendo aqui?
ndtreviv
14
Sí, siempre debe validar sus entradas. Si no es así, ¿por qué no usar una variable pública? Se convierte en lo mismo. El beneficio total de usar setters versus variables es que le permite asegurarse de que su objeto nunca sea inválido, como que la edad sea un número negativo. Si no hace esto en el objeto, lo hará en otro lugar (como un objeto de dios de "servicio" ) y eso no es realmente POO en ese punto.
Trineo
1
Bueno, ese es probablemente el punto culminante de mi semana. Gracias.
Trineo
19

Sí, pero no siempre de forma aislada.

Permítame elaborar:

¿Qué es una prueba unitaria?

De trabajar eficazmente con el código heredado 1 :

El término prueba unitaria tiene una larga historia en el desarrollo de software. Común a la mayoría de las concepciones de las pruebas unitarias es la idea de que son pruebas aisladas de los componentes individuales del software. ¿Qué son los componentes? La definición varía, pero en las pruebas unitarias, generalmente nos interesan las unidades de comportamiento más atómicas de un sistema. En el código de procedimiento, las unidades son a menudo funciones. En el código orientado a objetos, las unidades son clases.

Tenga en cuenta que con OOP, donde encuentra captadores y establecedores, la unidad es la clase , no necesariamente métodos individuales .

¿Qué es una buena prueba?

Todos los requisitos y pruebas siguen la forma de la lógica Hoare :

{P} C {Q}

Dónde:

  • {P}es la precondición ( dada )
  • Ces la condición desencadenante ( cuando )
  • {Q}es la condición posterior ( entonces )

Luego viene la máxima:

Probar el comportamiento, no la implementación

Esto significa que no debe probar cómo se Clogra la condición posterior, debe verificar que {Q}es el resultado de C.

Cuando se trata de OOP, Ces una clase. Por lo tanto, no debe probar los efectos internos, solo los externos.

¿Por qué no probar getters y setters de bean de forma aislada?

Los captadores y establecedores pueden involucrar algo de lógica, pero mientras esta lógica no tenga un efecto externo, lo que los convierte en accesores de frijoles 2 ) una prueba tendrá que mirar dentro del objeto y no solo violará la encapsulación sino también la prueba de implementación.

Por lo tanto, no debe probar los captadores y establecedores de beans de forma aislada. Esto es malo:

Describe 'LineItem class'

    Describe 'setVAT()'

        it 'should store the VAT rate'

            lineItem = new LineItem()
            lineItem.setVAT( 0.5 )
            expect( lineItem.vat ).toBe( 0.5 )

Aunque si setVATarrojaría una excepción, una prueba correspondiente sería apropiada ya que ahora hay un efecto externo.

¿Cómo deberías probar getters y setters?

Prácticamente no tiene sentido cambiar el estado interno de un objeto si dicho cambio no tiene efecto en el exterior, incluso si dicho efecto llega más tarde.

Por lo tanto, una prueba para setters y getters debería estar relacionada con el efecto externo de estos métodos, no con los internos.

Por ejemplo:

Describe 'LineItem class'

    Describe 'getGross()'

        it 'should return the net time the VAT'

            lineItem = new LineItem()
            lineItem.setNet( 100 )
            lineItem.setVAT( 0.5 )
            expect( lineItem.getGross() ).toBe( 150 )

Puedes pensar para ti mismo:

Espera un segundo, estamos probando getGross()aquí no setVAT() .

Pero si el setVAT()mal funcionamiento de esa prueba fallara de todos modos

1 Feathers, M., 2004. Trabajando efectivamente con código heredado. Prentice Hall Profesional.

2 Martin, RC, 2009. Código limpio: un manual de artesanía ágil de software. Educación Pearson.

Izhaki
fuente
13

Si bien existen razones justificadas para las Propiedades, existe una creencia común de Diseño Orientado a Objetos de que exponer el estado miembro a través de Propiedades es un mal diseño. El artículo de Robert Martin sobre el Principio Abierto Cerrado amplía esto al afirmar que las Propiedades fomentan el acoplamiento y, por lo tanto, limitan la capacidad de cerrar una clase de la modificación; si modifica la propiedad, todos los consumidores de la clase también deberán cambiar. Él califica que exponer las variables miembro no es necesariamente un mal diseño, sino que podría ser un mal estilo. Sin embargo, si las propiedades son de solo lectura, hay menos posibilidades de abuso y efectos secundarios.

El mejor enfoque que puedo proporcionar para las pruebas unitarias (y esto puede parecer extraño) es hacer que todas las propiedades sean protegidas o internas. Esto evitará el acoplamiento y desalienta la escritura de pruebas tontas para captadores y establecedores.

Existen razones obvias en las que se deben usar las propiedades de lectura / escritura, como las propiedades de ViewModel que están vinculadas a campos de entrada, etc.

Más prácticamente, las pruebas unitarias deberían conducir la funcionalidad a través de métodos públicos. Si el código que está probando utiliza esas propiedades, obtendrá cobertura de código de forma gratuita. Si resulta que estas propiedades nunca se destacan por la cobertura de código, existe una gran posibilidad de que:

  1. Le faltan pruebas que usan indirectamente las propiedades
  2. Las propiedades no se usan

Si escribe pruebas para captadores y establecedores, obtiene una falsa sensación de cobertura y no podrá determinar si las propiedades son realmente utilizadas por el comportamiento funcional.

bryanbcook
fuente
12

Si la complejidad ciclomática del captador y / o definidor es 1 (que generalmente son), entonces la respuesta es no, no debería.

Por lo tanto, a menos que tenga un SLA que requiera una cobertura de código del 100%, no se moleste y concéntrese en probar el aspecto importante de su software.

PD Recuerde diferenciar getters y setters, incluso en lenguajes como C # donde las propiedades pueden parecer lo mismo. La complejidad del setter puede ser mayor que la del getter y, por lo tanto, validar una prueba unitaria.

Claus Jørgensen
fuente
44
Aunque uno debe probar la copia defensiva en getters
Trineo
8

Una toma humorística pero sabia: El camino de Testivus

"Escribe el examen que puedas hoy"

Las pruebas de getters / setters pueden ser excesivas si eres un probador experimentado y este es un proyecto pequeño. Sin embargo, si recién está comenzando a aprender a hacer pruebas unitarias o estos captadores / establecedores pueden contener lógica (como el setMom()ejemplo de @ ArtB ), sería una buena idea escribir pruebas.

muymoo
fuente
1
su enlace debería apuntar a: The Way of Testivus
JJS
2

Este ha sido un tema reciente entre mi equipo y yo. Disparamos por una cobertura de código del 80%. Mi equipo argumenta que los getters y setters se implementan automáticamente, y el compilador está generando un código básico detrás de escena. En este caso, dado que el código generado no es intrusivo, realmente no tiene sentido probar el código que el compilador crea para usted. También tuvimos esta discusión sobre los métodos asíncronos y en ese caso el compilador genera un montón de código detrás de escena. Este es un caso diferente y algo que SI probamos. Respuesta larga corta, tráigala con su equipo y decida por sí mismo si vale la pena probarlo.

Además, si está utilizando el informe de cobertura de código como nosotros, una cosa que puede hacer es agregar el atributo [ExcludeFromCodeCoverage]. Nuestra solución ha sido usar esto para modelos que solo tienen propiedades que usan getters y setters, o en la propiedad misma. De esa manera, no afectará el% de cobertura total del código cuando se ejecute el informe de cobertura del código, suponiendo que eso es lo que está utilizando para calcular los porcentajes de cobertura del código. ¡Feliz prueba!

Devin C
fuente
2

En mi opinión, la cobertura del código es una buena manera de ver si perdió alguna funcionalidad que debería cubrir.

Cuando inspecciona la cobertura manualmente por su colorido, entonces se puede argumentar que los captadores y establecedores simples no necesitan ser probados (aunque siempre lo hago).

Cuando solo verifica el porcentaje de cobertura del código en su proyecto, entonces un porcentaje de cobertura de prueba como 80% no tiene sentido. Puede probar todas las partes no lógicas y olvidar algunas partes cruciales. En este caso, solo el 100% significa que ha probado todo su código vital (y también todo el código no lógico). Tan pronto como sea del 99,9%, sabes que has olvidado algo.

Por cierto: la cobertura del código es la verificación final para ver si ha probado completamente (unidad) una clase. Pero la cobertura del 100% del código no significa necesariamente que haya probado toda la funcionalidad de la clase. Por lo tanto, las pruebas unitarias siempre deben implementarse siguiendo la lógica de la clase. Al final, ejecutas la cobertura para ver si olvidaste algo. Cuando lo hiciste bien, llegaste al 100% la primera vez.

Una cosa más: mientras trabajaba recientemente en un gran banco en los Países Bajos, noté que Sonar indicaba una cobertura de código del 100%. Sin embargo, sabía que faltaba algo. Al inspeccionar los porcentajes de cobertura de código por archivo, indicó un archivo con un porcentaje más bajo. El porcentaje base del código completo era tan grande que el único archivo no hizo que el porcentaje se mostrara como 99.9%. Por lo tanto, es posible que desee vigilar esto ...

Radboud
fuente
1

Hice un pequeño análisis de la cobertura lograda en el propio código JUnit .

Una categoría de código descubierto es "demasiado simple para probar" . Esto incluye getters y setters simples, que los desarrolladores de JUnit no prueban.

Por otro lado, JUnit no tiene ningún método (no obsoleto) de más de 3 líneas que no esté cubierto por ninguna prueba.

avandeursen
fuente
1

Yo diría: SÍ Los errores en los métodos getter / setter pueden colarse silenciosamente y causar algunos errores feos.

He escrito una lib para facilitar esta y algunas otras pruebas. Lo único que tienes que escribir en tus pruebas JUnit es esto:

        assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
            new EmptyCollectionCheck(), new GetterIsSetterCheck(),
            new HashcodeAndEqualsCheck(), new PublicVariableCheck())));

-> https://github.com/Mixermachine/base-test

Aaron Dietz
fuente
0

Sí, especialmente si el elemento a obtener es un objeto de una clase subclase de una clase abstracta. Su IDE puede o no alertarlo de que cierta propiedad no se ha inicializado.

Y luego, una prueba aparentemente no relacionada se bloquea con a NullPointerExceptiony le lleva un tiempo darse cuenta de que una propiedad obtenible no está allí para obtenerla en primer lugar.

Aunque eso no sería tan malo como descubrir el problema en la producción.

Puede ser una buena idea asegurarse de que todas sus clases abstractas tengan constructores. Si no, la prueba de un captador podría alertarlo sobre un problema allí.

En cuanto a los captadores y establecedores de primitivas, la pregunta podría ser: ¿Estoy probando mi programa o estoy probando JVM o CLR? En términos generales, la JVM no necesita ser probada.

Alonso del Arte
fuente