2009-07-08 14 views
61

Estoy probando el desarrollo impulsado por pruebas en un proyecto de juguete. Puedo hacer que las pruebas funcionen para la interfaz pública de mis clases (aunque todavía estoy en la cerca porque estoy escribiendo más código de prueba que en los métodos que se están probando).Pruebas unitarias de métodos privados en Xcode

Tiendo a usar muchos métodos privados porque me gusta mantener limpias las interfaces públicas; sin embargo, aún me gustaría usar pruebas en estos métodos.

Como Cocoa es un lenguaje dinámico, todavía puedo llamar a estos métodos privados, pero recibo advertencias en mis pruebas de que mi clase puede no responder a estos métodos (aunque es evidente que sí). Como me gusta compilar sin advertencias aquí están mis preguntas:

  1. ¿Cómo desactivo estas advertencias en Xcode?
  2. ¿Hay algo más que pueda hacer para desactivar estas advertencias?
  3. ¿Estoy haciendo algo mal al probar las pruebas de "caja blanca"?
+0

Hasta ahora no he expuesto mis métodos privados en el archivo de encabezado. Por qué debería hacerlo, me parece más elegante tener solo métodos públicos en el encabezado. Sin embargo, parece que tengo que cambiar esto en Objective-c. – Nils

+1

@Nils No tiene métodos privados en un archivo de encabezado. Una vez allí, son métodos públicos. – Abizern

+0

Sí @Abizern hasta ahora seguí el consejo de Peter Hosey a continuación. – Nils

Respuesta

88

¿Cómo puedo desactivar estas advertencias en Xcode?

Do not.

¿Hay alguna otra cosa que pueda hacer para desactivar estas advertencias?

Do not.

¿Estoy haciendo algo mal al intentar las pruebas de "caja blanca"?

La solución es mover sus métodos privados a una categoría en su propia cabecera. Importe este encabezado tanto en la clase real como en los archivos de implementación de la clase de prueba.

+4

+1 - ¡Gran sugerencia!Para obtener más información, consulte esta pregunta/respuesta: http://stackoverflow.com/questions/1020070/#1020330 El encabezado privado que contiene la categoría con métodos privados se puede llamar MyClass_Private.h, por ejemplo. –

+2

Gracias por esta respuesta. Todavía no estoy seguro, pero debo admitir que hay un elemento "divertido" de escribir una prueba y luego hacerla pasar. – Abizern

+0

He declarado estos métodos privados en un archivo de encabezado de extensión y, por alguna razón, en XCode5 el compilador se queja cuando llama a estos métodos privados declarados en el encabezado de la extensión. ¿Alguna idea? – ArdenDev

4

Parece que otra pregunta tiene la respuesta: Is there a way to suppress warnings in Xcode?

+1

Eso tiene parte de la respuesta, pero la mejor (o al menos más completa) solución para un caso particular probablemente esté relacionada con la respuesta de Peter o mi respuesta. –

+0

Este tampoco es el lugar para poner enlaces duplicados. – Rambatino

124

Recuerda que en Objective-C no existen los "métodos privados", y no es solo porque es un lenguaje dinámico. Por diseño, Objective-C tiene modificadores de visibilidad para ivars, pero no para métodos, no es por accidente que puede llamar a cualquier método que desee.

@Peter es una buena sugerencia. Para complementar su respuesta, una alternativa que he usado (cuando no quiero/necesito un encabezado solo para métodos privados) es declarar una categoría en el archivo de prueba de unidad en sí. (Yo uso @interface MyClass (Test) como nombre.) Esta es una gran manera de agregar métodos que serían innecesarios en el código de lanzamiento, como para acceder a ivars a los que tiene acceso la clase bajo prueba. (Esto es obviamente un problema menor cuando se usan propiedades).

He encontrado que este enfoque hace que sea fácil exponer y verificar el estado interno, así como agregar métodos de solo prueba. Por ejemplo, en this unit test file, escribí un método -isValid para verificar la corrección de un montón binario. En producción, este método sería una pérdida de espacio, ya que supongo que un montón es válido; solo me preocupo cuando pruebo regresiones de pruebas unitarias si modifico el código.

