[Разработка под iOS, Разработка мобильных приложений, Swift, Аналитика мобильных приложений] Автоматизация тестирования продуктовой аналитики в мобильных приложениях
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Тестирование всех событий продуктовой аналитики перед каждым релизом обычно отнимает много времени. Это можно автоматизировать. Показываю, как именно, на примере iOS-приложения.Вы когда-нибудь выпускали релиз, в котором случайно удалили код отправляющий некоторые важные события аналитики? Или забывали покрыть событиями новую фичу? А сколько времени ваши аналитики или тестировщики тратят на ручное тестирование перед каждым релизом? А если это приложение с тысячей событий?В этой статье расскажу, как автоматизировать тестирование продуктовой аналитики, чтобы избежать проблем и сэкономить время и деньги.Тестирование аналитики вручнуюКогда пользователь совершает действие, то событие аналитики сразу отправляется в систему аналитики. И, к сожалению, это никак не отследить, чтобы протестировать. Только если модифицировать код приложения, чтобы вместе с отправкой события сделать дополнительное действие. Какие есть варианты:
- Можно отправить локальное уведомление (типа Push) с названием и параметрами события. Это неудобно, так как перекрывает интерфейс приложения, а также сложно тестировать цепочку событий из-за того, что каждое новое уведомление перекрывает старые.
- Добавить отладочный экран, на котором показан список всех отправленных событий. Но это тоже не очень удобно — нужно постоянно переключаться между приложением и этим экраном.
- Либо события аналитики можно логировать и сразу отслеживать в консоли.
События аналитики в Console.appТретий вариант — самый удобный: его несложно сделать и он позволяет фильтровать события. Также при тестировании приложения сразу видно, какие события отправляются. И если приложение содержит небольшое количество событий, то этим вариантом можно обойтись, не прибегая к автоматизации.Это всё способы тестирования события аналитики вручную. Но если в приложении событий много, то такое тестирование будет не быстрым.Для того чтобы автоматизировать тестирование, можно воспользоваться UI-тестами. С их помощью можно переходить между экранами, совершать действия и проверять, что определенные события с указанными параметрами отправляются.Тестирование аналитики UI-тестамиУ любого события есть имя, у некоторых бывают еще и параметры. Например, у «успешность авторизации» имя authorization и булевый параметр success.Вообще, из UI-тестов нельзя узнать, какие события отправило приложение. Когда пользователь совершает действие, то они сразу попадают в систему аналитики. Но в этот момент их можно перехватить и сохранить в место, куда у UI-тестов есть доступ.На практике есть два способа передачи данных из приложения в UI-тесты:
- Можно сохранить текстовые данные в невидимое текстовое поле или в свойство accessibilityLabel невидимой «вьюшки». Но в этом случае меняется иерархия «вьюшек», и это может привести к багам. Кроме того, не получится очистить список отправленных событий из UI-тестов.
- Или можно сохранить текстовые данные в буфер обмена, к которому у UI-тестов есть доступ. Этот вариант лучше, так как иерархия «вьюшек» не изменяется. Буфер обмена можно очистить из UI-тестов, а еще это проще в реализации.
Когда приложение запущено в режиме UI-тестирования, то можно подменить сервис отправки событий аналитики. Например, вместо AppMetrica подставить свой сервис, который будет отправлять события в буфер обмена. Далее в UI-тестах происходит чтение текстовых данных из буфера, преобразование их в массив событий и проверка.Так в итоге будет выглядеть UI-тест, проверяющий события аналитики на экране авторизации:
func testLoginSuccess() {
// Запустить приложение
launchApp()
// Проверить что отправилось событие показа экрана авторизации
analytics.assertContains(name: "open_login_screen")
// Успешно залогиниться
loginScreen.login(success: true)
// Проверить что отправилось событие успешной авторизации
analytics.assertContains("authorization", ["success": true])
}
Доработки со стороны приложенияРасскажу о том, как доработать код приложения, чтобы события аналитики отправлялись и в систему аналитики, и в буфер обмена в зависимости от переданных аргументов при запуске приложения.Базовые сущностиПредставим событие аналитики в виде следующей структуры:
public struct MetricEvent: Equatable {
public let name: String
public let values: [String: AnyHashable]?
public init(name: String, values: [String: AnyHashable]? = nil) {
self.name = name
self.values = values
}
}
Структура MetricEvent будет использоваться и в коде приложения, и в коде UI-тестов. Поэтому вынесем её в отдельный модуль — MetricExampleCore. Для этого нужно создать новый Target типа Framework.
Событие что-то должно отправлять, поэтому объявим соответствующий протокол:
import MetricExampleCore
/// Сервис отправки событий в аналитику
public protocol MetricService {
func send(event: MetricEvent)
}
В первой строчке импортируем модуль, в котором объявили структуру MetricEvent.Сервисы отправки событийЭтому протоколу будут соответствовать классы, отправляющие события куда-либо. К примеру, класс для отправки событий в AppMetrica:
import Foundation
import MetricExampleCore
import YandexMobileMetrica
open class AppMetricaService: MetricService {
public init(configuration: YMMYandexMetricaConfiguration) {
YMMYandexMetrica.activate(with: configuration)
}
open func send(event: MetricEvent) {
YMMYandexMetrica.reportEvent(event.name, parameters: event.values, onFailure: nil)
}
}
В нашем случае нужен класс, который отправляет события в буфер обмена. Создаем его:
import Foundation
import MetricExampleCore
import UIKit
final class MetricServiceForUITests: MetricService {
// Массив всех отправленных событий аналитики
private var metricEvents: [MetricEvent] = []
func send(event: MetricEvent) {
guard ProcessInfo.processInfo.isUITesting,
ProcessInfo.processInfo.sendMetricsToPasteboard else {
return
}
if UIPasteboard.general.string == nil ||
UIPasteboard.general.string?.isEmpty == true {
metricEvents = []
}
metricEvents.append(event)
if let metricsString = try? encodeMetricEvents(metricEvents) {
UIPasteboard.general.string = metricsString
}
}
private func encodeMetricEvents(_ events: [MetricEvent]) throws -> String {
let arrayOfEvents: [NSDictionary] = events.map { $0.asJSONObject }
let data = try JSONSerialization.data(withJSONObject: arrayOfEvents)
return String(decoding: data, as: UTF8.self)
}
}
В методе send можно проверить, что приложение запущено в режиме UI-тестирования и разрешена отправка событий в буфер обмена. Затем в массив всех отправленных событий добавляется новое. После этого массив представляется в виде текста с использованием метода encodeMetricEvents. Там каждое событие преобразуется в словарь и полученный массив сериализуется. После этого строка сохраняется в буфер обмена.
// MetricEvent.swift
...
/// Представляет событие в виде словаря для передачи в JSONSerialization.data(withJSONObject:)
public var asJSONObject: NSDictionary {
return [
"name": name,
"values": values ?? [:]
]
}
...
Каждый UIViewController, который будет отправлять события, получит в инициализатор зависимость MetricService.
final class LoginViewController: UIViewController {
private let metricService: MetricService
init(metricService: MetricService = ServiceLayer.shared.metricService) {
self.metricService = metricService
super.init(nibName: nil, bundle: nil)
}
...
Чтобы не передавать каждый раз вручную эту зависимость, можно использовать паттерн Service Locator и создать класс ServiceLayer. В нем будет создаваться и храниться MetricService, который будет передаваться во все контроллеры.
import Foundation
import YandexMobileMetrica
final class ServiceLayer {
static let shared = ServiceLayer()
private(set) lazy var metricService: MetricService = {
if ProcessInfo.processInfo.isUITesting {
return MetricServiceForUITests()
} else {
let config = YMMYandexMetricaConfiguration(apiKey: "APP_METRICA_API_KEY")
return AppMetricaService(configuration: config)
}
}()
}
Если приложение запущено в режиме UI-тестирования, то для отправки событий используется MetricServiceForUITests. В ином случае AppMetricaService.Отправка событийОсталось объявить все события, которые будут отправляться. Для этого нужно написать расширение MetricEvent:
import Foundation
import MetricExampleCore
extension MetricEvent {
/// Пользователь перешел на экран авторизации
static var openLogin: MetricEvent {
MetricEvent(name: "open_login_screen")
}
/// Пользователь ввел логин и пароль и инициировал авторизацию.
///
/// - Parameter success: Успешность запроса.
/// - Returns: Событие метрики.
static func authorization(success: Bool) -> MetricEvent {
MetricEvent(
name: "authorization",
values: ["success": success]
)
}
}
Теперь события можно отправлять:
metricService.send(event: .openLogin)
metricService.send(event: .authorization(success: true))
metricService.send(event: .authorization(success: false))
Аргументы запускаЯ уже упоминал такие вещи, как:
ProcessInfo.processInfo.isUITesting
ProcessInfo.processInfo.sendMetricsToPasteboard
При запуске UI-тестов на аналитику будут передаваться два аргумента: --UI-TESTING и --SEND-METRICS-TO-PASTEBOARD. Первый показывает, что приложение запущено в режиме UI-тестирования. Второй — что приложению разрешено отправлять события аналитики в буфер обмена. Чтобы получить доступ к этим аргументам, нужно написать расширение для ProcessInfo:
import Foundation
extension ProcessInfo {
var isUITesting: Bool { arguments.contains("--UI-TESTING") }
var sendMetricsToPasteboard: Bool { arguments.contains("--SEND-METRICS-TO-PASTEBOARD") }
}
Доработки со стороны UI-тестовТеперь расскажу, как на стороне UI-тестов получить список отправленных событий из буфера обмена и проверить их.Получение списка отправленных событийЧтобы получить текстовые данные из буфера, используем UIPasteboard.general.string. Затем строку нужно преобразовать в массив событий (MetricEvent). В методе decodeMetricEvents строка преобразуется в объект Data и десериализуется в массив с помощью JSONSerialization:
/// Возвращает список всех событий аналитики произошедших с момента запуска приложения
func extractAnalytics() -> [MetricEvent] {
let string = UIPasteboard.general.string!
if let events = try? decodeMetricEvents(from: string) {
return events
} else {
return []
}
}
/// Преобразует строку с массивом событий в массив объектов [MetricEvent]
private func decodeMetricEvents(from string: String) throws -> [MetricEvent] {
guard !string.isEmpty else { return [] }
let data = Data(string.utf8)
guard let arrayOfEvents: [NSDictionary] = try JSONSerialization.jsonObject(with: data) as? [NSDictionary] else {
return []
}
return arrayOfEvents.compactMap { MetricEvent(from: $0) }
}
Далее массив словарей преобразуется в массив MetricEvent. Для этого у MetricEvent нужно добавить инициализатор из словаря:
/// Пытается создать объект MetricEvent из словаря
public init?(from dict: NSDictionary) {
guard let eventName = dict["name"] as? String else { return nil }
self = MetricEvent(
name: eventName,
values: dict["values"] as? [String: AnyHashable])
}
Теперь можно получить массив событий [MetricEvent] и проанализировать его.Если в процессе тестирования понадобится очистить список событий, то тут поможет:
UIPasteboard.general.string = ""
Проверки списка событийМожно написать несколько вспомогательных методов, которые будут проверять массив событий. Вот один из них: он проверяет наличие события с указанным именем.
/// Проверяет наличие события с указанным именем
/// - Parameters:
/// - name: Название события
/// - count: Количество событий с указанным именем. По умолчанию равно 1.
func assertContains(
name: String,
count: Int = 1) {
let records = extractAnalytics()
XCTAssertEqual(
records.filter { $0.name == name }.count,
count,
"Событие с именем \(name) не найдено.")
}
В итоге получился класс AnalyticsTestBase. Посмотреть его можно на GitHub — AnalyticsTestBase.swiftСоздадим класс, наследника XCTestCase, от которого будут наследоваться классы, тестирующие аналитику. Он создает класс AnalyticsTestBase для тестирования аналитики и метод launchApp, запускающий приложение.
import XCTest
class TestCaseBase: XCTestCase {
var app: XCUIApplication!
var analytics: AnalyticsTestBase!
override func setUp() {
super.setUp()
app = XCUIApplication()
analytics = AnalyticsTestBase(app: app)
}
/// Запускает приложение для UI-тестирования с указанными параметрами.
func launchApp(with parameters: AppLaunchParameters = AppLaunchParameters()) {
app.launchArguments = parameters.launchArguments
app.launch()
}
}
Метод будет принимать AppLaunchParameters (параметры запуска приложения, о которых я говорил выше).
struct AppLaunchParameters {
/// Отправлять аналитику в UIPasteboard
private let sendMetricsToPasteboard: Bool
init(sendMetricsToPasteboard: Bool = false) {
self.sendMetricsToPasteboard = sendMetricsToPasteboard
}
var launchArguments: [String] {
var arguments = ["--UI-TESTING"]
if sendMetricsToPasteboard {
arguments.append("--SEND-METRICS-TO-PASTEBOARD")
}
return arguments
}
}
В обычных UI-тестах приложение будет запускаться с параметрами:
AppLaunchParameters(sendMetricsToPasteboard: false)
А в UI-тестах на аналитику:
AppLaunchParameters(sendMetricsToPasteboard: true)
Теперь можно писать тесты на аналитику. Например, это тест на экран входа:
final class LoginAnalyticsTests: TestCaseBase {
private let loginScreen = LoginScreen()
func testLoginSuccess() {
launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true))
// Проверить что отправилось событие показа экрана входа
analytics.assertContains(name: "open_login_screen")
// Успешно залогинится
loginScreen.login(success: true)
// Проверить что отправилось событие успешной авторизации
analytics.assertContains("authorization", ["success": true])
}
}
LoginScreen — это Page Object, описывающий экран авторизации. Посмотреть его можно на GitHub — LoginScreen.swiftПримерыExample проектiOS-проект, где используется автоматизированное тестирование аналитики UI-тестами. Это простое приложение, состоящее из двух экранов: вход и меню. События отправляются при заходе на каждый экран, при авторизации и при выборе пункта меню.
Тест, покрывающий все эти события:
import XCTest
final class AnalyticsTests: TestCaseBase {
private let loginScreen = LoginScreen()
private let menuScreen = MenuScreen()
// MARK: - Login
func testLoginSuccess() {
launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true))
analytics.assertContains(name: "open_login_screen")
loginScreen.login(success: true)
analytics.assertContains("authorization", ["success": true])
}
func testLoginFailed() {
launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true))
analytics.assertContains(name: "open_login_screen")
loginScreen.login(success: false)
analytics.assertContains("authorization", ["success": false])
}
// MARK: - Menu
func testOpenMenu() {
launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true))
loginScreen.login(success: true)
waitForElement(menuScreen.title)
analytics.assertContains(name: "open_menu_screen")
}
func testMenuSelection() {
launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true))
loginScreen.login(success: true)
waitForElement(menuScreen.title)
menuScreen.profileCell.tap()
analytics.assertContains("menu_item_selected", ["name": "Профиль"])
menuScreen.messagesCell.tap()
analytics.assertContains("menu_item_selected", ["name": "Сообщения"])
}
}
Реальный проектПример UI-тестов на аналитику экрана авторизации из реального проекта — LoginAnalyticsTests.swiftПример, как мне, разработчику, помогли UI-тесты на аналитику. На одном проекте нужно было произвести рефакторинг и редизайн главного экрана приложения. Экран был сложным, с большим количеством событий аналитики. На тот момент в проекте я уже настроил тесты. После рефакторинга и редизайна запустил тесты и обнаружил, что некоторые события случайно удалил. Если бы не тесты на аналитику, эти события не попали бы в релиз.ИтогиПлюсы подхода:
- Продуктовому аналитику или тестировщику не нужно проверять все события аналитики вручную. А это экономия времени и, соответственно, денег.
- Если у вас настроен CI, то UI-тесты на аналитику можно запускать по расписанию, например, раз в неделю или по команде из Slack.
Есть и минусы:
- UI-тесты выполняются относительно долго. Имеет смысл запускать их только в процессе регрессионного тестирования перед каждым релизом.
- UI-тесты на аналитику смогут написать только те тестировщики, которые имеют опыт написания нативных UI-тестов.
В случае ручного тестирования, при добавлении новых событий, нужно линейно больше времени на тестирование. Автоматизированное тестирования быстрее, но для него нужно подготовить инфраструктуру: это займет некоторое время. Но после этого добавление теста на новое событие будет проходить быстрее.Поэтому в случае большого проекта есть смысл автоматизировать проверку событий аналитики.
===========
Источник:
habr.com
===========
Похожие новости:
- [] Что такое дерево решений и где его используют?
- [Компьютерное железо, Процессоры] Производители материнских плат выложили обновления прошивки BIOS под процессоры Ryzen 5000-й серии
- [Тестирование IT-систем, Программирование, Виртуализация, TDD] Язык тестовых сценариев Testo Lang: простая автоматизация сложных тестов
- [Разработка мобильных приложений, Проектирование и рефакторинг, Dart, Flutter] Flutter + чистая архитектура: разбираем на примере
- [Веб-аналитика, Интернет-маркетинг, Контекстная реклама] Удобство Scatterplot для статистики ключевых слов
- [IT-компании, Конференции, Тестирование IT-систем] Приглашаем на QA Meeting Point
- [Программирование] Интеграция библиотеки на Swift в UE4
- [Монетизация мобильных приложений, Платежные системы, Разработка мобильных приложений, Разработка под Android] Таргетирование уведомлений, управление ценами в разных регионах и другие возможности HMS для интернет-платежей
- [Тестирование IT-систем, Тестирование веб-сервисов, Тестирование мобильных приложений, Тестирование игр] Тестирование со всех сторон: о чём расскажут на Heisenbug
- [Информационная безопасность, Разработка под iOS, Системы обмена сообщениями] Apple потребовала от Telegram заблокировать три белорусских канала
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_swift, #_analitika_mobilnyh_prilozhenij (Аналитика мобильных приложений), #_ios, #_testirovanie (тестирование), #_avtomatizatsija (автоматизация), #_uitesty (ui-тесты), #_analitika (аналитика), #_redmadrobot, #_mobilnoe_prilozhenie (мобильное приложение), #_uitestirovanie (ui-тестирование), #_analitika_mobilnyh_prilozhenij (аналитика мобильных приложений), #_blog_kompanii_redmadrobot (
Блог компании Redmadrobot
), #_razrabotka_pod_ios (
Разработка под iOS
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_swift, #_analitika_mobilnyh_prilozhenij (
Аналитика мобильных приложений
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:50
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Тестирование всех событий продуктовой аналитики перед каждым релизом обычно отнимает много времени. Это можно автоматизировать. Показываю, как именно, на примере iOS-приложения.Вы когда-нибудь выпускали релиз, в котором случайно удалили код отправляющий некоторые важные события аналитики? Или забывали покрыть событиями новую фичу? А сколько времени ваши аналитики или тестировщики тратят на ручное тестирование перед каждым релизом? А если это приложение с тысячей событий?В этой статье расскажу, как автоматизировать тестирование продуктовой аналитики, чтобы избежать проблем и сэкономить время и деньги.Тестирование аналитики вручнуюКогда пользователь совершает действие, то событие аналитики сразу отправляется в систему аналитики. И, к сожалению, это никак не отследить, чтобы протестировать. Только если модифицировать код приложения, чтобы вместе с отправкой события сделать дополнительное действие. Какие есть варианты:
События аналитики в Console.appТретий вариант — самый удобный: его несложно сделать и он позволяет фильтровать события. Также при тестировании приложения сразу видно, какие события отправляются. И если приложение содержит небольшое количество событий, то этим вариантом можно обойтись, не прибегая к автоматизации.Это всё способы тестирования события аналитики вручную. Но если в приложении событий много, то такое тестирование будет не быстрым.Для того чтобы автоматизировать тестирование, можно воспользоваться UI-тестами. С их помощью можно переходить между экранами, совершать действия и проверять, что определенные события с указанными параметрами отправляются.Тестирование аналитики UI-тестамиУ любого события есть имя, у некоторых бывают еще и параметры. Например, у «успешность авторизации» имя authorization и булевый параметр success.Вообще, из UI-тестов нельзя узнать, какие события отправило приложение. Когда пользователь совершает действие, то они сразу попадают в систему аналитики. Но в этот момент их можно перехватить и сохранить в место, куда у UI-тестов есть доступ.На практике есть два способа передачи данных из приложения в UI-тесты:
func testLoginSuccess() {
// Запустить приложение launchApp() // Проверить что отправилось событие показа экрана авторизации analytics.assertContains(name: "open_login_screen") // Успешно залогиниться loginScreen.login(success: true) // Проверить что отправилось событие успешной авторизации analytics.assertContains("authorization", ["success": true]) } public struct MetricEvent: Equatable {
public let name: String public let values: [String: AnyHashable]? public init(name: String, values: [String: AnyHashable]? = nil) { self.name = name self.values = values } } Событие что-то должно отправлять, поэтому объявим соответствующий протокол: import MetricExampleCore
/// Сервис отправки событий в аналитику public protocol MetricService { func send(event: MetricEvent) } import Foundation
import MetricExampleCore import YandexMobileMetrica open class AppMetricaService: MetricService { public init(configuration: YMMYandexMetricaConfiguration) { YMMYandexMetrica.activate(with: configuration) } open func send(event: MetricEvent) { YMMYandexMetrica.reportEvent(event.name, parameters: event.values, onFailure: nil) } } import Foundation
import MetricExampleCore import UIKit final class MetricServiceForUITests: MetricService { // Массив всех отправленных событий аналитики private var metricEvents: [MetricEvent] = [] func send(event: MetricEvent) { guard ProcessInfo.processInfo.isUITesting, ProcessInfo.processInfo.sendMetricsToPasteboard else { return } if UIPasteboard.general.string == nil || UIPasteboard.general.string?.isEmpty == true { metricEvents = [] } metricEvents.append(event) if let metricsString = try? encodeMetricEvents(metricEvents) { UIPasteboard.general.string = metricsString } } private func encodeMetricEvents(_ events: [MetricEvent]) throws -> String { let arrayOfEvents: [NSDictionary] = events.map { $0.asJSONObject } let data = try JSONSerialization.data(withJSONObject: arrayOfEvents) return String(decoding: data, as: UTF8.self) } } // MetricEvent.swift
... /// Представляет событие в виде словаря для передачи в JSONSerialization.data(withJSONObject:) public var asJSONObject: NSDictionary { return [ "name": name, "values": values ?? [:] ] } ... final class LoginViewController: UIViewController {
private let metricService: MetricService init(metricService: MetricService = ServiceLayer.shared.metricService) { self.metricService = metricService super.init(nibName: nil, bundle: nil) } ... import Foundation
import YandexMobileMetrica final class ServiceLayer { static let shared = ServiceLayer() private(set) lazy var metricService: MetricService = { if ProcessInfo.processInfo.isUITesting { return MetricServiceForUITests() } else { let config = YMMYandexMetricaConfiguration(apiKey: "APP_METRICA_API_KEY") return AppMetricaService(configuration: config) } }() } import Foundation
import MetricExampleCore extension MetricEvent { /// Пользователь перешел на экран авторизации static var openLogin: MetricEvent { MetricEvent(name: "open_login_screen") } /// Пользователь ввел логин и пароль и инициировал авторизацию. /// /// - Parameter success: Успешность запроса. /// - Returns: Событие метрики. static func authorization(success: Bool) -> MetricEvent { MetricEvent( name: "authorization", values: ["success": success] ) } } metricService.send(event: .openLogin)
metricService.send(event: .authorization(success: true)) metricService.send(event: .authorization(success: false)) ProcessInfo.processInfo.isUITesting
ProcessInfo.processInfo.sendMetricsToPasteboard import Foundation
extension ProcessInfo { var isUITesting: Bool { arguments.contains("--UI-TESTING") } var sendMetricsToPasteboard: Bool { arguments.contains("--SEND-METRICS-TO-PASTEBOARD") } } /// Возвращает список всех событий аналитики произошедших с момента запуска приложения
func extractAnalytics() -> [MetricEvent] { let string = UIPasteboard.general.string! if let events = try? decodeMetricEvents(from: string) { return events } else { return [] } } /// Преобразует строку с массивом событий в массив объектов [MetricEvent] private func decodeMetricEvents(from string: String) throws -> [MetricEvent] { guard !string.isEmpty else { return [] } let data = Data(string.utf8) guard let arrayOfEvents: [NSDictionary] = try JSONSerialization.jsonObject(with: data) as? [NSDictionary] else { return [] } return arrayOfEvents.compactMap { MetricEvent(from: $0) } } /// Пытается создать объект MetricEvent из словаря
public init?(from dict: NSDictionary) { guard let eventName = dict["name"] as? String else { return nil } self = MetricEvent( name: eventName, values: dict["values"] as? [String: AnyHashable]) } UIPasteboard.general.string = ""
/// Проверяет наличие события с указанным именем
/// - Parameters: /// - name: Название события /// - count: Количество событий с указанным именем. По умолчанию равно 1. func assertContains( name: String, count: Int = 1) { let records = extractAnalytics() XCTAssertEqual( records.filter { $0.name == name }.count, count, "Событие с именем \(name) не найдено.") } import XCTest
class TestCaseBase: XCTestCase { var app: XCUIApplication! var analytics: AnalyticsTestBase! override func setUp() { super.setUp() app = XCUIApplication() analytics = AnalyticsTestBase(app: app) } /// Запускает приложение для UI-тестирования с указанными параметрами. func launchApp(with parameters: AppLaunchParameters = AppLaunchParameters()) { app.launchArguments = parameters.launchArguments app.launch() } } struct AppLaunchParameters {
/// Отправлять аналитику в UIPasteboard private let sendMetricsToPasteboard: Bool init(sendMetricsToPasteboard: Bool = false) { self.sendMetricsToPasteboard = sendMetricsToPasteboard } var launchArguments: [String] { var arguments = ["--UI-TESTING"] if sendMetricsToPasteboard { arguments.append("--SEND-METRICS-TO-PASTEBOARD") } return arguments } } AppLaunchParameters(sendMetricsToPasteboard: false)
AppLaunchParameters(sendMetricsToPasteboard: true)
final class LoginAnalyticsTests: TestCaseBase {
private let loginScreen = LoginScreen() func testLoginSuccess() { launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true)) // Проверить что отправилось событие показа экрана входа analytics.assertContains(name: "open_login_screen") // Успешно залогинится loginScreen.login(success: true) // Проверить что отправилось событие успешной авторизации analytics.assertContains("authorization", ["success": true]) } } Тест, покрывающий все эти события: import XCTest
final class AnalyticsTests: TestCaseBase { private let loginScreen = LoginScreen() private let menuScreen = MenuScreen() // MARK: - Login func testLoginSuccess() { launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true)) analytics.assertContains(name: "open_login_screen") loginScreen.login(success: true) analytics.assertContains("authorization", ["success": true]) } func testLoginFailed() { launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true)) analytics.assertContains(name: "open_login_screen") loginScreen.login(success: false) analytics.assertContains("authorization", ["success": false]) } // MARK: - Menu func testOpenMenu() { launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true)) loginScreen.login(success: true) waitForElement(menuScreen.title) analytics.assertContains(name: "open_menu_screen") } func testMenuSelection() { launchApp(with: AppLaunchParameters(sendMetricsToPasteboard: true)) loginScreen.login(success: true) waitForElement(menuScreen.title) menuScreen.profileCell.tap() analytics.assertContains("menu_item_selected", ["name": "Профиль"]) menuScreen.messagesCell.tap() analytics.assertContains("menu_item_selected", ["name": "Сообщения"]) } }
=========== Источник: habr.com =========== Похожие новости:
Блог компании Redmadrobot ), #_razrabotka_pod_ios ( Разработка под iOS ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_swift, #_analitika_mobilnyh_prilozhenij ( Аналитика мобильных приложений ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:50
Часовой пояс: UTC + 5