Получить ID тестового набора и плана с ID теста в Azure DevOps
Узнайте, как получить test_suite_id, test_plan_id и test_case_id в одном DataFrame с помощью Python и Azure DevOps API, исправив NULL‑значения при извлечении данных.
Как можно получить идентификаторы Test Suite и Test Plan вместе с идентификатором Test Case в одном DataFrame, используя Python для интеграции с Azure DevOps API?
Я пишу скрипт на Python, который извлекает test_run_id, test_result_id, test_case_id, test_suite_id и test_plan_id из Azure DevOps. В настоящее время я получаю только NULL‑значения для полей test_suite_id и test_plan_id. Какой правильный подход, чтобы получить эти ID в том же DataFrame, что и ID тест‑кейса?
Понимание иерархии объектов тестирования в Azure DevOps
В Azure DevOps объекты тестирования образуют иерархическую структуру:
- Test Plans содержат Test Suites
- Test Suites содержат Test Cases
- Test Runs выполняют Test Cases из конкретных Test Suites
При получении результатов тестов API обычно возвращает только ID тест‑кейсов, но не ID родительских планов и наборов. Чтобы собрать все ID в один DataFrame, необходимо явно запросить эти связи.
Полное решение на Python
Ниже приведено комплексное решение с использованием библиотеки azure-devops-python-api:
from azure.devops.connection import Connection
from azure.devops.v6_0.work_item_tracking.models import Wiql
from azure.devops.v6_0.test.models import TestCaseResult
from azure.devops.v6_0.test_plan.models import TestCase, TestCaseResult as TestCaseResultv7
import pandas as pd
from typing import List, Dict, Any
def get_all_test_ids_with_relationships(organization: str, project: str,
personal_access_token: str) -> pd.DataFrame:
"""
Получить test_run_id, test_result_id, test_case_id, test_suite_id и test_plan_id
в одном DataFrame с помощью Azure DevOps Python API.
"""
# Создание подключения
credentials = BasicAuthentication('', personal_access_token)
connection = Connection(base_url=f"https://dev.azure.com/{organization}", creds=credentials)
# Получение клиентов
wit_client = connection.clients.get_work_item_tracking_client()
test_client = connection.clients.get_test_client()
test_plan_client = connection.clients.get_test_plan_client()
# Шаг 1: Получить все тест‑планы
test_plans = []
tp_query = Wiql(query="""SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] = 'Test Plan'""")
for plan in wit_client.query_by_wiql(tp_query).work_items:
test_plans.append({
'test_plan_id': plan.id,
'test_plan_url': plan.url
})
# Шаг 2: Для каждого плана получить его наборы и тест‑кейсы
all_test_cases = []
for plan in test_plans:
plan_id = plan['test_plan_id']
# Получить все наборы в плане
suites = test_plan_client.get_suites(project=project, plan_id=plan_id)
for suite in suites:
suite_id = suite.id
# Получить все тест‑кейсы в наборе
test_cases = test_plan_client.get_test_cases(
project=project,
plan_id=plan_id,
suite_id=suite_id
)
for tc in test_cases:
all_test_cases.append({
'test_case_id': tc.id,
'test_case_name': tc.name,
'test_suite_id': suite_id,
'test_suite_name': suite.name,
'test_plan_id': plan_id,
'test_plan_name': plan.name if hasattr(plan, 'name') else f"Plan {plan_id}"
})
# Шаг 3: Получить тест‑рансы и результаты
test_results_data = []
for plan in test_plans:
plan_id = plan['test_plan_id']
# Получить тест‑рансы для этого плана
test_runs = test_client.get_test_runs(project=project, plan_id=plan_id)
for run in test_runs:
run_id = run.id
# Получить результаты тестов для этого ранса
test_results = test_client.get_test_results(project=project, run_id=run_id)
for result in test_results:
test_results_data.append({
'test_run_id': run_id,
'test_result_id': result.id,
'test_case_id': result.test_case.id,
'outcome': result.outcome,
'duration_in_ms': result.duration_in_ms
})
# Шаг 4: Объединить все данные в один DataFrame
df_test_cases = pd.DataFrame(all_test_cases)
df_test_results = pd.DataFrame(test_results_data)
# Соединить результаты тестов с тест‑кейсами, чтобы получить ID наборов и планов
merged_df = pd.merge(
df_test_results,
df_test_cases,
on='test_case_id',
how='left'
)
return merged_df
Пошаговая реализация
Шаг 1: Установить необходимые библиотеки
pip install azure-devops pandas requests
Шаг 2: Настроить аутентификацию
from azure.devops.connection import Connection
from azure.devops.credentials import BasicAuthentication
# Данные вашей организации и проекта Azure DevOps
organization = "your-organization"
project = "your-project"
personal_access_token = "your-pat-token"
# Создание подключения
credentials = BasicAuthentication('', personal_access_token)
connection = Connection(base_url=f"https://dev.azure.com/{organization}", creds=credentials)
Шаг 3: Получить иерархию тест‑планов, наборов и кейсов
def get_test_plan_suite_case_relationships(organization: str, project: str,
personal_access_token: str) -> pd.DataFrame:
"""
Получить полную иерархию: Test Plan -> Test Suite -> Test Case
"""
connection = Connection(base_url=f"https://dev.azure.com/{organization}",
creds=BasicAuthentication('', personal_access_token))
test_plan_client = connection.clients.get_test_plan_client()
wit_client = connection.clients.get_work_item_tracking_client()
# Получить все тест‑планы
tp_query = Wiql(query="""SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] = 'Test Plan'""")
test_plans = wit_client.query_by_wiql(tp_query).work_items
relationships = []
for plan in test_plans:
plan_id = plan.id
plan_name = plan.fields.get('System.Title', f'Test Plan {plan_id}')
# Получить все наборы в плане
suites = test_plan_client.get_suites(project=project, plan_id=plan_id)
for suite in suites:
suite_id = suite.id
suite_name = suite.name
# Получить все тест‑кейсы в наборе
test_cases = test_plan_client.get_test_cases(
project=project,
plan_id=plan_id,
suite_id=suite_id
)
for tc in test_cases:
relationships.append({
'test_plan_id': plan_id,
'test_plan_name': plan_name,
'test_suite_id': suite_id,
'test_suite_name': suite_name,
'test_case_id': tc.id,
'test_case_name': tc.name
})
return pd.DataFrame(relationships)
Шаг 4: Получить тест‑рансы и результаты
def get_test_runs_and_results(organization: str, project: str,
personal_access_token: str) -> pd.DataFrame:
"""
Получить тест‑рансы и их результаты
"""
connection = Connection(base_url=f"https://dev.azure.com/{organization}",
creds=BasicAuthentication('', personal_access_token))
test_client = connection.clients.get_test_client()
test_plan_client = connection.clients.get_test_plan_client()
# Получить все тест‑планы
tp_query = Wiql(query="""SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] = 'Test Plan'""")
test_plans = wit_client.query_by_wiql(tp_query).work_items
all_results = []
for plan in test_plans:
plan_id = plan.id
# Получить тест‑рансы для этого плана
test_runs = test_client.get_test_runs(project=project, plan_id=plan_id)
for run in test_runs:
run_id = run.id
# Получить результаты тестов для этого ранса
test_results = test_client.get_test_results(project=project, run_id=run_id)
for result in test_results:
all_results.append({
'test_run_id': run_id,
'test_result_id': result.id,
'test_case_id': result.test_case.id,
'outcome': result.outcome,
'duration_in_ms': result.duration_in_ms,
'test_plan_id': plan_id # Мы знаем это из внешнего цикла
})
return pd.DataFrame(all_results)
Шаг 5: Объединить всё в один DataFrame
def get_complete_test_dataframe(organization: str, project: str,
personal_access_token: str) -> pd.DataFrame:
"""
Получить полный DataFrame со всеми ID тестов
"""
# Получить иерархию связей
relationships_df = get_test_plan_suite_case_relationships(organization, project, personal_access_token)
# Получить тест‑рансы и результаты
results_df = get_test_runs_and_results(organization, project, personal_access_token)
# Объединить всё
complete_df = pd.merge(
results_df,
relationships_df,
on=['test_case_id', 'test_plan_id'],
how='left'
)
return complete_df
Ключевые конечные точки API
Согласно документации Microsoft, вот ключевые конечные точки API, которые вы будете использовать:
-
Получить Test Plans:
GET https://dev.azure.com/{organization}/{project}/_apis/test/Plans?api-version=7.1 -
Получить Test Suites по плану:
GET https://dev.azure.com/{organization}/{project}/_apis/test/Plans/{planId}/Suites?api-version=7.1 -
Получить Test Cases по набору:
GET https://dev.azure.com/{organization}/{project}/_apis/testplan/Plans/{planId}/Suites/{suiteId}/TestCase?api-version=7.1 -
Получить Test Runs:
GET https://dev.azure.com/{organization}/{project}/_apis/test/Runs?api-version=7.1 -
Получить Test Results:
GET https://dev.azure.com/{organization}/{project}/_apis/test/Runs/{runId}/Results?api-version=7.1
Частые ошибки и решения
Ошибка 1: Получение NULL‑значений для ID наборов и планов
Проблема: При прямом получении результатов тестов они не включают информацию о наборе и плане.
Решение: Используйте иерархический подход – сначала получите все связи, затем объедините их с результатами тестов.
Ошибка 2: Проблемы с производительностью в больших проектах
Проблема: Слишком много запросов к API может замедлить выполнение.
Решение: Используйте пакетные операции и пагинацию, где это возможно:
# Пример: Получить тест‑кейсы с пагинацией
def get_all_test_cases_with_pagination(organization, project, plan_id, suite_id):
test_cases = []
skip = 0
top = 1000 # Максимум элементов на странице
while True:
response = test_plan_client.get_test_cases(
project=project,
plan_id=plan_id,
suite_id=suite_id,
top=top,
skip=skip
)
test_cases.extend(response.value)
if len(response.value) < top:
break
skip += top
return test_cases
Ошибка 3: Проблемы с аутентификацией
Проблема: Ошибки аутентификации.
Решение: Убедитесь, что ваш PAT имеет правильные разрешения:
# Необходимые разрешения для Personal Access Token:
# - Test Plans: Read
# - Test Runs: Read
# - Work Items: Read
Ошибка 4: Отсутствие данных в объединениях
Проблема: Некоторые тест‑кейсы могут не иметь рансов, или некоторые рансы могут ссылаться на тест‑кейсы, которые не находятся в текущем запросе.
Решение: Используйте подходящие типы объединений и обрабатывайте отсутствующие данные:
# Левое объединение, чтобы сохранить все результаты тестов, даже если связи не найдены
complete_df = pd.merge(results_df, relationships_df, on='test_case_id', how='left')
# Или внутреннее объединение, чтобы получить только результаты с полной информацией
complete_df = pd.merge(results_df, relationships_df, on='test_case_id', how='inner')
Альтернативный подход с использованием Test Plan API
Для повышения производительности, особенно в больших проектах, можно использовать напрямую Test Plan API:
def get_complete_data_using_test_plan_api(organization: str, project: str,
personal_access_token: str) -> pd.DataFrame:
"""
Альтернативный подход, использующий Test Plan API напрямую для лучшей производительности
"""
connection = Connection(base_url=f"https://dev.azure.com/{organization}",
creds=BasicAuthentication('', personal_access_token))
test_plan_client = connection.clients.get_test_plan_client()
test_client = connection.clients.get_test_client()
wit_client = connection.clients.get_work_item_tracking_client()
# Шаг 1: Получить все тест‑планы
tp_query = Wiql(query="""SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] = 'Test Plan'""")
test_plans = wit_client.query_by_wiql(tp_query).work_items
all_data = []
for plan in test_plans:
plan_id = plan.id
plan_name = plan.fields.get('System.Title', f'Test Plan {plan_id}')
# Шаг 2: Получить все наборы в плане
suites = test_plan_client.get_suites(project=project, plan_id=plan_id)
for suite in suites:
suite_id = suite.id
suite_name = suite.name
# Шаг 3: Получить все тест‑кейсы в наборе
test_cases = test_plan_client.get_test_cases(
project=project,
plan_id=plan_id,
suite_id=suite_id
)
for tc in test_cases:
# Шаг 4: Найти тест‑рансы для этого тест‑кейса
test_runs = test_client.get_test_runs(
project=project,
plan_id=plan_id,
test_case_id=tc.id
)
for run in test_runs:
# Шаг 5: Получить результаты тестов для этого ранса
test_results = test_client.get_test_results(project=project, run_id=run.id)
for result in test_results:
all_data.append({
'test_plan_id': plan_id,
'test_plan_name': plan_name,
'test_suite_id': suite_id,
'test_suite_name': suite_name,
'test_case_id': tc.id,
'test_case_name': tc.name,
'test_run_id': run.id,
'test_result_id': result.id,
'outcome': result.outcome,
'duration_in_ms': result.duration_in_ms
})
return pd.DataFrame(all_data)
Этот всесторонний подход гарантирует, что вы получите все необходимые ID (test_plan_id, test_suite_id, test_case_id, test_run_id, test_result_id) в одном DataFrame, устраняя проблему NULL‑значений.
Источники
- How to get Test Suite ID and Test Plan ID in the same dataframe as the Test Case ID? - Stack Overflow
- How to retrieve Test Results in Azure DevOps with Python REST API - Stack Overflow
- Suite Test Case - Get Test Case List - REST API (Azure DevOps Test Plan) | Microsoft Learn
- Test Suites - Get Test Cases - REST API (Azure DevOps Test) | Microsoft Learn
- azure-devops-python-api/azure-devops/azure/devops/released/test/test_client.py at dev · microsoft/azure-devops-python-api
Вывод
- Используйте иерархический подход: сначала получите связи Test Plan → Test Suite → Test Case, затем объедините их с тест‑рансами и результатами.
- Библиотека
azure-devops-python-apiпредоставляет необходимые клиенты для доступа к API Azure DevOps. - Ключ к успеху – правильный порядок вызовов API, чтобы захватить все связи.
- Обрабатывайте пагинацию и проблемы производительности в больших проектах.
- Выбирайте подходящий тип объединения (left или inner) в зависимости от того, нужны ли вам все результаты или только полностью заполненные данные.
- Убедитесь, что ваш Personal Access Token имеет все необходимые разрешения для чтения тест‑планов, рансов и work‑items.
Такой подход даст вам полный DataFrame со всеми ID тестов, которые вам нужны, устраняя проблему NULL‑значений для ID наборов и планов.