Опыт использования AI для QA: тестировщикам приготовиться на выход?
Всем привет, меня зовут Илья. И я использую LLM в своей работе уже полгода. Хочу поделиться своим опытом и наблюдениями, как ИИ может повысить эффективность работы. И стоит ли бояться тестировщикам замены железными мозгами и руками?
Тестировщики встречаются с ИИ лицом к лицу
Будет справедливым сказать, что ИИ, LLM — это отличная база для знакомства с какой-либо областью или предметом, и тестирование — не исключение. ИИ отлично справляется с объяснением теории тестирования, в интерактивном режиме может объяснить лучшие подходы, ссылаясь на первоисточники, книги, ответить на вопросы и помочь разобраться начинающему тестировщику. Раз так, то возникает резонный вопрос — сможет ли ИИ полностью заменить тестировщика? И этот вопрос неоднозначный. Учитывая темпы развития ИИ — ответ положительный, с другой стороны, в какой степени?
Ручное тестирование и база знаний
Я каждый день пользуюсь ИИ помощниками в своей работе, и сейчас возможности ИИ сравнимы с несколькими Junior-подопечными тестировщиками. То есть, сейчас ИИ может очень неплохо справляться с тем, что могут делать молодые специалисты, находясь под контролем опытного руководителя, senior«a. Скажем, в зависимости от ИИ, и того, как вы его используете, ИИ очень неплохо справляется в том числе и с работой ручного тестировщика — написанием чек-листов, тест-дизайнера — подробным описанием тест-кейсов. Но с тем нюансом, что вам, как старшему тестировщику всё же нужно его контролировать и корректировать, а, главное, хорошо разбираться в своей предметной области. Дело в том, что ИИ может неплохо объяснять и выполнять задачу на пальцах, самым понятным и очевидным способом. Это значит не ожидайте от него сложных взаимосвязанных, а главное глубоких сценариев, не ожидайте от него разделения тестовых данных от кейсов (хотя он прекрасно знает, как правильно). То есть каждый раз вам придётся его корректировать — переделай, доделай, углуби. И каждый раз вопрос проработки кейсов — это будет вопрос размера контекста, инструкций, с которым работает ИИ. Это схоже с тем, как обучать под себя юного тестировщика, под ваше видение, под нужную вам глубину и правильность, под ваши возможности и стоимость — по деньгам и времени. А дальше — передавать контекст перед генераций каждый раз, или переобучить модель под ваши специфические нужды. Другим словами, без большого контекста, отдать ему, как мидлу, работу не получится, плюс у него нет механизма контроля — он ляжет на вас. Но о контроле чуть попозже.
Если у нас есть требования, чтобы передать боту или зададим уточняющие вопросы, то можно получать более детальные чек-листы и тесты
При этом, ИИ — это не только джун для делегирования рутины, но так же ваш верный помощник и справочник, очень вовлечённый в решение вашей проблемы. Предоставьте ему лог, ошибку, задачу о том, как настроить какое-либо окружение или инструмент, и ИИ быстро и отлично справится, без необходимости гуглить stackoverflow. Но надо понимать, что большинство моделей актуальны на какую-либо дату, обучения, и их модель не будет знать об изменениях сделанных в последние недели, месяцы, а то и год. Если, конечно, она не имеет доступа к интернет-поиску.
Далее я перейду к автоматизированному тестированию. Итак, для чего лично я использую ИИ и какой?
Документация
Я использую Copilot от GitHub уже третий месяц. То есть, плачу за него $10 в месяц. Стоит ли этого? Честно скажу, что для меня — да. Мне очень нравится делегировать Copilot«у написание комментариев, докстрингов и прочей документации. В большинстве случаев у него это неплохо получается, но если вам нужно нечто другое, то он со второй-третьей попытки подхватывает то, что вы от него хотите — в каком стиле писать докстринги, какими терминами, и из каких (саб)модулей. Одна эта функциональность для меня окупает моё время, которое я бы потратил на написание документации.
Просто пробегаемся по коду и заполняем комментарии — Copilot напишет их исходя из кода
Написание кода
Но функциональность copilot«a не ограничена лишь генерацией документации. Он может генерировать и код. Для простоты можно считать, что copilot — это усовершенствованная версия подсказок IDE. Во время написания кода он будет выплёвывать вам наиболее вероятные продолжения вашего кода. Это не всегда работает, но, как минимум, у вас будет возможность выбрать как вариант copilot«a, так и классическую подстановку IDE. Что же касается генерации кода как такого из описания, а тем более из названия функции, то тут всё обстоит хуже. ИИ может генерировать скелетны и какие-то банальные функции, либо варьировать те, которые вы уже успели написать. Но если вам нужна какая-то специфичная логика, то, увы, придётся писать её самому. И тут важный нюанс — как инструмент разработчика copilot» скорее мешает, чем помогает — он легко может генерировать неверный код, который вам потом придётся отлаживать. Но вот как инструмент автоматизатора тестирования он очень даже неплох — выдать схожие, по своей сути, тесты буквально за нажатие 2–3 клавиш на клавиатуре — это ускорение работы на порядок. Нужно протестировать форму? Напишите 1–2 теста, а потом начинайте писать название теста и вот уже у вас написаны все остальные.
Чем больше вы пишите кода, тем адаптивнее пишет помощник. Начиная с дополнения до генерации целой функции. Либо по аналогии, либо исходя из её названия. Даже без текстового описания.
Итого, как и в случае с «джуном»-ии, так и в случае с copilot«ом, вам нужно будет показать ему примеры, повести и показать как правильно писать тесты — использовать генераторы вместо хардкода, ну и иметь готовую POM (ну или хотя бы часть её). Да, её можно попытаться получить из генерации, но не всё так радужно, как дополнение её по аналогии.
Copilot умеет генерировать и юнит-тесты и объяснять чужой код. Но и то, и другое — не очень глубоко.
С другой стороны, Сopilot может неплохо объяснять чужой код, если вам это нужно. Исправлять ошибки и оптимизировать код (учитите, он может это делать специфично и даже выкидывать то, что вам нужно — но, опять-таки вы можете его скорректировать). А так же генерировать юнит-тесты на основании исходного кода вашего ПО, причём достаточно аккуратно и глубоко, с мокированием. Это достаточно впечатляет, но нужно понимать, что всегда есть вероятность, что что-то не заработает ни с первого, ни с пятого раза, так как опять у Copilot«a, как и любого другого ии будет проблема с величиной контекста. То есть глубиной ваших вызовов, кода, и его сложности и нюансов. То есть, как и в случае с мануальным ИИ-джуном, всё равно нельзя делегировать ему задачу «покрой мою функционально 100% тестами». Не покроет. По крайней мере без вашего активного участия.
Делегируем написание page object’ов и тестов
Вне зависимости от того, используете вы или нет Copilot, ИИ можно делегировать и другую рутину. Например, каждый день, когда я пишу тесты, я прошу LLM написать мне локаторы или сразу PO-объекты, конечно, соответствующие моему шаблону. То есть я передаю ИИ боту исходный код страницы и прошу его сгенерировать для меня page object’ы. Это ускоряет мою работу, но при этом, всё равно получаемый результат нужно контролировать и итеративно улучшать. Например, локаторы, которые быстро предложит бот, будут далеки от идеальных, так нужно будет уточнять, что и как вы хотите укоротить. Бот — тот же джун, которому нужно объяснить, что не нужно просто брать и копировать из браузера огромный XPATH, который сломается при первом же изменении фронтенда.
Без объяснений и обучений ИИ генерирует «заборы» вместо нормальных локаторов
Ну конечно, это касается не только UI тестирования, но и API — никто вам не мешает передать JSON файл Swagger«a и попросить написать тестовые зеркальные endpoint«ы и тесты на ответы и параметры к ним.
В целом, если у вас хороший сервис, вы можете получить готовые тесты прямо налету. При необходимости — передать модели требования, документацию и скорректировать его по расширению вашей модели. То есть указываете модели URL, говорите написать тесты, затем переписать в POM-виде — и вот они готовые тесты. Более того, есть инструменты которые не только позволяют вам написать на лету тесты, но вживую, «потыкать» ваш сайт роботом. Выглядит впечатляюще и заменяет работу ручного тестировщика, у которого вы сидите за спиной. Только еще того, который сразу записывает за собой код автотестов. Ну это я говорю о достаточно кусачем по цене Sider.ai.
Растим электронного Middle
И тут мы подходим к самой актуальной проблеме — отсутствие у ИИ вашего контекста. Скажем, какой бы замечательный не был бы ИИ, какой-то тестовой куки \ логина через SSO для получения информации закрытых вовне страниц у него не будет. Если вам нужен свой стиль и код — ему нужно предоставить свою кодовую базу или доки, скажем, которые вы упакуете в embeddings. Это первая проблема — наверняка ваши автотест уже используют какой-то ваш тестовый фреймворк и обвязки, готовые функции, о которых ИИ не знает. Во-вторых, было бы неплохо не сидеть с ИИ над душой и копировать HTML/JSON ему, затем получать ответ, копировать в ваш код, и затем проверять. Вот возникнут вопросы — пусть приходит с результатом. Ну чтобы не джун был, а такой вот электронный мидл. Возможно ли?
Вместо джуна вы будете сидеть и кричать на ИИ. И да, он будет тупить и бесить не хуже человеков.
Задача вырастить джуна на ручном привода в полуавтоматического мидла ляжет на вас. Но это вполне выполнимо. Вообще говоря, у ИИ все необходимые детали есть. Локаторы, page object«ы он умеет генерировать, тесты на их основе — тоже. Нужно объединить, и дать ему возможность получать обратную связь о той ерунде, что он написал. То есть обратно ему отдавать ошибки, чтобы он исправлял код. Хотите починить стили — литеры. Пусть исправляет до тех пор, пока не получит 100% пассрейт.
В качестве прототипа, proof-of-concept, я буду использовать свою обёртку для OpenAI. Для того, чтобы получить что-то вразумительно от ИИ, я буду использовать ChatGPT4, а так же мне нужно будет использовать функции, которые будут делать всю чёрную работу — звать получать контент страниц и звать тесты. То есть нам нужна будет модель gpt-4–0613. В качестве фреймворка я буду использовать pytest+selenium для простоты (ну и потому что обёртка тоже на Python).
Принципиальная схема прототипа
Весь мой POC тест-фреймворк будет состоять из одного лишь conftest c фикстурой driver, пускалки (runner«a) и по умолчанию будем считать, что на каждую уникальную страницу у нас де-факто имеется фикстура, которая будет открывать нужную страницу для тестирования. В реальной жизни, вероятно, перед стартом теста мы бы проходили аутентификацию и как-то вертели окружение. Но это опустим. Мы уже, фактически, запускаем тесты из реального окружения.
conftest.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
def pytest_runtest_makereport(item, call):
"""
Pytest hook for saving html page on test failure
:param item: pytest item
:param call: pytest call
"""
if "driver" in item.fixturenames:
web_driver = item.funcargs["driver"]
if call.when == "call" and call.excinfo is not None:
with open(f"{item.nodeid.split('::')[1]}.html", "w", encoding="utf-8") as file:
file.write(web_driver.page_source)
@pytest.fixture
def driver():
"""
Pytest fixture for selenium webdriver
:return: webdriver
"""
options = Options()
options.add_argument("--headless")
options.headless = True
path = ChromeDriverManager().install()
_driver = webdriver.Chrome(service=ChromeService(executable_path=path, options=options), options=options)
yield _driver
_driver.close()
_driver.quit()
Для начала напишем системные инструкции для бота. Нам нужно от него три вещи:
Попросить получать код страницы (вызывая определённую функцию), и затем для него сгенерировать json, который будет содержать page object«ы и тесты в определённом формате.
Запускать определённый тест и получить результат. Если есть ошибка, то исправить её.
Повторить 2.
При необходимости дополнять тесты пользовательским контекстом.
Системные инструкции для бота
1) You may obtain page code by calling "get_page_code" function. It will return you:
raw HTML document, what needs to be tested. And you need to respond with json in following format:
{
"page_objects": [
"@property\\n
def calculate_button(self):\\n
return WebDriverWait(self.driver, 10).until(\\n
EC.presence_of_element_located((By.XPATH, '//button[.='''Calculate''']'))\\n
)", <...>
],
"tests": ["def test_division_by_zero(page):\\n
page.numbers_input.send_keys(1024)\\n
page.divide_button.click()\\n
page.calculator_input.send_keys('0')\\n
page.calculate_button.click()\\n
assert page.error.text() == 'Error: divide by zero'", <...>],
}
This means you need to create page objects for each object on the page using laconic and stable XPATH locators (as short and stables as you can, use only By.XPATH locators, not By.ID, not By.CSS_SELECTOR or By.CLASS name), and then create all possible test cases for them. It might be some filed filling tests (errors, border checks, positive and negative cases), clicking, content changing, etc. Please respect to use 'page' fixture for every test, it's predefined in code and opens page under test before it.
2) Then I may ask you to execute some tests. You can run demanded test via "get_tests_results" function, based on gathered content, you need to respond with json in following format:
results = {
"passed": [],
"failed": [],
"error": [],
"failure details": {}
}
where "failure details" - is dict with keys equal to test names (which you generated) and possible failures details. If you got an failures and errors, you need to respond as in 1 with fixed code (page objects and/or tests).
Answer only with JSON in format I mentioned in 1. Never add anything more than that (no explanations, no extra text, only json).
3) In addition to 1 and 2 i may pass you extra info what kind of test data might be used (i.e. for form filling), but in general you need to generate all possible scenarios (valid/invalid/border cases, always add what's not listed by user, but should be for best quality of testing coverage).
Для того, чтобы немного поэкономить токены, получаем страницу (лучше это делать не request«ом, а с помощью selenium, чтобы отработал на странице возможный javascript), затем убираем всё лишнее — то есть оставляем только body и убираем все script«ы. Вы вполне можете расширить так, как вам нужно, например, убирая повторяющиеся элементы (сайдбары, хэдеры и прочая). Для этого я написал класс PageRetriever.
page_retriever.py
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager
class PageRetriever:
"""The PageRetriever class is for managing an instance of the PageRetriever."""
def __init__(self, url=""):
"""
General init.
:param url: (str) URL of the page.
"""
options = Options()
options.add_argument("--headless")
options.headless = True
path = ChromeDriverManager().install()
self.driver = webdriver.Chrome(service=ChromeService(executable_path=path), options=options)
self.url = url
def set_url(self, url):
"""
Set the url.
:param url: (str) URL of the page.
"""
self.url = url
def get_page(self, url=None):
"""
Get the page content from the url.
:param url: (str) URL of the page.
:return: (str) HTML content of the page.
"""
if url:
self.set_url(url)
return self.get_page_content(self.url)
def get_body(self, url=None):
"""
Get the body content of the page.
:param url: (str) URL of the page.
:return: (str) Body content of the page.
"""
if url:
self.set_url(url)
return self.extract_body_content(self.get_page())
def get_body_without_scripts(self, url=None):
"""
Get the body content of the page without tags.
:param url: (str) URL of the page.
:return: (str) Body content of the page without tags.
"""
if url:
self.set_url(url)
return self.remove_script_tags(self.get_body())
def get_page_content(self, url):
"""
Get the page content from the url.
:param url: (str) URL of the page.
:return: (str) HTML content of the page.
"""
self.driver.get(url)
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, "body")))
start_time = time.time()
while True:
network_activity = self.driver.execute_script(
"return window.performance.getEntriesByType('resource').filter(item => "
"item.initiatorType == 'xmlhttprequest' && item.duration == 0)"
)
if not network_activity or time.time() - start_time > 30:
break
content = self.driver.page_source
self.driver.close()
self.driver.quit()
return content
@staticmethod
def extract_body_content(html_content):
"""
Extract the body content from the html_content.
:param html_content: (str) HTML content of the page.
:return: (str) Body content of the page.
"""
soup = BeautifulSoup(html_content, "html.parser")
body_content = soup.body
return str(body_content)
@staticmethod
def remove_script_tags(input_content):
"""
Remove all tags from the input_content.
:param input_content: (str) HTML content of the page.
:return: (str) Body content of the page without tags.
"""
pattern_1 = re.compile(r".*?", re.DOTALL)
pattern_2 = re.compile(r".*?", re.DOTALL)
output = re.sub(pattern_1, "", input_content)
output = re.sub
Обратите внимание, что некоторые сервисы уже предоставляют возможности доступа к web для LLM. В том числе, эта функция уже анонсирована и включена для ChatGPT для коропоративных и plus аккаунтов. Но, в конце концов, вам никто не мешает использовать свой код в целях экономии или оптимизации.
Также нам нужен такой runner, который будет получать данные для обратной связи ИИ о выполненных тестах. Допустим, будем использовать pytest-json-report плагин. В репорт сложим информацию о пройденных и упавших тестах, для каждого упавшего теста добавим саму ошибку (если нужно, то может туда и запихнуть трейсбэк), а так же будет неплохо обратно отдавать код страницы на момент падения. Это будет полезно, чтобы ИИ видел, что изменилось на странице, чтобы он мог написать правильные ожидаемые значения. Правда, в реальной жизни, страницы не такие уж и маленькие, и отдавать на каждую ошибку страницу — слишком жирно. Так что мы всё равно упрёмся в размер контента, и сравнительно немаленькую стоимость в токенах такого эксперимента. Но, по крайней мере, работа ИИ стоит центы, а не сотни долларов, как человека-джуна.
pytest_runner.py
import io
import json
from os import remove
import pytest
from utils.page_retriever import PageRetriever
def run_tests(test_files, add_failed_html=True, add_failure_reasons=True, count_of_htmls=1):
"""
Run tests and return results in JSON format.
:param test_files: (list) list with test files.
:param add_failed_html: (bool) boolean to add html report.
:param add_failure_reasons: (bool) boolean to add failure reasons.
:param count_of_htmls: (int) count of htmls to add. Doesn't recommend to use more than 1.
:return: JSON with results.
"""
pytest.main(
[
"-q",
"--json-report",
"--json-report-file=test_report.json",
#"-n=4",
"-rfEx --tb=none -p no:warnings -p no:logging",
]
+ test_files
)
with open("test_report.json", encoding="utf-8") as json_file:
data = json.load(json_file)
results = {"passed": [], "failed": [], "error": [], "failure details": {}, "failed_pages": {}}
for test in data["tests"]:
node_name = test["nodeid"].split("::")[1]
if test["outcome"] == "passed":
results["passed"].append(node_name)
elif test["outcome"] == "failed" or test["outcome"] == "error":
results[test["outcome"]].append(node_name)
if add_failure_reasons:
results["failure details"][node_name] = {node_name: test["call"]["crash"]}
if add_failed_html:
if len(results["failed_pages"]) < count_of_htmls:
results["failed_pages"][node_name] = {node_name: parse_error_page(node_name)}
json_results = json.dumps(results)
return json_results
def parse_error_page(node_name):
"""
Parse error page.
:param node_name: (str) name of the node.
:return: (str) formatted content of the page.
"""
parser = PageRetriever()
try:
file_name = f"{node_name}.html"
with open(file_name, "r", encoding="utf-8") as file:
formatted_content = parser.remove_script_tags(parser.extract_body_content(file))
remove(file_name)
return formatted_content
except io.UnsupportedOperation:
return "No page available."
Дальше добавляем функции и json для chatgpt, которые будут звать pageretriever и runner соответственно.
gpt_functions.py
from examples.test_generator.pytest_runner import run_tests
from utils.page_retriever import PageRetriever
doc_engine = PageRetriever()
gpt_functions = [
{
"name": "get_page_code",
"description": "Get page code to generate locators and tests",
"parameters": {
"type": "object",
"properties": {"url": {"type": "string", "description": "The URL of the page to get the code from"}},
"required": [],
},
},
{
"name": "get_tests_results",
"description": "Get the results of the tests",
"parameters": {
"type": "object",
"properties": {
"test_files": {
"type": "array",
"items": {"type": "string"},
"description": "The list of test files to run",
}
},
"required": [],
},
},
]
gpt_functions_dict = {
"get_page_code": doc_engine.get_body_without_scripts,
"get_tests_results": run_tests,
}
Так как мы ожидаем, что ИИ будет возвращать только тесты и page object’ы, а не целиком файлы (так мы тоже чуть экономим токены), то нам надо это взять на себя, и написать класс, который будет пересоздавать файл каждый раз. Звать будем его сами, но, могли бы попросить это делать и ИИ, или даже отдельно сделать вариацию, чтобы «шапку» создавала для тестов\po нам она же. Здесь можно и дальше улучшать — добавить логику обновления файла, а не пересоздавать, добавления тестов к существующим и так далее. Но для эксперимента нам хватит просто пересоздания файла целиком.
pom_case_generator.py
import os
from urllib.parse import urlparse, unquote
class PomTestCaseGenerator:
"""Class for generating test files and page objects from json data"""
def __init__(self, url=""):
"""
General init.
:param url: (str) URL of the page.
"""
self.url = url
def set_url(self, url):
"""
Set the url.
:param url: (str) URL of the page.
"""
self.url = url
def ___create_pom_file(self, file_name, page_objects, url="", pom_folder="pom"):
"""
Create page object model file.
:param file_name: (str) Name of the file.
:param page_objects: (list) List of page objects.
:param url: (str) URL of the page.
:param pom_folder: (str) Folder for page object model files.
"""
if not url:
url = self.url
if not os.path.exists(pom_folder):
os.makedirs(pom_folder)
with open(f"{pom_folder}/page_{file_name}.py", "w", encoding="utf-8") as pom_file:
pom_file.write("from selenium.webdriver.common.by import By\n")
pom_file.write("from selenium.webdriver.support.ui import WebDriverWait\n")
pom_file.write("from selenium.webdriver.support import expected_conditions as EC\n\n\n")
pom_file.write(f'class Page{"".join(word.capitalize() for word in file_name.split("_"))}:\n')
pom_file.write(" def __init__(self, driver):\n")
pom_file.write(f' self.url = "{url}"\n')
pom_file.write(" self.driver = driver\n\n")
for method in page_objects:
pom_file.write(f" {method}\n\n")
@staticmethod
def ___create_test_file(file_name, tests, pom_folder="pom", tests_folder="tests"):
"""
Create test file.
:param file_name: (str) Name of the file.
:param tests: (list) List of tests.
:param pom_folder: (str) Folder for page object model files.
:param tests_folder: (str) Folder for test files.
"""
with open(f"{tests_folder}/test_{file_name}.py", "w", encoding="utf-8") as test_file:
test_file.write("import pytest\n\n")
test_file.write(
f'from {pom_folder}.{os.path.splitext(f"page_{file_name}")[0]} import Page'
f'{"".join(word.capitalize() for word in file_name.split("_"))}\n\n\n'
)
test_file.write('@pytest.fixture(scope="function")\n')
test_file.write("def page(driver):\n")
test_file.write(
f' page_under_test = Page{"".join(word.capitalize() for word in file_name.split("_"))}(driver)\n'
)
test_file.write(" driver.get(page_under_test.url)\n")
test_file.write(" return page_under_test\n\n\n")
for test in tests:
test_file.write(f"{test}\n\n\n")
def create_files_from_json(self, json_data, url="", pom_folder="pom", tests_folder="tests"):
"""
Create test and page object model files from json data.
:param json_data: (str) JSON data.
:param url: (str) URL of the page.
:param pom_folder: (str) Folder for page object model files.
:param tests_folder: (str) Folder for test files.
"""
if not url:
url = self.url
parsed_url = urlparse(unquote(url))
file_name = parsed_url.path.strip("/").replace("/", "_") or "index"
self.___create_test_file(file_name, json_data["tests"], pom_folder="..pom", tests_folder=tests_folder)
self.___create_pom_file(file_name, json_data["page_objects"], url, pom_folder=pom_folder)
Наконец, когда у нас есть все части, просто позовём всё в правильном порядке.
generator_test.py
import asyncio
import json
import logging
from examples.creds import oai_token, oai_organization
from examples.test_generator.gpt_functions import gpt_functions, gpt_functions_dict
from examples.test_generator.pom_case_generator import PomTestCaseGenerator
from openai_api.src.openai_api import ChatGPT
from openai_api.src.openai_api.logger_config import setup_logger
PAGE_UNDER_TEST = "https://www.saucedemo.com/"
generator = PomTestCaseGenerator(url=PAGE_UNDER_TEST)
def setup_gpt():
"""Setup GPT bot with appropriate functions and settings"""
gpt = ChatGPT(auth_token=oai_token, organization=oai_organization, model="gpt-4-0613")
gpt.function_dict = gpt_functions_dict
gpt.function_call = "auto"
gpt.functions = gpt_functions
gpt.system_settings = """%instructions_inside%"""
return gpt
async def main():
"""Main function for testing GPT bot"""
print("===Setup GPT bot===")
gpt = setup_gpt()
print(f"===Get page code of {PAGE_UNDER_TEST} and generate PO and tests===")
response = await anext(gpt.str_chat(f"Get page code of {PAGE_UNDER_TEST} and generate PO and tests"))
print(response) # print response as debug output
response = response.replace("\n", "") # need to proper json loading
generator.create_files_from_json(
json.loads(response), pom_folder="examples/test_generator/pom", tests_folder="examples/test_generator/tests"
)
print("===Get tests results for examples/test_generator/tests/test_index.py==")
response = await anext(gpt.str_chat("Get tests results for examples/test_generator/tests/test_index.py"))
print(response) # print response as debug output
print("===If there are failures in code, please fix it by fixing POM and tests===")
response = await anext(gpt.str_chat("If there are failures in code, please fix it by fixing POM and tests"))
print(response) # print response as debug output
generator.create_files_from_json(
json.loads(response), pom_folder="..pom", tests_folder="examples/test_generator/tests"
)
# repeat as many times as you wish or getting 100% passrate
asyncio.run(main())
Получение страницы, генерация и запуск тестов занимают полторы минуты (с перегенерацией на основе ошибок, т.е. весь цикл), что неплохо по времени для одной итерации. Результаты генерации «вслепую» выглядят достаточно неплохо. Да, возможно было бы написать больше тестов, но в целом сеть написала базовые сценарии, да и тесты выглядят правильными. Так, например, в первой итерации у нас прошёл один из пяти тестов. Правда, не совсем честно.
Неплохо, но где же assert в первом тесте? Разве это тест?
Зато во второй итерации ИИ исправил и первый, нечестный тест, и поправил тесты на основании результатов запуска. Теперь у нас проходят пять из шести тестов. Всего за 3 минуты!
5 из 6 — без моего участия за три минуты
Как видно, подход работает, и перебирая страницу за страницей можно быстро создавать рабочие тесты. С учётом того, если у вас есть требования или документация, что всё-таки является более правильным подходом, то дополняя запрос к ИИ, а так же уточняя, какие сценарии вы хотите видеть (Негативные сценарии? Граничные значения?) можно добиваться очень неплохих результатов в полу-автоматическом режиме, лишь передав набор url в скрипт.
Выводы
Я не представляю свою жизнь и дальнейшую работу без ИИ. Использование ИИ в моей работе повышает производительность на порядок. Надеюсь, с развитием технологии, возможности и отдача так же возрастёт без роста цены, и многие вещи, сегодня требующие участия и вовлечения будут так же делегированы ИИ.
Означает ли это, что тестировщики больше не нужны? Нет. Несмотря на все замечательные описательные и генеративные возможности ИИ — это всего лишь мощный инструмент, если угодно станок, которым тоже нужно уметь пользоваться, а при неумении можно отрубить себе пальцы.
Железный конь идёт на смену крестьянской лошадке!
Означает ли это, что джуны не нужны? И да, и нет. На возню что с джунами, что с ИИ всё равно нужно тратить время. И с теми, и с другими, вырастив, получаешь отдачу — и рабочую силу, и моральное удовлетворение, и высвобождение времени. ИИ быстро учится, но под каждую задачу требует перенастройки. Люди в этом отношении более гибкие и универсальные, но и более медленные. На мой взгляд, внедрение ИИ повысит планку для вхождения в профессию, но при этом сделает это вхождение более быстрым и результативным, так как у джунов так же есть доступ к этому инструменту. Уменьшится количество людей, требуемых в команде.
Согласно прогнозам, из-за внедрения ИИ до 300 миллионов человек могут потерять свою работу
Отвечая на вопрос HR и нанимающих менеджеров: «надо увольнять и ждать сокращений»? Если человека может заменить ИИ сейчас, то у меня встречный вопрос к вам: зачем вы держите таких людей? Зачем вы их нанимали? Хорошего тестировщика, пусть даже джуна, решившего освоить профессию, не заменит инструмент, как рабочих на заводе не заменили станки. Просто суть рабочих изменилась — они научились обращаться со станками. Если человек понимает что есть его профессия, хочет развиваться — читая книги и\или общаясь с ИИ, то любой инструмент в его руках и вклад в команду от этого человека будет выше, чем у ИИ, просто из-за его амбиций и стремлений, любопытства, мотивации и пытливости. Если это человек, которого, как ИИ нужно просить каждый раз открывать и делать, но при этом ждать результата не минуту, а неделю — тогда ответ очевиден.
ИИ, хотите ли вы этого или нет — это и ваш инструмент, и ваш коллега
Решает ли любой вопрос ИИ сегодня? Нет, как и требует постоянного контроля и перепроверки, причём компетентным человеком. С другой стороны, позволяет очень быстро решать быстрые, часто рутинные вопросы — на долгосрок. Грубо говоря, если у вас есть заказчик, для которого нужно что-то быстро сделать, но не обязательно качественно: получить какое-то тестовое покрытие и smoke-модель из разряда «проверь, что оно работает», то это можно сделать за минуты. И, в конце концов сконцентрироваться на важных вещах в работе и вне её. Пусть поработают роботы.
На этом всё. Если этот материал был вам полезен, то не забудьте поставить лайк этому посту, написать комментарий, а так же, если воодушевитесь — поделиться монеткой. Оригинал статьи тут.