¿Es la cobertura del 100% del código un sueño imposible?


28

¿Es factible esperar una cobertura de código del 100% en aplicaciones web jquery / backbonejs pesadas? ¿Es razonable fallar un sprint debido a que no se cumple la cobertura del 100% cuando la cobertura del código real oscila alrededor del 92% -95% en javascript / jquery?


77
"Fail a sprint" suena extrañamente siniestro ...
Donal Fellows

55
Es una asíntota.
Robert Harvey

12
incluso si tienes cobertura total, no se encontrarán algunos errores, así que no confíes en ese número para arreglarlo todo
fanático de trinquete

11
Todo es posible. La verdadera pregunta es si el valor del 100% de cobertura del código vale el costo en tiempo y recursos.
JohnFx

55
¿Por qué se preocupa por esto, cuando la suposición subyacente, que el 100% (o cualquier otro número) de cobertura de prueba automatizada mejorará mágicamente su código, es un sueño imposible?
Mason Wheeler

Respuestas:


30

Es igualmente realista ya que no es realista.

Realista
Si tiene pruebas automatizadas que han demostrado cubrir toda la base del código, entonces es razonable insistir en una cobertura del 100%.
También depende de cuán crítico sea el proyecto. Cuanto más crítico, más razonable es esperar / exigir una cobertura completa del código.
Es más fácil hacer esto para proyectos pequeños a medianos.

Poco realista
Estás comenzando con una cobertura del 0% ...
El proyecto es monstruoso con muchas, muchas rutas de error que son difíciles de recrear o activar.
La gerencia no está dispuesta a comprometerse / invertir para asegurarse de que la cobertura esté allí.

He trabajado en toda la gama de proyectos que van desde sin cobertura hasta decentes. Nunca un proyecto con el 100%, pero ciertamente hubo momentos en que deseé tener una cobertura más cercana al 100%.
En última instancia, la pregunta es si la cobertura existente cumple con suficientes casos requeridos para que el equipo se sienta cómodo al enviar el producto.

No sabemos el impacto de una falla en su proyecto, por lo que no podemos decir si 92% o 95% es suficiente, o si ese 100% es realmente necesario. O para el caso, el 100% prueba completamente todo lo que espera.


30
... Y el hecho de que tenga una cobertura de código del 100% no significa que tenga una cobertura de sucursal del 100%, por lo que incluso con una cobertura de código del 100% podría perderse mucho.
Bryan Oakley

3
+1 para el tamaño del proyecto. Desglosar en componentes más pequeños, reutilizables y comprobables nos ha permitido obtener ~ 95% de cobertura nosotros mismos. 100% de cobertura no es necesaria. Las pruebas de integración deben cubrir los vacíos en las pruebas unitarias
Apoorv Khurasia

55
@BryanOakley ... y también tus pruebas podrían no tener sentido, o incluso no probar nada
David_001

55
@BryanOakley E incluso con una cobertura de sucursales del 100%, es posible que cierta combinación de sucursales pueda causar un problema. (dos sentencias IF secuenciales, por ejemplo, pueden ramificarse en y alrededor de pruebas separadas, pero faltan una prueba que ingrese a ambas . Cobertura completa de la rama, pero se pierde una ruta de ejecución)
Izkata

44
Incluso el 100% de cobertura de sucursal, incluidas todas las rutas de ejecución, no es suficiente. Tal vez algún error solo ocurre cuando toma alguna combinación de ramas y tiene alguna entrada externa, por ejemplo, una fecha con formato incorrecto. No hay posibilidad de que todos los casos estén cubiertos. Al mismo tiempo, se puede tener una buena confianza con menos del 100% de cobertura, pero los casos límite elegidos adecuadamente como entrada.
Andrea

32

¿Quién prueba las pruebas?

Es muy ingenuo en el mejor de los casos y poco realista, incluso en el sentido teórico, y poco práctico en el sentido comercial.

  • No es realista con el código que tiene una alta complejidad ciclomática. Hay demasiadas variables para cubrir cada combinación.
  • No es realista con el código que es muy concurrente. El código no es determinista, por lo que no puede cubrir todas las condiciones que puedan ocurrir porque el comportamiento cambiará en cada ejecución de prueba.
  • No es realista en un sentido comercial, solo paga dividendos para escribir pruebas para el código que es un código de ruta crítico, es decir, un código que es importante y un código que puede cambiar con frecuencia.

