En un entorno de desarrollo que avanza tan rápido como Swift, las herramientas para realizar pruebas unitarias necesitan evolucionar. Es por eso que hoy te hablamos de Swift Testing y cómo aprovechar esta nueva herramienta.
Presentando Swift Testing
🤷🏻 ¿Qué es Swift Testing? Es el nuevo sistema de pruebas open source diseñado específicamente para Swift. Su objetivo es proporcionar una solución más moderna, flexible y potente que las herramientas de testing anteriores, como XCTest. Con Swift Testing, ahora aprovechamos características avanzadas como la concurrencia y los macros para reinventar la forma en que se hacen test, además de gestionar de manera eficiente grandes bases de código.
Los pilares de Swift Testing se basan en cuatro elementos clave: las funciones de test, las expectativas, los traits y las suites de prueba. Estos componentes permiten que nuestros tests no solo sean más fáciles de escribir, sino también más eficientes y escalables, sin perder la seguridad y fiabilidad que necesitamos en cualquier proyecto.
Las soluciones de siempre, pero mejoradas
🙈 Si has trabajado con XCTest, recordarás la famosa familia de afirmaciones XCTAssert. Estas nos ayudaban a comprobar que nuestras condiciones fueran ciertas, pero no siempre eran lo suficientemente flexibles o fáciles de interpretar en caso de fallo. Aquí es donde entra Swift Testing, que ha simplificado todo esto con la macro #expect.
Veamos un ejemplo para comprobar que un pedido de una tienda online contiene artículos que están en la tienda:
struct OrderCatalogTests { func validateOrderItemsInCatalog() async throws { let catalog = try await ProductCatalog() let order = Order(id: 12345, items: ["book", "pen", "notebook"]) for item in order.items { #expect(catalog.contains(item)) } } }
💡 En este ejemplo, estamos comprobando que cada artículo del pedido (order.items) existe en el catálogo (catalog). El test pasa si todos los productos del pedido están disponibles en el catálogo.
La macro #expect no solo evalúa condiciones como XCTAssert, sino que, en caso de fallo, captura los valores involucrados en la expresión, facilitando el diagnóstico.
Traits: personalizando tus pruebas
📬 Un aspecto interesante de Swift Testing es el uso de traits para personalizar los tests. Los traits permiten añadir información descriptiva, controlar cuándo se ejecuta un test, o modificar su comportamiento de diversas formas.
Por ejemplo, puedes crear un test que solo se ejecute si una funcionalidad está habilitada:
@Test(.enabled(if: AppFeatures.isDiscountActive)) func validateDiscountedPrice() { let product = Product(name: "Laptop", price: 1200) let discountedPrice = product.price * 0.9 #expect(product.priceAfterDiscount == discountedPrice) }
🙅🏻 También puedes deshabilitar tests temporalmente por algún error conocido:
@Test(.disabled("Debido a un bug en la calculadora de impuestos")) func validateTaxCalculation() { // Prueba deshabilitada temporalmente }
El uso de estos traits hace que el control de las pruebas sea más granular y se adapte mejor a situaciones dinámicas, como nuevas características o bugs en ciertas versiones del software.
Agrupando tests: la magia de las suites
🎭 En lugar de tener tests desorganizados en distintas partes del código, Swift Testing permite agruparlos en suites. Las suites te ayudan a organizar mejor tus tests, asegurando que partes relacionadas del código se validen juntas y evitando problemas de estado compartido accidental.
Por ejemplo, podrías agrupar todos los tests relacionados con el cálculo de precios en una suite:
@Suite(.tags(.pricing) struct PricingTests { let product = Product(name: "Laptop", price: 1200) @Test func validateBasePrice() { #expect(product.price == 1200) } @Test func validateDiscountedPrice() { let discountedPrice = product.price * 0.9 #expect(product.priceAfterDiscount == discountedPrice) } }
💼 Las suites no solo organizan mejor los tests, sino que también aseguran que cada test se ejecute en un entorno aislado, evitando la compartición de estado entre diferentes casos de prueba. Esto es esencial en aplicaciones más grandes y con tests más complejos. Y es 100% compatible con los planes de test.
Tests parametrizados: menos repetición, más eficiencia
🎈 Uno de los puntos fuertes de Swift Testing es la capacidad de crear tests parametrizados, es decir, un mismo test que puede repetirse con diferentes valores de entrada, eliminando la duplicación de código.
Por ejemplo, si quisiéramos validar los precios de distintos productos en una tienda online, podríamos hacerlo así:
struct ProductPriceTests { @Test("Validate product prices", arguments: [ ("Laptop", 1200), ("Phone", 800), ("Tablet", 600), ("Smartwatch", 250), ("Headphones", 150), ("Camera", 900), ("Printer", 300), ("Monitor", 400), ]) func validateProductPrice(productName: String, expectedPrice: Double) async throws { let productCatalog = try await ProductCatalog() let product = try #require(await productCatalog.product(named: productName)) #expect(product.price == expectedPrice) } }
📊 En este ejemplo, el test validateProductPrice se ejecuta con diferentes combinaciones de productos y precios. Cada ejecución comprueba que el precio del producto en cuestión coincide con el valor esperado.
Usamos la macro #require que funciona como un if let y propagará un error en caso que la expresión evaluada (opcional) devolviera nil.
Esta técnica te permite validar múltiples escenarios de manera eficiente sin necesidad de escribir un test separado para cada producto, lo que simplifica el código y facilita el mantenimiento. Además, puedes ejecutar cada test parametrizado de forma independiente, lo que permite depurar errores de manera más precisa si algo no sale como esperabas.
Conclusiones finales
🤔 Swift Testing está aquí para quedarse y simplificar el mundo de las pruebas en Swift. Desde expectativas y tests parametrizados hasta suites organizadas y traits dinámicos, este nuevo sistema nos ofrece más flexibilidad y poder, con una integración fluida con las últimas características del lenguaje.
📝 Si quieres aprender más sobre cómo sacar el máximo partido a Swift Testing, en Apple Coding Academy tenemos formaciones diseñadas para ayudarte a implementar estas técnicas en tu flujo de trabajo. El futuro de las pruebas es ahora, y está en Swift.