Як автоматизувати тестування в Salesforce

Всім привіт! Мене звати Юра, і ось уже майже два роки я працюю Automation QA Engineer в компанії Customertimes.

За цей час мав шанс побувати на декількох Salesforce проектах й запровадити автоматизацію тестування. Починали ми з вибору інструментів та оцінки тестів, а в результаті отримували скрипти, які можна запустити одним кліком на СІ і отримати репорт про поточний стан системи, що тестується. Кожен із проектів мав свою специфіку, але це були великі проекти для компаній-лідерів у медичній сфері та роздрібній торгівлі. Я на них виконував роль ліда автоматизації або сіньйор інженера, який приймав більшість рішень щодо вибору технологій чи підходів.

Salesforce — найпопулярніша CRM система, що стрімко розвивається. Про платформу та її можливості ґрунтовно написано у цій статті, я ж спробую охарактеризувати типовий Salesforce проект з боку Automation QA Engineer, а також описати декілька рішень, які у нас виявились вдалими.

Стаття буде корисною для Automation QA, які розглядають можливість перейти на Salesforce проект, для тих, хто уже на такому проекті працює, а також усім, хто цікавиться Salesforce та автоматизацією тестування.

Великі проекти з багатьма користувачами

Salesforce частіше використовують на середніх та великих проектах. Великі проекти специфічні тим, що моделюють систему, в якій присутні користувачі різних типів. І вони можуть користуватись різними додатками: mobile, web, або ж Salesforce web UI.

Взаємодія користувачів з Salesforce екосистемою у гіпотетичній медичній компанії

Такі великі проекти зазвичай розбиваються на підпроекти, і команда автоматизаторів працює з частиною функціоналу. У mobile підпроекті, як правило, достатньо взаємодії з мобільним додатком — для відтворення роботи користувача, а також взаємодії з API.

Інколи виникає потреба покривати комплексні end-to-end флоу, що охоплюють декількох користувачів, які по-різному взаємодіють із системою. Наприклад, підписання контракту потребує дій від декількох учасників. Такі довгі тести можна розбивати, більше використовувати API та моки, але на практиці це може ставити під ризик валідність скриптів після розбиття, а також нести в собі громіздкі та трудомісткі для реалізації і підтримки задачі.

Відповідно, тестовий фреймворк має бути достатньо гнучким, щоб підтримувати такі сценарії. Це виливається в потребу працювати з декількома об’єктами драйвера одночасно, можливо, mobile та web, і оперувати ними в одному тесті.

Я мав змогу випробувати декілька підходів, щоб покрити таку специфіку. Як найпростіший спосіб — можна зберігати різні драйвери в різних змінних (варто ще враховувати, що тести можуть запускатись у кілька паралельних потоків). На одному з проектів мені випала нагода використати Screenplay pattern, який добре лягає на специфіку багатьох користувачів системи, але врешті ми віддали перевагу класичним Page Object, аби уникнути ризиків того, що Screenplay підвищить рівень входження у фреймворк і зробить онбординг на проект складнішим.

Добре себе показало рішення мати перелічуваний тип AppUser, який містить дані про користувача: юзернейм, пароль, а також Appium URL та Capabilities мобільного девайса. Елемент може мати такий вигляд:

SALES_REP("Sales Rep") {
        @Override
        public String getUsername() {
            return ConfigReader.getRunParameter(SALES_REP_USERNAME);
        }
 
        @Override
        public String getPassword() {
            return ConfigReader.getRunParameter(SALES_REP_PASSWORD);
        }
 
        @Override
        public MutableCapabilities getAppiumCapabilities() {
            MutableCapabilities capabilities = getCommonCapabilities();
            capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, ConfigReader.getRunParameter(SALES_REP_DEVICE_NAME));
            capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, ConfigReader.getRunParameter(SALES_REP_PLATFORM_VERSION));
            return capabilities;
        }
 
        @Override
        public URL getAppiumUrl() {
            try {
                return new URL(ConfigReader.getRunParameter(RunParameters.SALES_REP_APPIUM_URL));
            } catch (MalformedURLException e) {
                throw new IllegalStateException();
            }
        }
    }

