Modern QA2026Тестирование голосовых интерфейсов и функций на основе камеры
Join

Course09 Mobile & Cross-Platform Testing

Cutting-edge · Chapter 09

Тестирование голосовых интерфейсов и функций на основе камеры

Updated Jul 2026

Тестирование голосовых интерфейсов

Голосовые функции (Siri Shortcuts, действия Google Assistant, голосовые команды в приложении) требуют иного подхода к тестированию. Вы тестируете не визуальный интерфейс — вы тестируете конвейер понимания естественного языка (NLU), который преобразует произнесённые слова в структурированные интенты и сущности.

Основная проблема: преобразование речи в текст порождает вариативные транскрипции. Одна и та же произнесённая фраза может быть транскрибирована по-разному в зависимости от акцента, фонового шума, качества микрофона и версии модели распознавания речи. Ваше приложение должно обрабатывать эту вариативность.

Тестирование обработки голосовых команд

Тестирование NLU-слоя

# Testing voice command processing (the recognition part)
def test_voice_command_parsing():
    """Test that the NLU layer correctly parses voice commands."""
    test_cases = [
        {
            "transcript": "show me my orders from last week",
            "expected_intent": "view_orders",
            "expected_entities": {"time_range": "last_week"}
        },
        {
            "transcript": "cancel order number twelve thirty four",
            "transcript_variants": [
                "cancel order number 1234",
                "cancel order #1234",
            ],
            "expected_intent": "cancel_order",
            "expected_entities": {"order_id": "1234"}
        },
        {
            "transcript": "how much is shipping to new york",
            "expected_intent": "shipping_estimate",
            "expected_entities": {"destination": "new_york"}
        },
        {
            "transcript": "add two widgets to my cart",
            "transcript_variants": [
                "add 2 widgets to my cart",
                "at two widgets to my cart",  # Common misrecognition
            ],
            "expected_intent": "add_to_cart",
            "expected_entities": {"quantity": 2, "product": "widgets"}
        },
    ]

    for case in test_cases:
        # Test primary transcript
        result = parse_voice_command(case["transcript"])
        assert result.intent == case["expected_intent"], \
            f"Intent mismatch for '{case['transcript']}': expected {case['expected_intent']}, got {result.intent}"
        for key, value in case["expected_entities"].items():
            assert result.entities[key] == value

        # Test transcript variants (speech-to-text produces different outputs)
        for variant in case.get("transcript_variants", []):
            result = parse_voice_command(variant)
            assert result.intent == case["expected_intent"], \
                f"Variant '{variant}' produced wrong intent: {result.intent}"

Неоднозначность и обработка ошибок

def test_ambiguous_voice_commands():
    """Test handling of ambiguous or incomplete commands."""
    ambiguous_cases = [
        {
            "transcript": "order",
            "expected": "clarification_needed",
            "description": "Too vague -- could be view, create, or cancel"
        },
        {
            "transcript": "cancel",
            "expected": "clarification_needed",
            "description": "Missing order ID"
        },
        {
            "transcript": "",
            "expected": "no_input",
            "description": "Empty transcript (silence)"
        },
        {
            "transcript": "asdfghjkl",
            "expected": "unrecognized",
            "description": "Nonsensical input"
        },
    ]

    for case in ambiguous_cases:
        result = parse_voice_command(case["transcript"])
        assert result.intent == case["expected"], \
            f"'{case['transcript']}' ({case['description']}): expected {case['expected']}, got {result.intent}"

def test_voice_command_confidence_threshold():
    """Commands below confidence threshold should trigger confirmation."""
    # Low confidence should prompt "Did you mean...?"
    result = parse_voice_command("cancel order maybe twelve thirty four ish")
    if result.confidence < 0.7:
        assert result.needs_confirmation is True

Сквозное тестирование голосового потока

def test_voice_to_action_flow(driver):
    """Test the complete voice command flow: mic -> speech -> action."""
    # Navigate to voice-enabled screen
    driver.find_element(AppiumBy.ACCESSIBILITY_ID, "voice-command-btn").click()

    # On emulators, inject pre-recorded audio
    driver.execute_script("mobile: shell", {
        "command": "am",
        "args": ["broadcast", "-a", "com.testapp.INJECT_AUDIO",
                 "--es", "audio_path", "/sdcard/test_audio/cancel_order.wav"]
    })

    # Verify the app processed the command
    confirmation = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "voice-confirmation")
    assert "cancel" in confirmation.text.lower()
    assert "order" in confirmation.text.lower()

Тестирование функций на основе камеры

AR-функции, сканеры штрих-кодов, захват документов и детекция лиц требуют камеру, что делает их сложными для тестирования в автоматизированных пайплайнах.