Probar cada línea de código no es un buen objetivo

Es muy costoso escribir pruebas, es un código que debe escribirse y probarse por sí mismo, es un código que debe documentarse en lo que realmente intenta probar, es un código que debe mantenerse con cambios en la lógica de negocios y las pruebas fallan porque están desactualizadas. Mantener pruebas automatizadas y la documentación sobre ellas puede ser más costoso que mantener el código a veces.

Esto no quiere decir que las pruebas unitarias y las pruebas de integración no sean útiles, sino solo donde tienen sentido, y fuera de las industrias que pueden matar a las personas, no tiene sentido intentar probar cada línea de código en una base de código. Fuera de estos asesinatos críticos, muchas personas codifican rápidamente bases, es imposible calcular un retorno positivo de la inversión que implicaría una cobertura de código del 100%.

Problema de detención:

En la teoría de la computabilidad, el problema de detención es el problema de determinar, a partir de una descripción de un programa de computadora arbitrario y una entrada, si el programa terminará de ejecutarse o continuará ejecutándose para siempre.

Alan Turing demostró en 1936 que no puede existir un algoritmo general para resolver el problema de detención para todos los pares posibles de entrada de programa. Una parte clave de la prueba fue una definición matemática de una computadora y un programa, que se conoció como una máquina de Turing; El problema de detención es indecidible sobre las máquinas de Turing. Es uno de los primeros ejemplos de un problema de decisión.

Ya que ni siquiera puedes probar que algo funciona al 100%, ¿por qué hacer de eso tu objetivo?

Claro y simple, en la mayoría de los casos no tiene ningún sentido comercial.


77
Esto realmente necesita ser la respuesta aceptada. La cobertura del 100% del código es casi tan mala como el 0%.
Ryathal

1
"Hay demasiadas variables para cubrir cada combinación". Esto no tiene nada que ver con obtener una cobertura del 100% del código. Si una línea de código era lo suficientemente importante como para ser escrita, y es lo suficientemente importante como para mantenerse, entonces es lo suficientemente importante como para ser cubierta por una prueba. Si no está cubierto por una prueba, la única suposición segura es que no funciona. Es cierto que para algunos códigos no tiene sentido desde una perspectiva comercial probarlo. Ese es el mismo código que no tenía sentido desde una perspectiva comercial para escribir.
still_dreaming_1

2
por lo que cree que escribir casos de prueba para cubrir simples o getXXX()/setXXX()simples constructores de asignaciones para objetos de valor es un buen uso de tiempo y recursos, lo siento, pero ese no es el caso en realidad y una opinión extremadamente ingenua que carece de experiencia en el mundo real para respaldarlo. Recuerde que el código de prueba sigue siendo un código que debe mantenerse. Cuanto menos código escriba para resolver un problema, mejor en todos los casos .

Uhm "No es realista con código que tiene una alta complejidad ciclomática. Hay demasiadas variables para cubrir cada combinación". - por supuesto, es por eso que debe dividir dicho código en partes más pequeñas que tienen una complejidad ciclomática pequeña y, por lo tanto, son más fáciles de probar. Refactorizar de esa manera es esencial para las pruebas: facilita las pruebas.
Predrag Stojadinović

17

En la mayoría de los casos, una cobertura de código del 100% significa que ha "engañado" un poco:

  • Las partes complejas del sistema que cambian con frecuencia (como la interfaz gráfica de usuario) se han movido a plantillas declarativas u otras DSL.
  • Todo el código que toca los sistemas externos ha sido aislado o manejado por bibliotecas.
  • Lo mismo ocurre con cualquier otra dependencia, particularmente las que requieren efectos secundarios.

Básicamente, las partes difíciles de probar se han desviado a áreas donde no necesariamente cuentan como "código". No siempre es realista, pero tenga en cuenta que, independientemente de ayudarlo a probar, todas estas prácticas hacen que su base de código sea más fácil de trabajar.


¿Cómo está moviendo las cosas a las trampas DSL?
back2dos

2
@ back2dos: si bien es posible que realice pruebas unitarias, por ejemplo, sus scripts de Python incrustados, es probable que no esté probando sus plantillas html o CSS, ni contando las líneas en ellas hacia las estimaciones de cobertura.
Dan Monego

