# Testes Mobile: Guia Completo para Apps iOS e Android

**Meta Description:** Aprenda a testar aplicativos mobile: Appium, Espresso, XCTest, testes emulators vs dispositivos reais, e automação mobile para iOS e Android.

---

## Introdução aos Testes Mobile

Testar aplicativos mobile apresenta desafios únicos: múltiplas versões de OS, diferentes tamanhos de tela, conectividade variável e interação touch. Uma estratégia de teste mobile eficaz deve cobrir dispositivos, plataformas e condições de uso diversas.

### Panorama Mobile

| Dado | Valor |
|------|-------|
| Apps disponíveis | 6.5+ milhões |
| Tempo médio em apps | 4.8 horas/dia |
| Usuários mobile | 7.33 bilhões |
| Fragmentação Android | 24.000+ dispositivos |
| iOS versions ativas | 4-5 simultâneas |

---

## Estratégia de Testes Mobile

### Pirâmide de Testes Mobile

```
              /\
             /  \         E2E Tests
            /____\        (10%)
           /      \       Funcionalidade completa
          /        \      em dispositivos reais
         /__________\
        /            \   Integration Tests
       /______________\  (20%)
      /                \ Componentes e módulos
     /__________________\
    /                    \ Unit Tests
   /______________________\ (70%)
   Componentes e lógica
```

### Dispositivos: Emuladores vs. Reais

| Aspecto | Emuladores | Dispositivos Reais |
|---------|------------|-------------------|
| **Custo** | Grátis | Dispositivos físicos |
| **Velocidade** | Mais lentos | Mais rápidos |
| **Acurácia** | Limitada | Real |
| **Hardware** | CPU/RAM limitados | Real performance |
| **Sensores** | Simulados | Reais (GPS, câmera) |
| **Rede** | Controlável | Real, variável |

**Recomendação:** Usar ambos - emuladores para CI/rapidez, dispositivos reais para QA final.

---

## Ferramentas de Teste Mobile

### 1. Appium - Cross-Platform

**Melhor para:** Testes cross-platform (iOS + Android) com uma codebase.

#### Instalação

```bash
# macOS com Homebrew
brew install node
npm install -g appium
npm install -g appium-doctor
appium &
```

#### Configuração Android

```bash
# SDK Android
export ANDROID_HOME=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools

# Start emulador
$ANDROID_HOME/emulator/emulator -avd Pixel_6_API_33
```

#### Script Appium (Python)

```python
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class TestApp:
    @pytest.fixture
    def driver(self):
        capabilities = {
            "platformName": "Android",
            "deviceName": "Pixel_6_API_33",
            "app": "/path/to/app.apk",
            "automationName": "UiAutomator2",
            "autoGrantPermissions": True
        }
        
        driver = webdriver.Remote(
            "http://localhost:4723/wd/hub",
            capabilities
        )
        yield driver
        driver.quit()
    
    def test_login_flow(self, driver):
        # Aguardar elementos
        wait = WebDriverWait(driver, 10)
        
        # Clicar no campo email
        email_field = wait.until(
            EC.presence_of_element_located(
                (AppiumBy.ANDROID_UIAUTOMATOR, 'resourceId("com.app:id/email")')
            )
        )
        email_field.send_keys("usuario@test.com")
        
        # Clicar no campo senha
        driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'resourceId("com.app:id/password")'
        ).send_keys("senha123")
        
        # Clicar em Login
        driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'text("Entrar")'
        ).click()
        
        # Verificar redirecionamento para dashboard
        wait.until(
            EC.presence_of_element_located(
                (AppiumBy.ANDROID_UIAUTOMATOR, 'text("Dashboard")')
            )
        )
    
    def test_swipe_gesture(self, driver):
        # Obter tamanho da tela
        size = driver.get_window_size()
        start_x = size['width'] * 0.5
        start_y = size['height'] * 0.8
        end_y = size['height'] * 0.2
        
        # Swipe up
        TouchAction(driver).press(start_x, start_y).move_to(0, -500).release().perform()
    
    def test_scroll_to_element(self, driver):
        # Scroll até encontrar elemento
        driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'new UiScrollable(new UiSelector()).scrollIntoView(text("Settings"))'
        )
```

