Cualquier herramienta / sugerencia sobre cómo refutar el argumento de calidad de cobertura de código


11

Ahora sé que la gente podría considerar esta pregunta duplicada o formulada muchas veces, en cuyo caso agradecería un enlace a preguntas relevantes con respuesta a mi pregunta.

Recientemente he estado en desacuerdo con algunas personas sobre la cobertura del código. Tengo un grupo de personas que quieren que nuestro equipo deje de mirar la cobertura del código por completo basándose en el argumento de que una cobertura del 100% no significa pruebas de buena calidad y, por lo tanto, un código de buena calidad.

He podido retroceder al vender el argumento de que Code Coverage me dice lo que no se ha probado con seguridad y nos ayuda a centrarnos en esas áreas.

(Lo anterior se ha discutido de manera similar en otras preguntas SO como esta: /programming/695811/pitfalls-of-code-coverage )

El argumento de estas personas es: entonces el equipo reaccionaría creando rápidamente pruebas de baja calidad y, por lo tanto, perdería tiempo sin agregar una calidad significativa.

Si bien entiendo su punto de vista, estoy buscando una manera de hacer un caso más sólido para la cobertura de código mediante la introducción de herramientas / marcos más robustos que cuiden más criterios de cobertura (Functional, Statement,Decision, Branch, Condition, State, LCSAJ, path, jump path, entry/exit, Loop, Parameter Value etc) .

Lo que estoy buscando es una sugerencia para una combinación de tales herramientas de cobertura de código y prácticas / procesos para acompañarlos, lo que puede ayudarme a contrarrestar tales argumentos mientras me siento cómodo con mi recomendación.

También agradecería cualquier comentario / sugerencia que lo acompañe basado en su experiencia / conocimiento sobre cómo contrarrestar tal argumento, porque si bien es subjetivo, la cobertura del código ha ayudado a mi equipo a ser más consciente de la calidad del código y el valor de las pruebas.


Editar: para reducir cualquier confusión sobre mi comprensión de la debilidad de la cobertura de código típica, quiero señalar que no me estoy refiriendo a Statement Coverage (o líneas de código ejecutadas) herramientas (hay muchas). De hecho, aquí hay un buen artículo sobre todo lo que tiene de malo: http://www.bullseye.com/statementCoverage.html

Estaba buscando algo más que una declaración o cobertura de línea, yendo más a múltiples criterios y niveles de cobertura.

Ver: http://en.wikipedia.org/wiki/Code_coverage#Coverage_criteria

La idea es que si una herramienta nos puede decir nuestra cobertura basada en múltiples criterios, entonces eso se convierte en una evaluación automatizada razonable de la calidad de la prueba. De ninguna manera estoy tratando de decir que la cobertura de línea es una buena evaluación. De hecho, esa es la premisa de mi pregunta.


Editar:
Ok, tal vez lo proyecté demasiado dramáticamente, pero entiendes el punto. El problema es establecer procesos / políticas en general en todos los equipos de manera homogénea / consistente. Y el temor es general de que cómo se garantiza la calidad de las pruebas, cómo se asigna el tiempo garantizado sin tener ninguna medida. Por lo tanto, me gusta tener una característica medible que, cuando se respalda con los procesos adecuados y las herramientas adecuadas, nos permitiría mejorar la calidad del código al tiempo que sabemos que el tiempo no se gasta en procesos innecesarios.


EDITAR: hasta ahora lo que tengo de las respuestas:

  • Las revisiones del código deben cubrir las pruebas para garantizar la calidad de las pruebas.
  • La estrategia Test First ayuda a evitar las pruebas escritas después del hecho para simplemente aumentar el porcentaje de cobertura
  • Explorar herramientas alternativas que cubren criterios de prueba que no sean simplemente Declaración / Línea
  • El análisis del código cubierto / número de errores encontrados ayudaría a apreciar la importancia de la cobertura y a hacer un mejor caso
  • Lo más importante es confiar en la aportación del equipo para hacer lo correcto y luchar por sus creencias.
  • Bloques cubiertos / # de pruebas: debatible pero tiene cierto valor

Gracias por las increíbles respuestas hasta ahora. Realmente los aprecio. Este hilo es mejor que horas de lluvia de ideas con los poderes fácticos.


