✨ Features: - App iOS completa para leer manga sin publicidad - Scraper con WKWebView para manhwaweb.com - Sistema de descargas offline - Lector con zoom y navegación - Favoritos y progreso de lectura - Compatible con iOS 15+ y Sideloadly/3uTools 📦 Contenido: - Backend Node.js con Puppeteer (opcional) - App iOS con SwiftUI - Scraper de capítulos e imágenes - Sistema de almacenamiento local - Testing completo - Documentación exhaustiva 🧪 Prueba: Capítulo 789 de One Piece descargado exitosamente - 21 páginas descargadas - 4.68 MB total - URLs verificadas y funcionales 🎉 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
427 lines
12 KiB
Markdown
427 lines
12 KiB
Markdown
# 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.
|