+4

¡Gran sugerencia! Me gusta más esta idea, mantiene la exposición de los métodos en contexto. – Bach

+1

Vaya, Peter Hosey debería arreglar su respuesta para referirse a la suya. Buen trabajo, intentándolo ahora +1 –

+0

increíble, gracias! – kernix

3

Si bien tener un encabezado privado o definir su propia categoría probablemente sean soluciones más correctas, también hay otra solución muy simple: convertir el objeto en (id) antes de llamar al método.

+0

Yo diría que esto es preferible. Sin duplicación, sin fragmentación de los archivos fuente y aprovecha la naturaleza dinámica de Objective-C. – Michael

+1

@ Michael un problema con este enfoque en xCode5 es que usar un selector desconocido causará una advertencia. El compilador tiene que ver el selector de alguna manera al compilar una prueba de unidad particular. Por esta razón, me gusta la solución de Quinn Taylor. –

3

Si no desea distribuir las implementaciones de métodos privados en varios archivos fuente, un refinamiento de la solución de Categoría es definir una Extensión (esencialmente una Categoría anónima - consulte Apple's documentation) en un archivo de encabezado importado por ambos la implementación de su clase existente y los archivos fuente de prueba de unidad relevantes.

El uso de una extensión le permite al compilador advertirle si la implementación del método privado no está presente en el bloque @implementation principal. This link lo ilustra muy bien.

+0

No publique enlaces aquí, en lugar de palabras clave. El enlace a apple se ha movido. –

3

Estaba lidiando con el mismo problema cuando comencé con TDD hace unos días. He encontrado este punto de vista muy interesante en Test-Driven iOS Development libro:

A menudo me han preguntado: “¿Debo probar mis métodos privados?” O la pregunta relacionada “¿Cómo debería probar mis métodos privados?” La gente al hacer la segunda pregunta, asumimos que la respuesta a la primera es "Sí" y ahora estamos buscando una forma de exponer las interfaces privadas de sus clases en sus suites de prueba.

Mi respuesta se basa en la observación de un hecho sutil: ya ha probado sus métodos privados. Al seguir el enfoque de refactorización rojo-verde común en el desarrollo basado en pruebas, diseñó las API públicas de sus objetos para hacer el trabajo que esos objetos deben hacer. Con ese trabajo especificado por las pruebas y la ejecución continua de las pruebas que le aseguran que no ha roto nada, puede organizar las tuberías internas de sus clases como mejor le parezca.

Sus métodos privados ya han sido probados porque todo lo que está haciendo es un comportamiento de refactorización para el cual ya tiene pruebas. Nunca debe terminar en una situación en la que un método privado no se haya probado o no se haya probado por completo, ya que solo se creará cuando vea la oportunidad de limpiar la implementación de métodos públicos. Esto asegura que los métodos privados existen solo para respaldar la clase de que se deben invocar durante la prueba porque definitivamente se invocan desde métodos públicos.

+0

Leo este libro y aunque él tiene razón en algún momento, no responde la pregunta. ¿Por qué debo mover una IBAction al archivo de interfaz (debe ser privado a otras clases)? Este no es un objeto modelo, estamos hablando de un controlador aquí, un controlador de vista en particular (este libro no habla lo suficiente sobre las pruebas de IU en iOS) –

+0

Mi publicación es una reacción a la pregunta original. Pensé que la cita contenía un punto de vista interesante, porque cuando necesitas probar métodos privados, probablemente significa que debes mover esos métodos al objeto separado. Y este "liderazgo" del programador para los objetos más pequeños con métodos más cortos es el principal beneficio del TDD. Por supuesto, las pruebas unitarias no se tratan de pruebas de UI. Hay otras herramientas (en instrumentos) y otras pruebas para eso. –

0

Easy Job. Pasos: 1. Usted tiene - (NSString *) getTestString; en su archivo m objetivo para Foo interfaz

  1. añadir una categoría en el archivo de prueba de unidad:

    DemoHomeViewController @interface() - (NSString *) getTestString; @end

Entonces, haga lo que quiera ahora.