44
Nadie en ningún lugar sugiere cumplir con el 100% de la cobertura del código, eso es realmente una tarea tonta.
Jimmy Hoffa

1
Gracias. Y ya lo sé y lo reconozco. Y también las personas tienden a asociar la cobertura del 100% del código con la cobertura del 100% del estado de cuenta (o línea) típicamente. Tampoco podía resistirse: Jimmy, te estaban buscando por todas partes.
MickJ

3
"entonces el equipo reaccionaría rápidamente creando pruebas de baja calidad y, por lo tanto, perdería tiempo sin agregar una calidad significativa" - ¡gran equipo!
Piotr Perak

Ok, tal vez lo proyecté demasiado dramáticamente, pero entiendes el punto. El problema es establecer procesos / políticas en general en todos los equipos de manera homogénea / consistente. Y el temor es general de que cómo se garantiza la calidad de las pruebas, cómo se asigna el tiempo garantizado sin tener ninguna medida. Por lo tanto, me gusta tener una característica medible que, cuando se respalda con los procesos adecuados y las herramientas adecuadas, nos permitiría mejorar la calidad del código al tiempo que sabemos que el tiempo no se gasta en procesos innecesarios.
MickJ

1
@JimmyHoffa: el software de espacio crítico generalmente requiere una cobertura de código del 100%.
mouviciel

Respuestas:


9

En mi experiencia, la cobertura del código es tan útil como la crea . Si escribe buenas pruebas que cubren todos sus casos, entonces pasar esas pruebas significa que ha cumplido con sus requisitos. De hecho, esa es la idea exacta que utiliza Test Driven Development . Escribe las pruebas antes del código sin saber nada sobre la implementación (a veces esto significa que otro equipo escribe las pruebas por completo). Estas pruebas se configuran para verificar que el producto final hace todo lo que sus especificaciones dicen que hizo, y LUEGO escribe el código mínimo para pasar esas pruebas.

El problema aquí, obviamente, es que si sus pruebas no son lo suficientemente fuertes, perderá casos extremos o problemas imprevistos y escribirá código que realmente no cumple con sus especificaciones. Si realmente está decidido a usar pruebas para verificar su código, escribir buenas pruebas es una necesidad absoluta, o realmente está perdiendo el tiempo.

Quería editar la respuesta aquí porque me di cuenta de que realmente no respondía a su pregunta. Vería ese artículo wiki para ver algunos beneficios declarados de TDD. Realmente se trata de cómo funciona mejor su organización, pero TDD es definitivamente algo en uso en la industria.


+1 para la sugerencia de que escribir pruebas primero mejora la calidad de las pruebas, por lo que una combinación de Prueba primero con Cobertura de código es definitivamente útil.
MickJ

Sí, siempre he descubierto que si escribe pruebas después de haber desarrollado el código, solo probará las cosas que sabe que su código pasará, o escribirá las pruebas de una manera que complemente la implementación, que Realmente no ayuda a nadie. Si escribe sus pruebas independientemente del código, se concentra en lo que debe hacer el código en lugar de lo que hace .
Ampt

Gracias @Ampt. Además de reforzar el proceso con TDD, ¿hay alguna herramienta de cobertura de código que recomiende, que cuide más criterios de cobertura de una manera más exhaustiva, ayudando así a validar la calidad de las pruebas escritas, al menos en cierta medida?
MickJ

Puede que te esté entendiendo incorrectamente, pero ¿estás sugiriendo que diferentes herramientas te indicarán una cobertura diferente para tus pruebas? Siempre ha sido mi experiencia que las herramientas de cobertura solo observan cómo se ejecutan las pruebas y registran qué líneas de código se ejecutan. Las herramientas de cambio no deberían tener ningún impacto en la cobertura, ya que el número de líneas ejecutadas sigue siendo el mismo. Sería cauteloso con una herramienta que brinde más cobertura para la misma prueba. Dicho esto, no creo que golpear cada línea de código sea una buena evaluación de la calidad de la prueba , ya que es exhaustiva . Las buenas pruebas provienen de buenos requisitos.
Ampt