12

Para ver un ejemplo impresionante en el mundo real de una cobertura de sucursal del 100% , vea Cómo se prueba SQLite .

Me doy cuenta de que su pregunta se refiere específicamente a JavaScript, que es un tipo de producto de software completamente diferente, pero quiero dar a conocer lo que se puede hacer con suficiente motivación.


9

El 100% de cobertura de código para pruebas unitarias para todas las piezas de una aplicación en particular es un sueño imposible, incluso con nuevos proyectos. Desearía que fuera así, pero a veces simplemente no puedes cubrir un fragmento de código, no importa cuánto intentes abstraer las dependencias externas. Por ejemplo, supongamos que su código tiene que invocar un servicio web. Puede ocultar las llamadas al servicio web detrás de una interfaz para poder burlarse de esa pieza y probar la lógica empresarial antes y después del servicio web. Pero la pieza real que necesita invocar el servicio web no puede ser probada por unidad (muy bien de todos modos). Otro ejemplo es si necesita conectarse a un servidor TCP. Puede ocultar el código que se conecta a un servidor TCP detrás de una interfaz. Pero el código que se conecta físicamente a un servidor TCP no se puede probar en la unidad, porque si está fuera de servicio por alguna razón, eso provocaría que la prueba unitaria fallara. Y las pruebas unitarias siempre deben pasar, sin importar cuándo se invoquen.

Una buena regla general es que toda la lógica de su negocio debe tener una cobertura de código del 100%. Pero las piezas que deben invocar componentes externos deben tener una cobertura de código lo más cercana posible al 100%. Si no puedes alcanzarlo, no lo sudaría demasiado.

Mucho más importante, ¿son correctas las pruebas? ¿Reflejan con precisión su negocio y los requisitos? Tener cobertura de código solo para tener cobertura de código no significa nada si todo lo que está haciendo es probar incorrectamente o probar un código incorrecto. Dicho esto, si sus pruebas son buenas, entonces tiene una cobertura del 92-95%.


1
Probar lo que sucede cuando obtienes combinaciones extrañas de casos de error y fallas en la respuesta puede ser excepcionalmente complicado.
Donal Fellows

¿No es lo que hace su sistema cuando se le presentan estos problemas difíciles parte del atractivo de las pruebas unitarias? Además, hay un poco de confusión aquí entre las pruebas unitarias y las pruebas de integración.
Peter Smith

esto se combina unit testingcon integration testing, el código de prueba que no escribió es integrationprueba La pila TCP está en el sistema operativo, no debe probar eso, debe asumir que ya lo ha probado quien lo haya escrito.

4

Diría que a menos que el código esté diseñado con el objetivo específico de permitir una cobertura de prueba del 100%, es posible que no se pueda lograr el 100%. Una de las razones sería que si codifica a la defensiva, lo cual debería, a veces debería tener un código que maneje situaciones que está seguro de que no deberían estar sucediendo o no pueden estar sucediendo dado su conocimiento del sistema. Cubrir dicho código con pruebas sería muy difícil por definición. No tener ese código puede ser peligroso, ¿qué pasa si te equivocas y esta situación ocurre una vez de 256?? ¿Qué pasa si hay un cambio en un lugar no relacionado que hace posible lo imposible? Etc. Entonces, el 100% puede ser bastante difícil de alcanzar por medios "naturales", por ejemplo, si tiene un código que asigna memoria y tiene un código que verifica si ha fallado, a menos que se burle del administrador de memoria (lo que puede no ser fácil) y escriba una prueba que devuelva "sin memoria", cubriendo ese código puede ser difícil Para la aplicación JS, puede ser una codificación defensiva en torno a posibles caprichos DOM en diferentes navegadores, posibles fallas de servicios externos, etc.

Entonces, diría que uno debería esforzarse por estar lo más cerca posible del 100% y tener una buena razón para el delta, pero no vería que no obtener exactamente el 100% como necesariamente un fracaso. El 95% puede estar bien en un gran proyecto, dependiendo del 5%.


El hecho de que se suponga que el código no se debe ejecutar en producción en circunstancias normales no significa que no se pueda escribir de tal manera que las pruebas lo ejecuten. ¿Qué tan crítico es que ese código inusual se ejecute correctamente? ¿Es lo suficientemente importante como para cubrirlo con pruebas? Yo diría que si no es lo suficientemente importante como para cubrirlo mediante pruebas, no es lo suficientemente importante como para manejar ese caso. El código que no necesita pruebas es un código que no necesita existir y debe eliminarse.
still_dreaming_1