Такий елемент містить інформацію, необхідну для створення об’єкта mobile драйвера. Якщо у тесті задіяні кілька користувачів одночасно, то ми створюємо для кожного об’єкт драйвера і зберігаємо в ThreadLocal мапу користувач->драйвер:

private static ThreadLocal<HashMap<AppUser, IOSDriver>> mobileDrivers = ThreadLocal.withInitial(HashMap::new);

Таким чином ми можемо за потреби перемикатись між користувачами.

Інфраструктура для запуску тестів

Щоб прогнати тести, нам потрібна інфраструктура. Безпосередній запуск може здійснюватися із агента СІ або локальної машини.

Також потрібне середовище, де ми зможемо запустити мобільний додаток. Для прогону тестів на СІ необхідна ферма девайсів або ж хмарний сервіс, який ці девайси забезпечить. Локально можна запускати на iOS симуляторі чи Android емуляторі.

Для тестування web UI необхідний екземпляр браузера. У цьому випадку можна налаштувати Selenium Hub чи Selenoid або ж використовувати готові сервіси. Якщо ми проганяємо тести локально, то простим рішенням буде використати браузер, встановлений на робочу машину.

Запуск тестів, де використовуються API, mobile та web UI

У нас був випадок, коли тестувався здебільшого мобільний додаток, а через web треба було іноді робити мінорні дії. У такому разі хорошим варіантом буде використання headless браузера або ж використання браузера на мобільному девайсі, що спростить інфраструктуру запуску тестів.

Взаємодія з API

З погляду розробки Salesforce проекти специфічні тим, що кодингу бекенду практично немає як такого, а майже вся логіка налаштовується через адмінку. Потреба писати APEX код все ще залишається, але для специфічних випадків. Водночас розвиваються Salesforce фреймворки для UI.

В автотестах ми симулюємо роботу кінцевого користувача і взаємодіємо переважно з mobile або web UI. Особливості реалізації нам практично не відчутні. При взаємодії з API ми оперуємо так званими SObject — внутрішніми об’єктами Salesforce.

REST API

Часто виконання тесту, мануального чи автоматичного, потребує попередньої підготовки даних. Наприклад, якщо у нас є тест на видалення об’єкта, і ми прагнемо до атомарності і незалежності скриптів, то цей об’єкт можна створити через API, оптимізувавши таким чином час виконання тесту. Такий підхід можна вважати best practice не залежно від технологічного стеку проекту. Це ж працює і для Salesforce, його REST API дозволяє створювати тестові дані.

Взаємодіяти з REST API можна звичними інструментами, як RestAssured з коду чи Postman для дебагу. Створення об’єкта Account утилітою curl має такий вигляд:

curl https://yourInstance.salesforce.com/services/data/v51.0/sobjects/Account/ -H "Authorization: Bearer token" -H "Content-Type: application/json" -d "@newaccount.json"

У простому випадку тіло запиту може містити інформацію про єдине обов’язкове поле — Name:

{
  "Name" : "Express Logistics and Transport"
}

Якщо запит був успішний, ми отримаємо таку відповідь:

{
  "id" : "001D000000IqhSLIAZ",
  "errors" : [ ],
  "success" : true
}

Тобто при роботі з REST API в Salesforce можна використовувати стандартні інструменти і підходи. Щоб створити об’єкт Account, ми надсилаємо POST запит на ендпоінт sobjects/Account. Більше інформації про REST API можна знайти за посиланням.

Metadata API та Tooling API

Metadata API дозволяє отримувати інформацію, створювати чи змінювати не самі дані, а типи даних. Використовується, наприклад, для міграції на нову Salesforce оргу чи перенесення змін у типах даних. Не так часто доводилось використовувати це API, але деякі його можливості можуть бути корисними для автотестів. Наприклад, поле об’єкта може приймати перелічуваний тип даних, або Picklist за термінологією Salesforce. Допустимі значення ми не можемо отримати звичайним REST API, а Metadata API дає нам таку можливість.

