Bienvenido, les saluda Luis y para hoy les traigo un post.
Descubra lo fácil que es comenzar a probar sus componentes SwiftUI
De vez en cuando, es una buena idea tomarse un momento de trabajar en sus proyectos para dar un paso atrás y ver lo que está sucediendo actualmente en su campo de desarrollo.
Afortunadamente, para los desarrolladores de iOS, ese momento llega (al menos) una vez al año cuando Apple comienza a anunciar las nuevas funciones para iOS en la WWDC
.
No pretende ser un reemplazo directo de UIKit
, pero ciertamente es un enfoque más moderno para crear interfaces de usuario. Es uno de los mayores cambios en el desarrollo de la interfaz de usuario para iOS en los últimos años.
Con una buena forma declarativa de crear interfaces de usuario, me preguntaba si la vida sería más fácil al hacer el desarrollo basado en pruebas (TDD
). La perspectiva de no tener que ir y venir entre Storyboard
, ViewController
y XCTest
parecía al menos muy atractiva.
Creé un pequeño proyecto de prueba, que simplemente mostraría una etiqueta de texto, solicitaría alguna entrada de un campo de texto y mostraría un resultado en un tercer campo de texto.
Nada de ciencia espacial, sino algo con lo que empezar. Agregaré funcionalidades de forma incremental, no todas a la vez, para que pueda ver cómo evolucionan sus pruebas a medida que avanza.
Esto refleja la forma en que ocurre el desarrollo normal: las características se amplían y agregan con el tiempo, y las suites de prueba deben actualizarse en consecuencia.
El proyecto de prueba
Primero, creé una nueva aplicación de vista única desde el cuadro de diálogo de la opción Nuevo proyecto. Lo nombré SimpleGreeter
, ¡adivina lo que hará!
Asegúrese de seleccionar SwiftUI para Interfaz de usuario y marque la opción Incluir pruebas de IU. No marqué la opción Incluir pruebas unitarias en este caso debido a la escala limitada de la aplicación, pero es fácil agregar pruebas unitarias después si lo desea.
Una vez que haya guardado la aplicación en una ubicación adecuada, debería verse así. Si la vista previa no se muestra, asegúrese de presionar el botón Reanudar en la parte superior derecha de la ventana de vista previa.
Ahora, antes de comenzar a codificar la interfaz de usuario, tómese un momento y piense en lo que queremos mostrar en la aplicación y cómo puede interactuar el usuario (lo estamos haciendo mediante pruebas, ¿no?).
Para empezar, quiero mostrar una etiqueta de texto para proporcionar algunas instrucciones y un campo de texto para que el usuario ingrese su nombre.
SimpleGreeterUITests.swift
y agregue una prueba. En primer lugar, quite el tearDown
y métodos testLaunchPerformance
ya que no los usaremos para este ejemplo.
Cambiar el nombre del método testExample
a algo como testInitialViewState
para indicar que este es un método que prueba el estado predeterminado de la interfaz de usuario. Además, siéntase libre de eliminar la abundancia de comentarios que Xcode genera para usted.
Para completar el método de prueba, agregaremos cuatro afirmaciones:
- ¿Existe la etiqueta de texto?
- ¿La etiqueta de texto tiene el texto correcto?
- ¿Existe el campo de texto?
- ¿El campo de texto tiene el texto de marcador de posición correcto?
Deberías terminar con algo como esto:
import XCTest class SimpleGreeterUITests: XCTestCase { override func setUp() { continueAfterFailure = false } func testInitialViewState() { let app = XCUIApplication() app.launch() let textLabel = app.staticTexts.element let textField = app.textFields.element XCTAssert(textLabel.exists) XCTAssertEqual(textLabel.label, "Please enter your name below") XCTAssert(textField.exists) XCTAssertEqual(textField.placeholderValue, "Your name") } }
Ahora, por supuesto, si ejecuta esta prueba, fallará aunque haya una etiqueta de texto porque contiene un texto diferente (“¡Hola, mundo!”
).
Así que arreglemos eso, iremos a nuestro file ContentView.swift
y comience a agregar algunos controles, así como a asegurarse de que nuestras afirmaciones sean verdaderas.
Organizaremos la etiqueta de texto y el campo de texto en un VStack
, agregue algo de relleno y proporcione títulos y marcadores de posición razonables.
La entrada del campo de texto la almacenaremos en una variable llamada name
. Eso es todo por ahora, solo concéntrese en la funcionalidad mínima que necesita agregar para que sus pruebas pasen. Terminarás con algo como esto:
import SwiftUI struct ContentView: View { @State private var name: String = "" var body: some View { VStack { Text("Please enter your name below") TextField("Your name", text: $name) }.padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Ahora vuelve a ejecutar tus pruebas y listo. Verde en todos los ámbitos. Preste especial atención a la anotación @State
para la variable name
. Esto le indica a SwiftUI que cada vez que se actualiza este valor, la vista debe actualizarse en consecuencia.
Pero aún no hemos terminado, ahora queremos actualizarlo, por lo que cada vez que ingresa algo en el campo de texto, vemos un mensaje en una nueva etiqueta de texto que lo saluda.
Por tanto, si escribo "Jony"
en el campo de texto, deberíamos ver una etiqueta que diga "Encantado de conocerte, Jony"
. Nuevamente, haremos esta prueba primero.
Vuelve al archivo SimpleGreeterUITests.swift
y agregue otro método que pruebe si puede tocar en el campo de texto, agregar texto y ver el resultado correcto.
Sin embargo, dado que vamos a agregar otra etiqueta de texto, no podemos simplemente referirnos a la etiqueta de texto usando app.staticTexts.element
porque esto solo funciona si tenemos exactamente una etiqueta de texto en la vista. De la documentación:
Entonces, en cambio, necesitaremos diferenciar entre la etiqueta de texto anterior y la nueva etiqueta de texto que vamos a agregar.
Podemos hacer esto usando el accessibilityIdentifier
. Esta propiedad es proporcionada por el protocolo UIAccessibilityIdentification
y la documentación dice:
Entonces, esta es exactamente la propiedad que Apple cree que deberíamos usar para consultar nuestra vista para el elemento correcto.
Esto evita (ab
) usar el accessibilityLabel
(que utilizan los lectores de pantalla) o la comprobación de determinados títulos u otros contenidos textuales que pueden cambiar o tener valores inesperados en tiempo de ejecución. La prueba se verá así:
func testGreeter() { let app = XCUIApplication() app.launch() let textLabel = app.staticTexts["greetingTextLabel"] let textField = app.textFields.element textField.tap() textField.typeText("J") textField.typeText("o") textField.typeText("n") textField.typeText("y") XCTAssertEqual(textLabel.label, "Nice to meet you, Jony.") }
La parte interesante aquí es que escribiremos cada letra una por una. El método typeText
acepta una cadena completa, pero si lo usa, la automatización de la interfaz de usuario a veces escribirá demasiado rápido y algunos caracteres se omitirán, lo que resultará en pruebas fallidas al azar. No es genial.
Nuevamente, esta prueba fallará porque no tenemos una etiqueta con la accessibilityIdentifier
y no implementamos el comportamiento correcto.
Arreglemos eso. Simplemente agregaremos una nueva etiqueta de texto en la parte inferior del VStack
en ContentView.swift
, y estamos listos para comenzar:
Text("Nice to meet you, (name).") .accessibility(identifier: "greetingTextLabel")
¿Derecho? Bueno no. Porque, sí, su nueva prueba pasará, pero su primera prueba fallará. La aplicación tiene el efecto secundario no deseado de que ahora tenemos un campo de texto adicional que en su estado inicial dice Nice to meet you, .
¡Horrible!
Entonces queremos arreglar estas cosas:
- Corrija la primera prueba (y actualícela para la nueva interfaz de usuario).
- Asegúrese de que siempre que no se ingrese un nombre (el nombre está vacío), no mostramos ningún saludo.
Para corregir el primer punto, agregaremos un identificador a la primera etiqueta de texto, actualizaremos la prueba para usar esto y agregaremos aserciones adicionales para la etiqueta de texto recién introducida; verifique si está presente y si el estado predeterminado está vacío.
La prueba actualizada se verá así:
func testInitialViewState() { let app = XCUIApplication() app.launch() let textField = app.textFields.element let enterNameLabel = app.staticTexts["enterNameLabel"] let greeterLabel = app.staticTexts["greetingTextLabel"] XCTAssert(enterNameLabel.exists) XCTAssertEqual(enterNameLabel.label, "Please enter your name below") XCTAssert(greeterLabel.exists) XCTAssert(greeterLabel.label.isEmpty) XCTAssert(textField.exists) XCTAssertEqual(textField.placeholderValue, "Your name") }
Ahora que hemos actualizado la primera prueba, actualizaremos el comportamiento de la vista para que coincida con esto. Introduciremos una propiedad calculada, greetingText
, que devuelve una cadena vacía (si no se ingresó ningún nombre) o el texto de saludo completo.
Aquí, he usado la nueva función Swift 5.1 (SE-0255)
donde si su método solo contiene una expresión, también se usa como un retorno implícito. El final ContentView
se ve como esto:
import SwiftUI struct ContentView: View { @State private var name: String = "" private var greeterText: String { name.isEmpty ? "" : "Nice to meet you, \(name)." } var body: some View { VStack { Text("Please enter your name below") .accessibility(identifier: "enterNameLabel") TextField("Your name", text: $name) Text(greeterText) .accessibility(identifier: "greetingTextLabel") }.padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
El final SimpleGreeterUITests
parece:
import XCTest class SimpleGreeterUITests: XCTestCase { override func setUp() { continueAfterFailure = false } func testInitialViewState() { let app = XCUIApplication() app.launch() let textField = app.textFields.element let enterNameLabel = app.staticTexts["enterNameLabel"] let greeterLabel = app.staticTexts["greetingTextLabel"] XCTAssert(enterNameLabel.exists) XCTAssertEqual(enterNameLabel.label, "Please enter your name below") XCTAssert(greeterLabel.exists) XCTAssert(greeterLabel.label.isEmpty) XCTAssert(textField.exists) XCTAssertEqual(textField.placeholderValue, "Your name") } func testGreeter() { let app = XCUIApplication() app.launch() let textLabel = app.staticTexts["greetingTextLabel"] let textField = app.textFields.element textField.tap() textField.typeText("J") textField.typeText("o") textField.typeText("n") textField.typeText("y") XCTAssertEqual(textLabel.label, "Nice to meet you, Jony.") } }
Conclusión
Para terminar, esto es solo un breve vistazo a las pruebas de interfaz de usuario de Xcode usando SwiftUI. Por supuesto, se puede hacer mucho más para mejorar la interfaz de usuario, pero muestra que con muy poco esfuerzo, puede comenzar a usar las pruebas de interfaz en sus vistas de SwiftUI.
No creo que haya sido más fácil hasta ahora hacer cambios y verlos entrar en vigencia de inmediato en su aplicación.
TDD
) requiere un poco de disciplina y dedicación. Además, la documentación de Apple a este respecto a veces ofrece poco soporte y pueden ocurrir fallas en las pruebas sin una descripción clara de lo que realmente está causando esto. Puede ser un poco complicado, pero en el futuro te lo agradecerás.Si está comenzando un nuevo proyecto o simplemente ingresando a SwiftUI y aún no está utilizando las pruebas de IU, ahora es un momento tan bueno como cualquier otro para comenzar.
Como nota final: el uso de la lógica en sus vistas (por ejemplo, greeterText
) debería, por supuesto, evitarse tanto como sea posible y solo se hace aquí para no complicar las cosas para este proyecto de ejemplo.
Sin embargo, tan pronto como empiece a notar que tiene lógica de aplicación en sus vistas, considere cambiar a una arquitectura basada en ViewModel (y asegúrese de probar eso también).
Espero que te haya sido de utilidad. Gracias por leer este post.
Añadir comentario