2

Si está comenzando con un nuevo proyecto, y está utilizando estrictamente una metodología de prueba primero, entonces es completamente razonable tener una cobertura de código del 100% en el sentido de que todo su código se invocará en algún momento cuando sus pruebas tengan sido ejecutado Sin embargo, es posible que no haya probado explícitamente cada método o algoritmo individual directamente debido a la visibilidad del método, y en algunos casos puede que no haya probado algunos métodos incluso indirectamente.

Obtener el 100% de su código probado es un ejercicio potencialmente costoso, particularmente si no ha diseñado su sistema para permitirle alcanzar este objetivo, y si está enfocando sus esfuerzos de diseño en la capacidad de prueba, probablemente no esté prestando suficiente atención. para diseñar su aplicación para cumplir con sus requisitos específicos, particularmente cuando el proyecto es grande. Lo siento, pero simplemente no puedes tener las dos cosas sin que algo se vea comprometido.

Si está introduciendo pruebas a un proyecto existente donde las pruebas no se han mantenido o incluido antes, entonces es imposible obtener una cobertura de código del 100% sin que los costos del ejercicio superen el esfuerzo. Lo mejor que puede esperar es proporcionar cobertura de prueba para las secciones críticas de código que más se llaman.

¿Es razonable fallar un sprint debido a que no se cumple la cobertura del 100% cuando la cobertura del código real oscila alrededor del 92% -95% en javascript / jquery?

En la mayoría de los casos, diría que solo debe considerar que su sprint ha 'fallado' si no ha cumplido sus objetivos. En realidad, prefiero no pensar en los sprints como fallidos en tales casos porque debes ser capaz de aprender de los sprints que no cumplan con las expectativas para que tu planificación sea correcta la próxima vez que definas un sprint. De todos modos, no creo que sea razonable considerar la cobertura del código como un factor en el relativo éxito de un sprint. Su objetivo debe ser hacer lo suficiente para que todo funcione según lo especificado, y si está codificando la prueba primero, entonces debería poder confiar en que sus pruebas respaldarán este objetivo. Cualquier prueba adicional que sienta que necesita agregar es efectivamente un recubrimiento de azúcar y, por lo tanto, un gasto adicional que puede retrasarlo al completar sus sprints de manera satisfactoria.


"Lo siento, pero simplemente no puedes tener las dos cosas sin que algo se vea comprometido". Eso no es verdad. Siempre puede reducir las funciones o ir más lento. Si algo no vale la pena probar, no vale la pena escribirlo. Si una línea de código es lo suficientemente importante como para mantenerla, es lo suficientemente importante como para probarla. Si no es lo suficientemente importante como para realizar una prueba, no es lo suficientemente importante como para mantenerse cerca. La única suposición segura de una línea de código que no se prueba es que no funciona.
still_dreaming_1

@ still_dreaming_1, parece que apoyaste mi declaración y te contradeciste. La reducción de las funciones o la modificación de los plazos son compromisos, cada uno de los cuales puede afectar el costo del proyecto y las expectativas de las partes interesadas. Probar el código heredado que no se ha probado completamente antes es extremadamente difícil, ya que debe comprender no solo el código mientras se ejecuta, sino las intenciones del creador original, y escribir pruebas que capturen el comportamiento del código heredado existente no necesariamente muestra que el código funciona completamente como se supone que debe hacerlo.
S.Robins

Supongo que mi punto fue que algo que se vio comprometido, las características o cambios que aún no se crearon porque el desarrollo se está moviendo más rápido, no es un compromiso real porque si pierde cobertura para moverse más rápido, esas características y cambios pueden solo se asume que no funciona bien de todos modos. Entonces, ¿cuál era el punto de hacer esos cambios o agregar esas características si no importa si funcionan correctamente o no? Si no importa si funcionan o no correctamente, esos cambios no tuvieron que hacerse, y ahora deberían eliminarse del código.
still_dreaming_1