Gracias. A lo que se refiere es Statement Coverage(o líneas de código ejecutadas). Estaba buscando algo más que una declaración o cobertura de línea, profundizando multiple coverage criteriay nivelando. Ver: en.wikipedia.org/wiki/Code_coverage#Coverage_criteria y en.wikipedia.org/wiki/Linear_Code_Sequence_and_Jump . La idea es que si una herramienta nos puede decir nuestra cobertura basada en múltiples criterios, entonces eso se convierte en una evaluación automatizada razonable de la calidad de la prueba. De ninguna manera estoy tratando de decir que la cobertura de línea es una buena evaluación. De hecho, esa es la premisa de mi pregunta.
MickJ

6

En primer lugar, las personas hacen defensor de cobertura del 100%:

La mayoría de los desarrolladores ven ... "100% de cobertura de estado de cuenta" como adecuada. Este es un buen comienzo, pero apenas suficiente. Una mejor identificación estándar de cobertura para cumplir con lo que se llama "cobertura de sucursal al 100%" ...

Steve McConnell, Code Complete , Capítulo 22: Pruebas de desarrollador.

Como usted y otros han mencionado, es poco probable que la cobertura de código solo por el bien de la cobertura logre mucho. Pero si no puede ejecutar una línea de código, ¿por qué está escrita?

Sugeriría resolver el argumento reuniendo y analizando datos en sus propios proyectos.

Para recopilar los datos, personalmente uso las siguientes herramientas:

  • JaCoCo y el complemento Eclipse asociado EclEmma para medir la cobertura del código.
  • Scripts de hormigas para creación, prueba e informes automatizados.
  • Jenkins para compilaciones continuas: cualquier cambio en el control de origen desencadena una compilación automática
  • JaCoCo Plugin para Jenkins: captura métricas de cobertura para cada compilación y grafica las tendencias. También permite definiciones de umbrales de cobertura por proyecto que afectan el estado de la compilación.
  • Bugzilla para rastrear errores.

Una vez que tenga eso (o algo similar) en su lugar, puede comenzar a mirar sus propios datos más de cerca:

  • ¿Se encuentran más errores en proyectos mal cubiertos?
  • ¿se encuentran más errores en clases / métodos mal cubiertos?
  • etc.

Yo esperaría que sus datos van a apoyar su posición sobre la cobertura de código; esa ciertamente ha sido mi experiencia. Sin embargo, si no es así, tal vez su organización pueda tener éxito con estándares de cobertura de código más bajos de lo que quisiera. O tal vez tus pruebas no son muy buenas. Con suerte, la tarea centrará el esfuerzo en producir software con menos defectos, independientemente de la resolución del desacuerdo de cobertura del código.


Realmente me gusta esta respuesta. Gracias por las sugerencias de herramientas y, lo que es más importante, me gusta la idea del enfoque respaldado por datos para justificar la cobertura del código. Aunque tiendo a tomar la palabra de los equipos sobre el valor y no cuestionaría eso de todos modos. Posiblemente me pueda ayudar a construir un caso más sólido para nuestra experiencia hasta ahora. ¡Gracias!
MickJ

4

El argumento de estas personas es que el equipo reaccionaría creando rápidamente pruebas de baja calidad y, por lo tanto, perdería tiempo sin agregar una calidad significativa.

Este es un problema de confianza , no de herramientas .

Pregúnteles por qué, si realmente creen esa afirmación, ¿confiarían en que el equipo escriba algún código?


Debido a que saben que la funcionalidad del código es el resultado final, entonces: las pruebas, la documentación, los comentarios, las revisiones, etc. se pueden sacrificar sin consecuencias inmediatas; Aunque estoy de acuerdo, es una mala señal.
JeffO

Ok, tal vez lo proyecté demasiado dramáticamente, pero entiendes el punto. El problema es establecer procesos / políticas en general en todos los equipos de manera homogénea / consistente. Y el temor es general de que cómo se garantiza la calidad de las pruebas, cómo se asigna el tiempo garantizado sin tener ninguna medida. Por lo tanto, me gusta tener una característica medible que, cuando se respalda con los procesos adecuados y las herramientas adecuadas, nos permitiría mejorar la calidad del código al tiempo que sabemos que el tiempo no se gasta en procesos innecesarios.
MickJ

3

Ok, tal vez lo proyecté demasiado dramáticamente, pero entiendes el punto. El problema es establecer procesos / políticas en general en todos los equipos de manera homogénea / consistente.