### 2. Espresso - Android Nativo

**Melhor para:** Projetos Android com Kotlin/Java.

#### Dependências (build.gradle)

```groovy
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
```

#### Script Espresso (Kotlin)

```kotlin
package com.example.app

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import static androidx.test.espresso.Espresso.*
import static androidx.test.espresso.action.ViewActions.*
import static androidx.test.espresso.assertion.ViewAssertions.*
import static androidx.test.espresso.matcher.ViewMatchers.*

@RunWith(AndroidJUnit4::class)
@LargeTest
class LoginTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(LoginActivity::class.java)

    @Test
    fun testLoginWithValidCredentials() {
        // Preencher email
        onView(withId(R.id.email_input))
            .perform(typeText("usuario@test.com"), closeSoftKeyboard())
        
        // Preencher senha
        onView(withId(R.id.password_input))
            .perform(typeText("senha123"), closeSoftKeyboard())
        
        // Clicar em Login
        onView(withId(R.id.login_button))
            .perform(click())
        
        // Verificar navegação para Dashboard
        onView(withId(R.id.dashboard_title))
            .check(matches(isDisplayed()))
    }

    @Test
    fun testLoginWithInvalidCredentials() {
        onView(withId(R.id.email_input))
            .perform(typeText("invalido@test.com"))
            .perform(typeText("senhaerrada"), closeSoftKeyboard())
        
        onView(withId(R.id.login_button)).perform(click())
        
        // Verificar mensagem de erro
        onView(withId(R.id.error_message))
            .check(matches(withText("Credenciais inválidas")))
    }

    @Test
    fun testScrollThroughList() {
        // Scroll down
        onView(withId(R.id.product_list))
            .perform(
                RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
                    hasDescendant(withText("Product 50"))
                )
            )
        
        // Clicar no item
        onView(withText("Product 50"))
            .perform(click())
    }
}
```

### 3. XCTest - iOS Nativo

**Melhor para:** Projetos iOS com Swift/Objective-C.

#### Script XCTest (Swift)

```swift
import XCTest

class LoginTests: XCTestCase {
    
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launch()
    }
    
    func testLoginWithValidCredentials() {
        // Acessar campos
        let emailField = app.textFields["email_input"]
        let passwordField = app.secureTextFields["password_input"]
        let loginButton = app.buttons["login_button"]
        
        // Preencher credenciais
        emailField.tap()
        emailField.typeText("usuario@test.com")
        
        passwordField.tap()
        passwordField.typeText("senha123")
        
        // Clicar em Login
        loginButton.tap()
        
        // Verificar Dashboard
        XCTAssertTrue(
            app.staticTexts["dashboard_title"].waitForExistence(timeout: 5)
        )
    }
    
    func testPullToRefresh() {
        // Encontrar UITableView
        let tableView = app.tables["product_list"]
        
        // Pull to refresh
        let firstCell = tableView.cells.element(boundBy: 0)
        let cellCenter = firstCell.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
        let pullPoint = cellCenter.withOffset(CGVector(dx: 0, dy: -100))
        
        cellCenter.press(forDuration: 0, thenDragTo: pullPoint)
        
        // Verificar refresh indicator
        XCTAssertTrue(app.activityIndicators["refresh_indicator"].exists)
    }
    
    func testSwipeToDelete() {
        let tableView = app.tables["product_list"]
        let cell = tableView.cells.element(boundBy: 0)
        
        // Swipe left
        cell.swipeLeft()
        
        // Clicar em Delete
        app.buttons["delete_button"].tap()
        
        // Verificar confirmação
        app.alerts["Confirma exclusão?"].buttons["Excluir"].tap()
    }
}
```

---

## Testes em Dispositivos Reais

### Serviços de Cloud Testing

| Serviço | Dispositivos | Preço | Melhor Para |
|---------|-------------|-------|-------------|
| **BrowserStack** | 3000+ | $29+/mês | Cobertura completa |
| **Sauce Labs** | 1000+ | $39+/mês | Enterprise |
| **AWS Device Farm** |数百 | Sob demanda | AWS integration |
| **Firebase Test Lab** |数百 | Pay-per-use | Android focused |