Ya no lo creo completamente, o al menos me doy cuenta del aspecto práctico de la verdad que estás diciendo, especialmente en una base de código heredada, así que eso es solo una explicación del punto que estaba tratando de hacer en ese momento. En realidad, ahora estoy completamente en conflicto sobre incluso hacer TDD todo el tiempo en una nueva base de código, y mucho menos obtener una cobertura del 100%. Por un lado, cada forma de lógica y razón me dice que ambas cosas deberían ser buenas y, sin embargo, en la práctica parece que no puedo hacerlo práctico. Entonces, algo está muy mal con el mundo de la programación, necesitamos un nuevo paradigma.
still_dreaming_1

1

Por supuesto, no hago esto, pero lo hice en dos grandes proyectos. Si tiene un marco para las pruebas unitarias configuradas de todos modos, no es difícil exactamente, pero se suma a muchas pruebas.

¿Hay algún obstáculo particular con el que te encuentres que te impida golpear esas últimas líneas? Si no es así, si obtener una cobertura del 95% al ​​100% es sencillo, entonces también debería hacerlo. Puesto que usted está pidiendo aquí, voy a asumir que no es algo. ¿Qué es ese algo?


Esta es una de las mejores respuestas aquí. Preguntar qué impide que una línea de código sea fácilmente codificable es una buena pregunta. Cubrir esas líneas lo obligará a mejorar el código para que suceda, por lo que será un triunfo, un triunfo.
still_dreaming_1

0

El 92% está bien. Siento que las preguntas reales son:

  • ¿Es el 92% la norma 'nueva' ahora? Si el próximo sprint tiene un 88% de prueba, ¿estará bien? Este es frecuentemente el comienzo de las suites de prueba que se abandonan.

  • ¿Qué tan importante es que el software funcione y no tenga errores? Tiene pruebas por estos motivos, no "por el simple hecho de realizar pruebas"

  • ¿Hay algún plan para regresar y completar las pruebas faltantes?

  • ¿Por qué estás probando? Parece que el foco es el% de la línea cubierta, no la funcionalidad


"¿Qué tan importante es que el software funcione y no tenga errores"? Buena pregunta. ¿Cuál es la definición de un error? Algo que no funciona según lo previsto. Si está bien que algún código no funcione correctamente, no lo escriba. Todo el punto del código es que funcione.
still_dreaming_1

0

Martin Fowler escribe en su blog :I would be suspicious of anything like 100% - it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing.

Sin embargo, incluso hay estándares que exigen una cobertura del 100% a nivel de unidad. Por ejemplo, es uno de los requisitos en los estándares de la comunidad europea de vuelos espaciales (ECSS, European Cooperation for Space Standardization). El documento vinculado aquí , cuenta una historia interesante del proyecto que tenía el objetivo de alcanzar el 100% de cobertura de prueba en un software ya completado. Se basa en entrevistas con los ingenieros involucrados que desarrollaron las pruebas unitarias.

Algunas de las lecciones son:

  • 100% de cobertura es inusual pero alcanzable
  • 100% de cobertura es a veces necesaria
  • 100% de cobertura trae nuevos riesgos
  • No optimice para el 100% métrico
  • Desarrollar una estrategia adecuada para maximizar la cobertura.
  • 100% de cobertura no es una condición suficiente para una buena calidad

0

Quizás preguntar si es factible y razonable no son las preguntas más útiles para hacer. Probablemente la respuesta más práctica es la aceptada. Analizaré esto en un nivel más filosófico.

La cobertura del 100% sería ideal, pero idealmente, no sería necesaria o sería mucho más fácil de lograr. Prefiero pensar si es natural y humano que factible o razonable.

El acto de programar correctamente es casi imposible con las herramientas actuales. Es muy difícil escribir código que sea totalmente correcto y que no tenga errores. Simplemente no es natural. Entonces, sin otra opción obvia, recurrimos a técnicas como TDD y cobertura de código de seguimiento. Pero mientras el resultado final siga siendo un proceso antinatural, tendrá dificultades para lograr que las personas lo hagan de manera consistente y feliz.

Lograr una cobertura del 100% del código es un acto antinatural. Para la mayoría de las personas, obligarlos a lograrlo sería una forma de tortura.

Necesitamos procesos, herramientas, lenguajes y códigos que se correspondan con nuestros modelos mentales naturales. Si no lo hacemos, no hay forma de probar la calidad de un producto.