Creo que ese es el problema. Los desarrolladores no se preocupan (y a menudo por excelentes razones) sobre políticas consistentes o globales, y desean la libertad de hacer lo que piensan que es correcto en lugar de cumplir con las políticas corporativas.

Lo cual es razonable a menos que demuestre que los procesos y medidas globales tienen valor y un efecto positivo en la calidad y la velocidad de desarrollo.

Cronología habitual:

  1. dev: hey, mira: agregué métricas de cobertura de código a nuestro tablero, ¿no es genial?
  2. gerente: claro, agreguemos objetivos obligatorios y cumplimiento en esos
  3. dev: no importa, la cobertura del código es estúpida e inútil, vamos a dejarlo

1
+1 Lo he visto muchas veces para estar en desacuerdo. Soy mi caso, aunque la conversación va un poco de esta manera. dev: hey, mira: agregué métricas de cobertura de código a nuestro tablero, ¿no es genial? gerente: claro, cualquier cosa que ustedes piensen que mejora la calidad es excelente. Jefe del jefe de gerentes: creo que necesitamos tener un proceso en todos los equipos. Creo que la cobertura del código no tiene sentido a menos que podamos garantizar el valor del costo gastado.
MickJ

2

En mi experiencia, hay algunas cosas para combinar con la cobertura de código para que la métrica valga la pena:

Revisiones de código

Si puede devolver las pruebas negativas al desarrollador, puede ayudar a limitar la cantidad de pruebas incorrectas que proporcionan esta cobertura sin sentido.

Seguimiento de errores

Si tiene un montón de cobertura de código en un módulo, pero aún tiene muchos errores graves en esa área, podría indicar un problema en el que ese desarrollador necesita mejorar con sus pruebas.

Pragmatismo

Nadie va a llegar al 100% con buenas pruebas en código no trivial. Si usted como líder del equipo, observe la cobertura del código, pero en lugar de decir "¡necesitamos llegar al N%!" identifica las brechas y pide a las personas que "mejoren la cobertura en el módulo X" que logra su objetivo sin brindarles a las personas la oportunidad de jugar con el sistema.

Bloques cubiertos / # de pruebas

La mayoría de las herramientas de cobertura de código enumeran los bloques cubiertos frente a los bloques no cubiertos. La combinación de esto con el número de pruebas reales le permite obtener una métrica que indica qué tan "amplias" son las pruebas, ya sea indicando malas pruebas o diseño acoplado. Esto es más útil como un delta de un sprint a otro, pero la idea es la misma: combinar la cobertura del código con otras métricas para obtener más información.


+1 Buenas sugerencias, me gustan especialmente los Bloques cubiertos / # de pruebas y revisiones de código. Aunque ya hacemos revisiones de código, sería útil enfatizar la importancia de revisar las pruebas más de cerca.
MickJ

2

Aquí están mis 2 centavos.

Hay muchas prácticas que han recibido mucha atención recientemente porque pueden aportar beneficios al desarrollo de software. Sin embargo, algunos desarrolladores aplican esas prácticas a ciegas: están convencidos de que aplicar una metodología es como ejecutar un algoritmo y que después de realizar los pasos correctos, se debe obtener el resultado deseado.

Algunos ejemplos:

  • Escriba pruebas unitarias con una cobertura de código del 100% y obtendrá una mejor calidad de código.
  • Aplique TDD sistemáticamente y obtendrá un mejor diseño.
  • Empareje la programación y mejorará la calidad del código y reducirá el tiempo de desarrollo.

Creo que el problema básico con las declaraciones anteriores es que los humanos no son computadoras y escribir software no es como ejecutar un algoritmo.

Entonces, las declaraciones anteriores contienen algo de verdad pero simplifican demasiado las cosas, por ejemplo:

  • Las pruebas unitarias detectan muchos errores y la cobertura del código indica qué partes del código se prueban, pero probar cosas triviales es inútil. Por ejemplo, si al hacer clic en un botón se abre el cuadro de diálogo correspondiente, toda la lógica que envía el evento del botón al componente que abre el cuadro de diálogo puede probarse mediante una simple prueba manual (haga clic en el botón): ¿vale la pena para la unidad? prueba esta lógica?
  • Si bien TDD es una buena herramienta de diseño, no funciona bien si el desarrollador no comprende bien el dominio del problema (ver, por ejemplo, esta famosa publicación ).
  • La programación de pares es efectiva si dos desarrolladores pueden trabajar juntos, de lo contrario es un desastre. Además, los desarrolladores experimentados pueden preferir discutir brevemente los problemas más importantes y luego codificar por separado: pasar muchas horas discutiendo muchos detalles que ambos ya saben pueden ser aburridos y una gran pérdida de tiempo.