Tooling API — ще один потужний інструмент із широким спектром можливостей, за допомогою якого можна, зокрема, виконувати Apex код. В системі, яку ми тестуємо, може бути Apex Scheduler, який, наприклад, один раз в день перевіряє, у кого з клієнтів наближається день народження і створює менеджеру задачу про привітання. Tooling API дозволяє явно викликати Apex Scheduler, не чекаючи запланованого часу. За допомогою утиліти curl викликати Apex код можна таким чином:

curl --include --request GET \
--header "Authorization: OAuth token" \
"http://yourInstance.salesforce.com/services/data/v51.0/tooling/executeAnonymous/? anonymousBody=BirthdayTaskCreatorSchedule.execute(null);"

WSC Force.com

Salesforce надає зручного клієнта, який дозволяє взаємодіяти з його API з коду, а не через HTTP запити. Web Service Connector працює поверх SOAP API і може використовуватись як альтернатива REST API. Створення PartnerConnection виглядає так:

ConnectorConfig config = new ConnectorConfig();
config.setUsername("username");
config.setPassword("password");
PartnerConnection connection = Connector.newConnection(config);

Використовувати WSC можна в двох режимах.

1. Згенерувати бібліотеку на основі WSDL файлу орги.

Вона міститиме набір класів, котрі відповідають об’єктам на Salesforce бекенді, так званим SObject. Кожне поле можна прочитати за допомогою геттера і встановити за допомогою сеттера. Створення об’єкта Account виглядатиме так:

Account account = new Account();
account.setName("Express Logistics and Transport");
Account[] records = new Account[] {account};
connection.create(records);

На перший погляд дуже зручно, однак на практиці виникають деякі труднощі. Бібліотеку на основі WSDL треба генерувати щоразу, коли змінюються типи даних на бекенді. Також для комплексних об’єктів набір сеттерів може бути досить великим і містити поля зі схожими назвами. Наприклад, Date і EventDate — одне може проставлятись автоматично на основі другого. Може бути не очевидно, в яке поле треба записувати значення. І виникає потреба створювати поверх таких автогенерованих SObject ще один рівень абстракції з класами-обгортками зі зрозумілими назвами полів та геттерів і сеттерів. У результаті втрачається вигода від автогенерації, бо обгортки все одно треба писати вручну.

2. Явно прописувати поле, яке треба встановити.

Генерування бібліотеки на великому проекті виявилось нетривіальною задачею. Спочатку зависав браузер на викачці WSDL файлу, а потім генерування самої бібліотеки. Були думки вручну редагувати WSDL, але не випало шансу їх реалізувати, оскільки ми вирішили змінити підхід.

SObject account = new SObject();
account.setType("Account");
account.setField("Name", "Express Logistics and Transport");
connection.create(new SObject[]{account});

Такий підхід дозволив нам спростити роботу з API та зменшити кількість коду, необхідного для опису бекенд-об’єктів.

SOQL

У Salesforce немає можливості надсилати запити напряму в базу даних, однак є гнучкий спосіб робити вибірки даних через API за допомогою Salesforce Object Query Language (SOQL). Синтаксис схожий на SQL, але є деякі обмеження. Зокрема, відсутній JOIN оператор та не за всіма полями можна робити фільтрування. Також немає можливості вибрати всі поля символом «*», тож їх треба вказувати явно. SOQL запити виконуються через API, і не треба турбуватись про драйвер БД чи інші низькорівневі речі. У випадку з REST API простий запит матиме такий вигляд:

curl https://yourInstance.salesforce.com/services/data/v51.0/query/?q=SELECT+name+from+Account -H "Authorization: Bearer token"

Аналогічний запит у WSC виглядатиме так:

connection.query("SELECT Name from Account")

Взаємодія з UI