Solo mira todo el software que hay hoy en día. La mayor parte se estropea con bastante regularidad. No queremos creer esto. Queremos creer que nuestra tecnología es mágica y hacernos felices. Por eso, elegimos ignorar, disculpar y olvidar la mayoría de las veces que nuestra tecnología falla. Pero si hacemos una evaluación honesta de las cosas, la mayoría del software que existe hoy en día es bastante malo.

Aquí hay un par de esfuerzos para hacer que la codificación sea más natural:

https://github.com/jcoplien/trygve

https://github.com/still-dreaming-1/PurposefulPhp

El último es extremadamente incompleto y experimental. En realidad, es un proyecto que comencé, pero creo que sería un gran paso adelante para el arte de la programación si alguna vez pudiera dedicar el tiempo necesario para completarlo. Básicamente, es la idea de que si los contratos expresan los únicos aspectos del comportamiento de una clase que nos interesan, y ya estamos expresando los contratos como código, ¿por qué no solo tenemos las definiciones de clase y método junto con los contratos? De esa manera, los contratos serían el código, y no necesitaríamos implementar todos los métodos. Deje que la biblioteca descubra cómo cumplir los contratos para nosotros.


-2

Alcanzar el 100% en el nuevo código debería ser muy factible y si está practicando TDD, probablemente lo alcanzará de forma predeterminada, ya que está escribiendo pruebas deliberadamente para cada línea de código de producción.

En el código heredado existente que se escribió sin pruebas unitarias, puede ser difícil ya que a menudo el código heredado no se escribió teniendo en cuenta las pruebas unitarias y puede requerir mucha refactorización. Ese nivel de refactorización a menudo no es práctico dadas las realidades del riesgo y el cronograma, por lo que se hacen compensaciones.

En mi equipo, especifico una cobertura de código del 100% y si vemos menos que eso en la revisión del código, el propietario técnico del componente discute por qué no se alcanzó el 100% con el desarrollador y debe estar de acuerdo con el razonamiento del desarrollador. A menudo, si hay un problema que alcanza el 100%, el desarrollador hablará con el propietario técnico antes de la revisión del código. Descubrimos que una vez que adquieras el hábito y aprendas técnicas para solucionar varios problemas comunes al agregar pruebas al código heredado que alcanzar el 100% regularmente no es tan difícil como inicialmente pensarías.

El libro de Michael Feather " Trabajando eficazmente con código heredado " ha sido invaluable para nosotros al idear estrategias para agregar pruebas a nuestro código heredado.


-3

No, no es posible y nunca lo será. Si fuera posible, todas las matemáticas caerían en el finitismo. Por ejemplo, ¿cómo probaría una función que tomó dos enteros de 64 bits y los multiplicó? Este siempre ha sido mi problema con las pruebas versus probar que un programa es correcto. Para cualquier cosa que no sean los programas más triviales, las pruebas son básicamente inútiles ya que solo cubren un pequeño número de casos. Es como verificar 1,000 números y decir que has probado la conjetura de Goldbach.


Oh! Entonces alguien está molesto porque no respondí el problema en el plano de su concepción; probar es un desperdicio ... No me importa si es popular. Nunca funcionará No puede. Los científicos informáticos más inteligentes lo han sabido (Dijkstra, Knuth, Hoare et al.). Sin embargo, supongo que si eres un programador de JavaScript que huye de eXtreme Programming, entonces no te importan esas manivelas. Blah, lo que sea, a quién le importa ... escribir código malo. Residuos CO ^ 2 ejecutando sus pruebas. - Quiero decir, ¿quién tiene tiempo para sentarse y pensar más? Lo exportamos a la computadora.
tonto

3
La pregunta está etiquetada "TDD". TDD es más una herramienta de diseño y una herramienta de exploración de problemas que una de prueba, y cada "prueba" es solo un ejemplo de cómo se comportará el código en algún contexto, para que las personas puedan leer y comprender lo que está sucediendo, y luego cambiarlo de manera segura . TDD bien hecho tiende a conducir a un código más limpio y fácil de usar, y ejecutar las pruebas solo verifica que la documentación esté actualizada. La mayoría de las suites TDD casi nunca detectan errores; no es para lo que están ahí. Creo que está siendo rechazado porque su respuesta revela esa falta de comprensión, y espero que este comentario ayude con eso.
Lunivore
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.