Volviendo a la cobertura del código.

He podido retroceder al vender el argumento de que Code Coverage me dice lo que no se ha probado con seguridad y nos ayuda a centrarnos en esas áreas.

Creo que debe juzgar caso por caso si vale la pena tener una cobertura del 100% para un determinado módulo.

¿El módulo realiza algunos cálculos muy importantes y complicados? Luego me gustaría probar cada línea de código, pero también escribir pruebas unitarias significativas (pruebas unitarias que tengan sentido en ese dominio).

¿El módulo realiza alguna tarea importante pero simple como abrir una ventana de ayuda al hacer clic en un botón? Una prueba manual probablemente será más efectiva.

El argumento de estas personas es: entonces el equipo reaccionaría creando rápidamente pruebas de baja calidad y, por lo tanto, perdería tiempo sin agregar una calidad significativa.

En mi opinión, tienen razón: no puede exigir la calidad del código al solo requerir una cobertura del 100% del código. Agregar más herramientas para calcular la cobertura y hacer estadísticas tampoco ayudará. Por el contrario, debe analizar qué partes del código son más sensibles y deben probarse exhaustivamente y cuáles son menos propensas a errores (en el sentido de que un error puede descubrirse y repararse mucho más fácilmente sin usar pruebas unitarias).

Si aplica una cobertura de código del 100% a los desarrolladores, algunos comenzarán a escribir pruebas unitarias tontas para cumplir con sus obligaciones en lugar de intentar escribir pruebas sensatas.

¿Cómo se asigna el tiempo garantizado sin tener ninguna medida?

Tal vez sea una ilusión que puedas medir la inteligencia y el juicio humanos. Si tiene colegas competentes y confía en su criterio, puede aceptar cuando le digan "para este módulo, aumentar la cobertura del código traerá muy pocos beneficios. Por lo tanto, no pasemos tiempo en él" o "para este módulo necesitamos tanta cobertura como podamos obtener, necesitamos una semana extra para implementar pruebas unitarias sensatas ".

Entonces (nuevamente, estos son mis 2 centavos): no intente encontrar un proceso y establezca parámetros como la cobertura de código que debe ajustarse a todos los equipos, para todos los proyectos y para todos los módulos. Encontrar un proceso tan general es una ilusión y creo que cuando haya encontrado uno, será subóptimo.


Todo es verdad y ya estoy de acuerdo. Si observa que no soporto el 100% de la cobertura del código. Se trata de mejorar su valor mediante el uso de técnicas, herramientas y procesos bettet. También ayuda a comprender completamente los criterios de cobertura del código (la mayoría supone que se trata de una línea / declaración). +1 por tu excelente publicación.
MickJ

2

"el equipo reaccionaría creando rápidamente pruebas de baja calidad y, por lo tanto, perdería tiempo sin agregar una calidad significativa"

Este es un riesgo real, no solo teórico.

El exceso de código solo es una métrica disfuncional. Aprendí esa lección de la manera difícil. Una vez, lo enfaticé sin la disponibilidad de balancear métricas o prácticas. Cientos de pruebas que capturan y enmascaran excepciones, y sin afirmaciones es algo feo.

"sugerencia para una combinación de tales herramientas de cobertura de código y prácticas / procesos para acompañarlos"

