# MangaReader Test Suite Suite completa de tests para el proyecto MangaReader usando XCTest. ## Tabla de Contenidos - [Descripción General](#descripción-general) - [Estructura de Tests](#estructura-de-tests) - [Ejecutar Tests](#ejecutar-tests) - [Guía de Tests](#guía-de-tests) - [Mejores Prácticas](#mejores-prácticas) ## Descripción General Esta suite de tests cubre todos los componentes principales del proyecto MangaReader: 1. **Modelos de Datos** - Validación de Codable, edge cases, y lógica de negocio 2. **StorageService** - Almacenamiento local, favoritos, progreso de lectura 3. **ManhwaWebScraper** - Web scraping y parsing de HTML/JavaScript 4. **Integración** - Flujos completos que conectan múltiples componentes ## Estructura de Tests ``` Tests/ ├── ModelTests.swift # Tests para modelos de datos ├── StorageServiceTests.swift # Tests para servicio de almacenamiento ├── ManhwaWebScraperTests.swift # Tests para web scraper ├── IntegrationTests.swift # Tests de integración ├── TestHelpers.swift # Helpers y factories para tests └── XCTestSuiteExtensions.swift # Extensiones de XCTest ``` ## Ejecutar Tests ### Desde Xcode 1. Abrir el proyecto en Xcode 2. Cmd + U para ejecutar todos los tests 3. Cmd + 6 para abrir el Test Navigator 4. Click derecho en un test específico para ejecutarlo ### Desde Línea de Comandos ```bash # Ejecutar todos los tests xcodebuild test -scheme MangaReader -destination 'platform=iOS Simulator,name=iPhone 15' # Ejecutar tests específicos xcodebuild test -scheme MangaReader -only-testing:MangaReaderTests/ModelTests # Ejecutar con cobertura xcodebuild test -scheme MangaReader -enableCodeCoverage YES ``` ## Guía de Tests ### ModelTests.swift Prueba todos los modelos de datos del proyecto. #### Tests Incluidos: **Manga Model:** - `testMangaInitialization` - Verifica inicialización correcta - `testMangaCodableSerialization` - Prueba encoding/decoding JSON - `testMangaDisplayStatus` - Verifica traducción de estados - `testMangaHashable` - Prueba conformidad con Hashable **Chapter Model:** - `testChapterInitialization` - Inicialización con valores por defecto - `testChapterDisplayNumber` - Formato de número de capítulo - `testChapterProgress` - Cálculo de progreso de lectura - `testChapterCodableSerialization` - Serialización JSON **MangaPage Model:** - `testMangaPageInitialization` - Creación de páginas - `testMangaPageThumbnailURL` - URLs de thumbnails - `testMangaPageCodableSerialization` - Serialización **ReadingProgress Model:** - `testReadingProgressInitialization` - Creación de progreso - `testReadingProgressIsCompleted` - Lógica de completación - `testReadingProgressCodableSerialization` - Persistencia **DownloadedChapter Model:** - `testDownloadedChapterInitialization` - Creación de capítulos descargados - `testDownloadedChapterDisplayTitle` - Formato de títulos - `testDownloadedChapterCodableSerialization` - Serialización completa **Edge Cases:** - `testMangaWithEmptyGenres` - Manejo de arrays vacíos - `testMangaWithNilCoverImage` - Imagen de portada opcional - `testChapterWithZeroNumber` - Capítulo cero - `testMangaPageWithNegativeIndex` - Índices negativos ### StorageServiceTests.swift Prueba el servicio de almacenamiento local. #### Tests Incluidos: **Favorites:** - `testSaveFavorite` - Guardar un favorito - `testSaveMultipleFavorites` - Guardar varios favoritos - `testSaveDuplicateFavorite` - Evitar duplicados - `testRemoveFavorite` - Eliminar favorito - `testIsFavorite` - Verificar si es favorito **Reading Progress:** - `testSaveReadingProgress` - Guardar progreso - `testSaveMultipleReadingProgress` - Múltiples progresos - `testUpdateExistingReadingProgress` - Actualizar progreso - `testGetLastReadChapter` - Obtener último capítulo leído - `testGetReadingProgressWhenNotExists` - Progreso inexistente **Downloaded Chapters:** - `testSaveDownloadedChapter` - Guardar metadatos de capítulo - `testIsChapterDownloaded` - Verificar descarga - `testGetDownloadedChapters` - Listar capítulos - `testDeleteDownloadedChapter` - Eliminar capítulo **Image Caching:** - `testSaveAndLoadImage` - Guardar y cargar imagen - `testLoadNonExistentImage` - Imagen inexistente - `testGetImageURL` - Obtener URL de imagen **Storage Management:** - `testGetStorageSize` - Calcular tamaño usado - `testClearAllDownloads` - Limpiar todo el almacenamiento - `testFormatFileSize` - Formatear tamaño a legible **Concurrent Operations:** - `testConcurrentImageSave` - Guardar imágenes concurrentemente ### ManhwaWebScraperTests.swift Prueba el web scraper con mocks de WKWebView. #### Tests Incluidos: **Error Handling:** - `testScrapingErrorDescriptions` - Descripciones de errores - `testScrapingErrorLocalizedError` - Conformidad con LocalizedError **Chapter Parsing:** - `testChapterParsingFromJavaScriptResult` - Parsear respuesta JS - `testChapterParsingWithInvalidData` - Manejar datos inválidos - `testChapterDeduplication` - Eliminar capítulos duplicados - `testChapterSorting` - Ordenar capítulos **Image Parsing:** - `testImageParsingFromJavaScriptResult` - Parsear URLs de imágenes - `testImageParsingWithEmptyArray` - Array vacío de imágenes - `testImageParsingWithInvalidURLs` - Filtrar URLs inválidas **Manga Info Parsing:** - `testMangaInfoParsingFromJavaScriptResult` - Extraer info de manga - `testMangaInfoParsingWithEmptyFields` - Campos vacíos - `testMangaStatusParsing` - Normalizar estados **URL Construction:** - `testMangaURLConstruction` - Construir URLs de manga - `testChapterURLConstruction` - Construir URLs de capítulo - `testURLConstructionWithSpecialCharacters` - Caracteres especiales **Edge Cases:** - `testChapterNumberExtraction` - Extraer números de capítulo - `testChapterSlugExtraction` - Extraer slugs - `testDuplicateRemovalPreservingOrder` - Eliminar duplicados manteniendo orden ### IntegrationTests.swift Prueba flujos completos que integran múltiples componentes. #### Tests Incluidos: **Complete Flow:** - `testCompleteScrapingAndStorageFlow` - Scraper -> Storage - `testChapterDownloadFlow` - Descarga completa de capítulo - `testReadingProgressTrackingFlow` - Seguimiento de lectura **Multi-Manga Scenarios:** - `testMultipleMangasProgressTracking` - Varios mangas - `testMultipleChapterDownloads` - Descargas de múltiples capítulos **Error Handling:** - `testDownloadFlowWithMissingImages` - Imágenes faltantes - `testStorageCleanupFlow` - Limpieza de almacenamiento **Data Persistence:** - `testDataPersistenceAcrossOperations` - Persistencia de datos **Concurrent Operations:** - `testConcurrentFavoriteOperations` - Operaciones concurrentes favoritos - `testConcurrentProgressOperations` - Operaciones concurrentes progreso - `testConcurrentImageOperations` - Guardado concurrente de imágenes **Large Scale:** - `testLargeScaleFavoriteOperations` - 1000 favoritos - `testLargeScaleProgressOperations` - 500 progresos ## TestHelpers.swift Proporciona helpers y factories para crear datos de prueba: ### TestDataFactory Crea objetos de prueba: ```swift let manga = TestDataFactory.createManga( slug: "test-manga", title: "Test Manga" ) let chapter = TestDataFactory.createChapter(number: 1) let chapters = TestDataFactory.createChapters(count: 10) ``` ### ImageTestHelpers Crea imágenes de prueba: ```swift let image = ImageTestHelpers.createTestImage( color: .blue, size: CGSize(width: 800, height: 1200) ) ``` ### FileSystemTestHelpers Operaciones de sistema de archivos: ```swift let tempDir = try FileSystemTestHelpers.createTemporaryDirectory() try FileSystemTestHelpers.createTestChapterStructure( mangaSlug: "test", chapterNumber: 1, pageCount: 10, in: tempDir ) ``` ### StorageTestHelpers Limpieza y preparación de almacenamiento: ```swift StorageTestHelpers.clearAllStorage() StorageTestHelpers.seedTestData( favoriteCount: 5, progressCount: 10 ) ``` ## Mejores Prácticas ### 1. Independencia de Tests Cada test debe ser independiente y poder ejecutarse solo: ```swift override func setUp() { super.setUp() // Limpiar estado antes del test UserDefaults.standard.removeObject(forKey: "favoritesKey") } override func tearDown() { // Limpiar estado después del test super.tearDown() } ``` ### 2. Nombres Descriptivos Usa nombres que describan qué se está probando: ```swift // ✅ Bueno func testSaveDuplicateFavoriteDoesNotAddDuplicate() // ❌ Malo func testFavorite() ``` ### 3. Un Assert por Test Cuando sea posible, usa un assert por test: ```swift // ✅ Bueno func testFavoriteIsSaved() { storageService.saveFavorite(mangaSlug: "test") XCTAssertTrue(storageService.isFavorite(mangaSlug: "test")) } func testFavoriteIsRemoved() { storageService.saveFavorite(mangaSlug: "test") storageService.removeFavorite(mangaSlug: "test") XCTAssertFalse(storageService.isFavorite(mangaSlug: "test")) } // ❌ Evitar func testFavoriteOperations() { storageService.saveFavorite(mangaSlug: "test") XCTAssertTrue(storageService.isFavorite(mangaSlug: "test")) storageService.removeFavorite(mangaSlug: "test") XCTAssertFalse(storageService.isFavorite(mangaSlug: "test")) } ``` ### 4. AAA Pattern Usa el patrón Arrange-Act-Assert: ```swift func testChapterProgressCalculation() { // Arrange - Preparar el test var chapter = Chapter(number: 1, title: "Test", url: "", slug: "") let expectedPage = 5 // Act - Ejecutar la acción chapter.lastReadPage = expectedPage // Assert - Verificar el resultado XCTAssertEqual(chapter.progress, Double(expectedPage)) } ``` ### 5. Mock de Dependencias No hagas llamadas de red reales en tests unitarios: ```swift // ✅ Bueno - Mock let mockJSResult = [["number": 10, "title": "Chapter 10"]] let chapters = parseChaptersFromJS(mockJSResult) // ❌ Malo - Llamada real let chapters = await scraper.scrapeChapters(mangaSlug: "test") ``` ### 6. Tests Asíncronos Usa `async/await` apropiadamente: ```swift func testAsyncImageSave() async throws { let image = createTestImage() let url = try await storageService.saveImage( image, mangaSlug: "test", chapterNumber: 1, pageIndex: 0 ) XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) } ``` ## Cobertura de Código Objetivos de cobertura: - **Modelos**: 95%+ (lógica crítica de datos) - **StorageService**: 90%+ (manejo de archivos y persistencia) - **Scraper**: 85%+ (con mocks de WKWebView) - **Integración**: 80%+ (flujos críticos de usuario) ## Troubleshooting ### Tests Fallan Intermittentemente Si un test falla solo algunas veces: 1. Verifica que hay cleanup adecuado en `tearDown()` 2. Asegura que los tests son independientes 3. Usa `waitFor` apropiadamente para operaciones asíncronas ### Tests de Performance Fallan Si los tests de rendimiento fallan en diferentes máquinas: 1. Ajusta las métricas según el hardware 2. Usa medidas relativas en lugar de absolutas 3. Considera deshabilitar tests de performance en CI ### Memory Leaks en Tests Para detectar memory leaks: ```swift func testNoMemoryLeak() { let instance = MyClass() assertNoMemoryLeak(instance) } ``` ## Recursos Adicionales - [XCTest Documentation](https://developer.apple.com/documentation/xctest) - [Testing with Xcode](https://developer.apple.com/documentation/xcode/testing) - [Unit Testing Best Practices](https://www.objc.io/books/unit-testing/) ## Contribuir Para agregar nuevos tests: 1. Decide si es unit test, integration test, o performance test 2. Agrega el test al archivo apropiado 3. Usa los helpers en `TestHelpers.swift` cuando sea posible 4. Asegura que el test es independiente 5. Agrega documentación si el test es complejo 6. Ejecuta todos los tests para asegurar que nada se rompe ## Licencia Mismo que el proyecto principal.