### Setup BrowserStack

```python
# pytest + BrowserStack
from appium import webdriver

def get_browserstack_driver():
    desired_caps = {
        "platformName": "android",
        "deviceName": "Samsung Galaxy S23",
        "os_version": "13.0",
        "app": "bs://APP_ID",
        "browserstack.user": "USER",
        "browserstack.key": "ACCESS_KEY",
        "browserstack.debug": True,
        "browserstack.networkLogs": True
    }
    
    return webdriver.Remote(
        "http://hub-cloud.browserstack.com/wd/hub",
        desired_caps
    )
```

---

## Cenários de Teste Mobile

### 1. Testes de Interface

```python
def test_responsividade_telas():
    """Testa layouts em diferentes tamanhos de tela"""
    sizes = [
        (360, 640),  # Small phone
        (390, 844),  # iPhone 12/13
        (412, 915),  # Large phone
        (600, 1024), # Tablet portrait
    ]
    
    for width, height in sizes:
        driver.execute_script(f"setWindowSize({width}, {height})")
        
        # Verificar elementos não se sobrepõem
        assert not elements_overlap(header, footer)
        assert text_is_readable(header)
```

### 2. Testes de Conectividade

```python
def test_offline_mode():
    """Testa comportamento sem internet"""
    # Desligar rede
    driver.set_network_connection(0)  # Airplane mode
    
    # Tentar acessar conteúdo offline
    assert app.has_cached_content()
    assert app.can_show_cached_products()
    
    # Tentar ação que requer internet
    with pytest.raises(NetworkError):
        app.submit_order()
    
    # Reconectar
    driver.set_network_connection(6)  # WiFi
    
    # Sincronizar
    app.sync_pending_orders()
    assert order.was_synced()
```

### 3. Testes de Performance

```kotlin
@Test
fun testListScrollPerformance() {
    val recyclerView = espresso.onView(
        withId(R.id.product_list)
    )
    
    // Medir FPS durante scroll
    val monitor = FrameMetricsMonitor()
    
    recyclerView.perform(
        scrollToPosition(100),
        scrollToPosition(50),
        scrollToPosition(0)
    )
    
    val metrics = monitor.getMetrics()
    
    // Verificar 60 FPS (16.67ms por frame)
    assertThat(metrics.averageFrameTime).isLessThan(20.0)
    assertThat(metrics.droppedFrames).isLessThan(5)
}
```

---

## Appium + CI/CD

### GitHub Actions

```yaml
name: Mobile Tests

on: [push, pull_request]

jobs:
  android-tests:
    runs-on: macos-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          java-version: '17'
      
      - name: Setup Android SDK
        uses: android-actions/setup-android@v2
      
      - name: Cache Appium
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-appium
      
      - name: Install Dependencies
        run: |
          npm ci
          pip install pytest pytest-appium
      
      - name: Run Tests on BrowserStack
        env:
          BROWSERSTACK_USER: ${{ secrets.BS_USER }}
          BROWSERSTACK_KEY: ${{ secrets.BS_KEY }}
        run: |
          pytest tests/mobile/ \
            --device="Samsung Galaxy S23" \
            --platform="Android 13" \
            --hub="http://hub.browserstack.com/wd/hub"
```

---

## Conclusão

Testes mobile requerem estratégia bem definida cobrindo múltiplas plataformas, dispositivos e condições. As chaves para sucesso são:

1. **Automação estratégica** - Appium para cross-platform, nativo para performance
2. **Device matrix** - Priorizar por uso real
3. **CI/CD integrado** - Testes em cloud para escalabilidade
4. **Performance monitorada** - FPS, memória, bateria
5. **Testes de condições reais** - Offline, network switching

---

### FAQ

**P: Appium é mais lento que Espresso/XCTest?**  
R: Sim, mas permite código único para iOS e Android.

**P: Quantos dispositivos testar?**  
R: Foque nos top 10-20 dispositivos por mercado (iOS, Android).

**P: Como testar notificações push?**  
R: Use emuladores para automação, dispositivos reais para validação final.

**P: Testes mobile podem ser 100% automatizados?**  
R: Não. Alguns testes (usabilidade, exploratory) requerem testers humanos.