Además de todas las otras sugerencias, existe una técnica de automatización que puede evaluar la calidad de las pruebas: pruebas de mutación ( http://en.wikipedia.org/wiki/Mutation_testing ). Para el código Java, PIT ( http://pitest.org/ ) funciona, y es la primera herramienta de prueba de mutaciones que encuentro.

Como observa, la falta de cobertura de código es fácilmente identificable como un riesgo de calidad de software. Enseño que la cobertura del código es una condición necesaria, pero insuficiente, para la calidad del software. Tenemos que adoptar un enfoque de cuadro de mando integral para gestionar la calidad del software.


1

La cobertura del código ciertamente no es prueba de buenas pruebas unitarias, ya que son correctas.

Pero a menos que puedan proporcionar una forma de demostrar que todas las pruebas unitarias son buenas (para cualquier definición de bien que puedan encontrar), este es realmente un punto mudo.


2
Los puntos que no hablan tienden a ser discutibles . (Simplemente me gusta el juego de palabras allí, mi ortografía a menudo se corrige).

1

Siempre he encontrado que la cobertura del código es fácilmente susceptible al efecto Hawthorne . Esto me hizo preguntar "¿por qué tenemos alguna métrica de software?" y la respuesta generalmente es proporcionar una comprensión de alto nivel del estado actual del proyecto, cosas como:

"¿Qué tan cerca estamos de hacer?"

"¿Cómo es la calidad de este sistema?"

"¿Qué tan complicados son estos módulos?"

Por desgracia, nunca habrá una sola métrica que pueda decirle qué tan bueno o malo es el proyecto, y cualquier intento de derivar ese significado de un solo número necesariamente se simplificará demasiado. Si bien las métricas tienen que ver con los datos, interpretar lo que significan es una tarea mucho más emocional / psicológica y, como tal, probablemente no se pueda aplicar genéricamente a equipos de diferentes composiciones o problemas de diferentes dominios.

En el caso de la cobertura, creo que a menudo se usa como un proxy para la calidad del código, aunque sea burdo. Y el verdadero problema es que reduce un tema terriblemente complicado a un solo número entero entre 0 y 100 que, por supuesto, se utilizará para impulsar un trabajo potencialmente inútil en una búsqueda interminable para lograr una cobertura del 100%. Gente como Bob Martin dirá que el 100% de cobertura es el único objetivo serio, y puedo entender por qué es así, porque cualquier otra cosa parece arbitraria.

Por supuesto, hay muchas maneras de obtener cobertura que en realidad no me ayudan a comprender el código base, por ejemplo, ¿es valioso probar toString ()? ¿Qué pasa con getters y setters para objetos inmutables? Un equipo solo tiene mucho esfuerzo para aplicar en un tiempo fijo y ese tiempo siempre parece ser menor que el tiempo requerido para hacer un trabajo perfecto, por lo que en ausencia de un horario perfecto tenemos que conformarnos con aproximaciones.

Una medida que he encontrado útil para hacer buenas aproximaciones es Crap4J . Ahora está desactivado, pero puede portarlo / implementarlo usted mismo fácilmente. Crap4J trata de relacionar la cobertura del código con la complejidad ciclomática al implicar que el código que es más complicado (ifs, whiles, fors, etc.) debería tener una mayor cobertura de prueba. Para mí, esta simple idea realmente sonaba verdadera. Quiero entender dónde hay riesgo en mi base de código, y un riesgo realmente importante es la complejidad. Entonces, usando esta herramienta, puedo evaluar rápidamente cuán riesgoso es mi código base. Si es complicado, es mejor que la cobertura aumente. Si no es así, no necesito perder el tiempo tratando de cubrir cada línea de código.

Por supuesto, esto es solo una métrica y YMMV. Tiene que pasar tiempo con él para comprender si tendrá sentido para usted y si le dará a su equipo una sensación razonablemente acertada de dónde se encuentra el proyecto.


Gracias por la excelente sugerencia de utilizar la complejidad ciclomática para elegir el código que merece cobertura y el enlace Crap4J. También encontré un gran artículo que habla sobre exprimir la genialidad de crap4j en la cobertura - schneide.wordpress.com/2010/09/27/…
MickJ

0

No diría que regresar y cubrir el código existente es la mejor ruta hacia adelante. Yo diría que tiene sentido escribir pruebas de cobertura para cualquier código nuevo que escriba o cualquier código que cambie.

Cuando se encuentran errores, escriba una prueba que falle debido a ese error y corríjala para que la prueba se vuelva verde. Ponga en los comentarios de la prueba para qué error está escrito.

El objetivo es tener suficiente confianza en sus pruebas para poder hacer cambios sin preocuparse por los efectos secundarios inesperados. Consulte Trabajar eficazmente con código heredado para obtener un buen resumen de los enfoques para domesticar código no probado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.