Тестирование сканеров штрих-кодов

# Testing barcode scanner with synthetic camera input
def test_barcode_scanner(driver):
    """Test barcode scanning using a pre-recorded camera feed."""

    # Appium can inject camera images on supported devices
    driver.push_file(
        "/sdcard/DCIM/test_barcode.png",
        source_path="test_data/barcode_ean13.png"
    )

    # Navigate to scanner
    driver.find_element(AppiumBy.ACCESSIBILITY_ID, "scan-barcode-btn").click()

    # On emulators, use a virtual camera scene
    driver.execute_script("mobile: shell", {
        "command": "am",
        "args": ["broadcast", "-a", "com.testapp.INJECT_CAMERA_FRAME",
                 "--es", "image_path", "/sdcard/DCIM/test_barcode.png"]
    })

    # Verify barcode was decoded
    result = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "scan-result")
    assert result.text == "4006381333931"  # Expected EAN-13

def test_barcode_scanner_with_various_formats(driver):
    """Test that the scanner handles multiple barcode formats."""
    test_barcodes = [
        {"file": "barcode_ean13.png", "expected": "4006381333931", "format": "EAN-13"},
        {"file": "barcode_qr.png", "expected": "https://example.com/product/123", "format": "QR"},
        {"file": "barcode_code128.png", "expected": "ABC-12345", "format": "Code 128"},
        {"file": "barcode_upc.png", "expected": "042100005264", "format": "UPC-A"},
    ]

    for barcode in test_barcodes:
        driver.push_file(
            "/sdcard/DCIM/test_barcode.png",
            source_path=f"test_data/{barcode['file']}"
        )

        driver.find_element(AppiumBy.ACCESSIBILITY_ID, "scan-barcode-btn").click()
        driver.execute_script("mobile: shell", {
            "command": "am",
            "args": ["broadcast", "-a", "com.testapp.INJECT_CAMERA_FRAME",
                     "--es", "image_path", "/sdcard/DCIM/test_barcode.png"]
        })

        result = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "scan-result")
        assert result.text == barcode["expected"], \
            f"{barcode['format']} barcode: expected {barcode['expected']}, got {result.text}"

        # Go back for next test
        driver.back()

Тестирование AR-функций

def test_ar_furniture_placement(driver):
    """Test AR furniture placement feature with synthetic camera."""

    # Inject a synthetic room scene
    driver.push_file(
        "/sdcard/DCIM/ar_test_room.jpg",
        source_path="test_data/ar_room_scene.jpg"
    )

    # Navigate to AR viewer
    driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ar-viewer-btn").click()

    # Select a furniture item
    driver.find_element(AppiumBy.ACCESSIBILITY_ID, "furniture-sofa").click()

    # Verify AR session started
    ar_canvas = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ar-canvas")
    assert ar_canvas.is_displayed()

    # Verify placement controls are available
    rotate_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ar-rotate")
    scale_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "ar-scale")
    assert rotate_btn.is_displayed()
    assert scale_btn.is_displayed()

def test_camera_permission_denied_gracefully(driver):
    """App should handle camera permission denial without crashing."""
    # Deny camera permission
    driver.find_element(AppiumBy.ACCESSIBILITY_ID, "scan-barcode-btn").click()

    # Dismiss permission dialog with "Deny"
    driver.find_element(
        AppiumBy.ID, "com.android.permissioncontroller:id/permission_deny_button"
    ).click()

    # App should show a friendly message, not crash
    error_msg = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "camera-denied-message")
    assert error_msg.is_displayed()
    assert "camera" in error_msg.text.lower()

Стратегии тестирования камеры/голоса в CI

Функция Тестирование на эмуляторе Тестирование на реальном устройстве Подход
Голосовые команды Инъекция аудиофайлов Использование микрофона устройства Тестирование NLU-слоя в юнит-тестах, инъекция аудио в интеграционных
Сканирование штрих-кодов Инъекция изображений через Camera API Представление штрих-кода физической камере Использование виртуальной камеры на эмуляторах
AR-функции Ограниченно (нет датчиков глубины) Обязательно для настоящего AR Тестирование интеграции AR SDK, мок AR-сессии
Детекция лиц Инъекция изображений лиц Нужна реальная камера Использование предобработанных изображений для тестирования ML-модели
Захват документов Инъекция изображений документов Реальная камера для тестирования качества Отдельное тестирование OCR-конвейера от захвата

Общий паттерн: разделяйте механизм захвата (камера, микрофон) и логику обработки (декодирование штрих-кодов, преобразование речи в текст, ML-инференс). Юнит-тестируйте логику обработки с известными входными данными. Интеграционно тестируйте конвейер захвата на реальных устройствах в облачных фермах устройств.