Тестування mobile клієнтів, котрі мають Salesforce на бекенді, не вимагає майже нічого специфічного порівняно з роботою зі звичайними mobile додатками. Додатки можуть реалізовуватись за допомогою різних технологій. Мені довелось попрацювати з native Android та iOS, а також з гібридним додатком, написаним на Ionic. Специфіка Salesforce може бути в тому, що дані не одразу йдуть на бекенд, а для цього треба зробити синхронізацію. Однак на безпосередню взаємодію з mobile скрінами це ніяк не впливає.

Web UI у Salesforce може працювати у двох форматах: застарілий Visualforce та актуальний Lightning. Після логіну можна переключатися між ними. Іноді можуть траплятись ситуації, коли один функціонал працює тільки на Visualforce, а інший — тільки на Lightning. Тоді треба взаємодіяти з обома. Але такі випадки скоріше винятки, ніж правило. Також Visualforce трохи нагадує сторінки, написані на GWT, часто треба використовувати силу xpath селекторів та переключатись між фреймами. З Lightning працювати зручніше, оскільки він близький до сучасних JavaScript фреймворків.

Підготовка тестових даних

Динамічна генерація

Якщо ви перфекціоніст, то може виникнути спокуса всі тестові дані генерувати динамічно при кожному запуску тестів. Їх можна створювати перед запуском конкретного тесту чи сьюта і чистити після його завершення. Якщо створення даних займає багато часу, можна попередньо нагенерувати потрібних об’єктів, а в тесті перевіряти, чи є готовий об’єкт. Якщо є, то брати його, а якщо ні — тільки тоді створювати новий.

Створення тестового об’єкта з перевіркою, чи був він згенерований раніше

Однак не всі об’єкти легко створити. Одні можуть бути пов’язані з іншими, і замість одного треба створювати цілу ієрархію. Трапляється, що створення об’єкта може залежати від сторонніх сервісів, котрі важко замінити на mock.

Попередня підготовка

Якщо є об’єкти, які не змінюються в процесі прогону тестів, то їх не обов’язково створювати щоразу. Такі дані можна наконфігурувати один раз і передавати в тест id або ім’я об’єктів як вхідні параметри. Тоді в скриптах ми перевіряємо, чи потрібний об’єкт існує, а не щоразу його створюємо. Під час міграції на новий сендбокс ці тестові дані теж переносяться.

Використання тестового об’єкта, що не змінюється при прогоні тестів

На практиці виявився успішним підхід генерувати більшість тестових даних динамічно, а комплексні взаємопов’язані об’єкти мати уже готовими й передавати на вхід під час запуску тестів.

Висновки

До Salesforce я здебільшого працював на технічно багатих і цікавих проектах, але вони часто були прив’язані до одного вузького домену. Коли проект змінювався, втрачалась частина отриманого там досвіду. Виникало бажання сконцентруватись на чомусь більш прив’язаному до UI та мейнстрімових технологій, таких як Selenium та Appium. Salesforce проекти дають таку можливість. З одного боку, вони різноманітні, а з іншого — при зміні проекту попередній досвід використовується і збагачується.

Розглядаючи Salesforce, я мав побоювання, що проекти будуть технічно одноманітними, а задачі однотипними. Зараз я можу сказати, що ці побоювання не виправдались, а задачі цікаві й стосуються роботи з API та UI.

Оскільки Salesforce стрімко зростає, з’являється багато нових проектів, і кожен з них несе цікаві виклики.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось14
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Дякуюю за статтю, дуже цікаво.
Але маю одне уточнення. Ви написали наступне:

Також немає можливості вибрати всі поля символом «*», тож їх треба вказувати явно.

Насправді, існує функція fields(all), яка повертає всі поля на об’єкті. Документація тут — developer.salesforce.com/...​ls_soql_select_fields.htm

Вірно підмічено. FIELDS() функція зʼявилась і спрощує життя інженерів починаючи з релізу Spring ’21. Спасибі за уточнення

Підписатись на коментарі