Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Режим сервера MTP позволяет редактору, интегрированной среде разработки, пользовательскому тестовому интерфейсу или другому средству тестирования запустить тестовое приложение и управлять обнаружением и выполнением с помощью JSON-RPC. Используйте режим сервера, когда средство нуждается в тестовых метаданных, выполнении выбранного теста, обновлении хода выполнения или артефактах результатов без анализа выходных данных терминала. Для обычных запусков командной строки и CI запустите тестовое приложение напрямую или используйте dotnet test.
Для одноразового обнаружения без постоянного подключения используйте --list-tests json. Сведения о существующем клиенте VSTest см. в разделе "Программное обнаружение и выполнение тестов".
Запустите общедоступный сервер JSON-RPC с --server или --server jsonrpc.
Important
SDK .NET использует отдельный внутренний двоичный протокол через --server dotnettestcli. Не передавайте это значение вручную.
dotnet test настраивает аргументы транспорта и протокола.
Использование исходного клиента
MTP 2.4 предоставляет канонический JSON-RPC клиент. Добавьте пакет Microsoft.Testing.Platform.ServerMode.Client.Sources. Пакет внедряет источник C# в проект вместо добавления сборки среды выполнения.
Конфигурация только для источника предусматривает следующее:
- Нет библиотеки DLL среды выполнения или дополнительных зависимостей развертывания.
- Сериализация, совместимая с Native AOT и не использующая рефлексию.
- Типы протоколов и код сериализации из того же репозитория, что и сервер MTP.
Внедрённые MtpServerClient, IMtpServerClient и типы протоколов могут инициализировать подключение, обнаруживать тесты, выполнять тесты, запрашивать завершение работы сервера и сообщать об обновлениях тестовых узлов. Все внедренные типы остаются internal в потребляющей сборке. Клиент обрабатывает запуск процесса, транспорт, кадрирование сообщений и сериализацию; вам не нужно реализовывать JSON-RPC самостоятельно.
Пакет компилирует исходный код C#. Чтобы использовать клиент из Visual Basic или другого языка .NET, добавьте ссылку на библиотеку классов C#, которая предоставляет собственный общедоступный API вокруг внедренных типов, как показано в следующем примере.
Обнаруживайте и запускайте тесты из .NET
В этом примере используется исходный пакет клиента версии 2.5.2 и ориентирован на .NET 10. Сначала создайте тестовое приложение с поддержкой MTP, а затем передайте путь к созданному .dll или исполняемому файлу клиенту.
Общий вспомогательный модуль C# обнаруживает тесты, печатает сериализуемые метаданные каждого узла и выполняет обнаруженные тесты по UID. Чтобы запустить подмножество, выберите нужные UID из обновлений механизма обнаружения и передайте только эти UID в RunTestsAsync. Не используйте отображаемые имена в качестве тестовых идентификаторов.
using System.Text.Json;
using Microsoft.Testing.Platform.ServerMode.Client;
namespace TestClientSample;
public static class TestClient
{
public static async Task<int> DiscoverAndRunAsync(
string testApplicationPath,
CancellationToken cancellationToken = default)
{
string path = Path.GetFullPath(testApplicationPath);
var testUids = new HashSet<string>(StringComparer.Ordinal);
using (IMtpServerClient client = await MtpServerClient.LaunchAsync(
path, cancellationToken: cancellationToken))
{
try
{
MtpServerCapabilities capabilities =
await client.InitializeAsync(cancellationToken);
if (capabilities.MultiConnectionProvider)
{
throw new NotSupportedException("A passive controller connection can't discover or run tests.");
}
if (!capabilities.SupportsDiscovery)
{
throw new NotSupportedException("The test application doesn't support discovery.");
}
client.LogReceived += (_, log) => Console.Error.WriteLine(log.Message);
client.TestNodesUpdated += (_, update) =>
{
foreach (MtpTestNodeUpdate change in update.Changes)
{
Console.WriteLine(JsonSerializer.Serialize(change.Node));
if (change.NodeType != "group"
&& change.ExecutionState == "discovered"
&& change.Uid is string uid)
{
testUids.Add(uid);
}
}
};
await client.DiscoverTestsAsync(cancellationToken);
await client.ExitAsync(cancellationToken);
}
finally
{
await client.ShutdownAsync();
}
}
if (testUids.Count == 0)
{
Console.Error.WriteLine("No tests were discovered.");
return 8;
}
var completedUids = new HashSet<string>(StringComparer.Ordinal);
bool hasUnsuccessfulResult = false;
using IMtpServerClient runClient = await MtpServerClient.LaunchAsync(
path, cancellationToken: cancellationToken);
try
{
MtpServerCapabilities capabilities =
await runClient.InitializeAsync(cancellationToken);
if (capabilities.MultiConnectionProvider)
{
throw new NotSupportedException("A passive controller connection can't discover or run tests.");
}
runClient.LogReceived += (_, log) => Console.Error.WriteLine(log.Message);
runClient.TestNodesUpdated += (_, update) =>
{
foreach (MtpTestNodeUpdate change in update.Changes)
{
Console.WriteLine(JsonSerializer.Serialize(change.Node));
if (change.Uid is string uid
&& testUids.Contains(uid)
&& change.ExecutionState is string state
&& state is not ("discovered" or "in-progress")
&& (!change.Node.TryGetValue("retry.is-superseded", out object? superseded)
|| superseded is not true))
{
completedUids.Add(uid);
hasUnsuccessfulResult |= state is not ("passed" or "skipped");
}
}
};
MtpRunResult result = await runClient.RunTestsAsync(testUids, cancellationToken);
foreach (MtpAttachment artifact in result.Artifacts)
{
Console.WriteLine($"Artifact: {artifact.Uri}");
}
await runClient.ExitAsync(cancellationToken);
if (hasUnsuccessfulResult || !testUids.All(completedUids.Contains))
{
Console.Error.WriteLine("A selected test failed or didn't report a final result.");
return 2;
}
return 0;
}
finally
{
await runClient.ShutdownAsync();
}
}
}
Вспомогательный модуль запускает отдельный процесс тестирования для обнаружения и выполнения, поэтому ему не требуется MtpServerCapabilities.MultiRequestSupport. Чтобы повторно использовать процесс, проверьте эту возможность перед InitializeAsync отправкой нескольких запросов. Кроме того, проверьте SupportsDiscovery, прежде чем запрашивать обнаружение.
Important
Вспомогательные средства запуска клиента, работающего только с исходным кодом, управляют одним подключением JSON-RPC. В версии 2.5.2 не принимаются и не маршрутизируются дополнительные соединения, используемые расширениями, работающими через контроллер. Например, создание TRX-отчетов в Windows может запустить контроллер, пассивное подключение которого поступает до активного узла тестирования. Соединение с MultiConnectionProvider с параметром, установленным только в true, отправляет только обновления и артефакты; не отправляйте в него запросы обнаружения или выполнения. Этот пример отклоняет это соединение.
Для отчетов на основе контроллера запустите тестовое приложение через ИНТЕРФЕЙС командной строки или dotnet test, или используйте клиентский инструмент, обрабатывающий дополнительные подключения протокола. Клиент с одним подключением по-прежнему может получать артефакты, публикуемые активным узлом тестирования.
Проекты консоли C# и Visual Basic ссылаются на вспомогательный проект C#:
using TestClientSample;
if (args.Length != 1)
{
Console.Error.WriteLine("Usage: DiscoverAndRun <test-application-path>");
return 1;
}
return await TestClient.DiscoverAndRunAsync(args[0]);
Запустите любой консольный проект, передав в качестве аргумента путь к тестовому приложению:
dotnet run --project <client-project> -- <test-application-path>
В этом примере возвращается 0, когда каждый выбранный UID имеет окончательный результат и все отчеты об окончательных результатах, которые не были заменены, содержат passed или skipped, 2 — для неуспешных или отсутствующих окончательных результатов, а 8 — когда при обнаружении тестов не найдено ни одного теста. Эти решения принадлежат тестовому клиенту. Завершенная RunTestsAsync задача означает, что запрос протокола завершен, а не то, что тесты прошли успешно.
MtpRunResult.Artifacts содержит артефакты запуска, а не статус «прошел/не прошел». Используйте обновления test-node для определения результатов теста.
Несколько результатов могут иметь общий UID. В примере игнорируется более ранняя повторная попытка только в том случае, если retry.is-superseded это true; только равенство UID не устанавливает повторную попытку. Более поздний успешный или пропущенный результат не может удалить более ранний не замещенный сбой.
Получение обновлений и метаданных
Подпишитесь на TestNodesUpdated перед поиском или выполнением. Считывайте Changes из каждого обновления и определяйте узлы по Uid. Клиент предоставляет удобные свойства, такие как DisplayName, ErrorMessage, ExecutionState и DurationInMilliseconds.
Node Прочитайте словарь для других сериализованных полей, включая traits и свойства, специфичные для фреймворка.
MSTest публикует тестовые категории и пользовательские значения TestProperty как атрибуты. Доступность метаданных зависит от фреймворка тестирования и от того, что он публикует во время обнаружения. Произвольные атрибуты и реализации в памяти IProperty не пересекают границу процесса автоматически. Чтобы опубликовать переносимые метаданные в формате «ключ-значение», используйте общедоступный тип TestMetadataProperty. Платформа и слои совместимости могут предоставлять другие сериализуемые поля; их внутренние типы свойств не являются API расширения. Дополнительные сведения см. в разделе Свойства test-node.
Не ожидайте, что словарь узлов JSON-RPC совпадал с --list-tests json документом. Транспорты используют разные структуры полей. Кроме того, не следует предполагать, что каждый фреймворк формирует специфические для VSTest vstest.* поля.
Клиент обрабатывает обновления по порядку и вызывает соответствующие обработчики до завершения задачи обнаружения или выполнения. Вам не нужно ждать окончательного обновления. Обработчики выполняются синхронно в цикле чтения клиента, поэтому не блокируйте их или синхронно подождите другую операцию клиента. Отправка дорогостоящих рабочих или пользовательских обновлений в соответствующий поток.
Передайте маркер отмены для обнаружения или запуска методов для запроса отмены через протокол. Если платформа тестирования поддерживает фильтры деревьев, используйте DiscoverTestsWithFilterAsync или RunTestsWithFilterAsync с выражением фильтра дерева MTP, чтобы выбрать тесты без первого создания коллекции UID. Эти методы не принимают выражения VSTest --filter .
Соответствие требованиям клиента
Используйте C# 12 или более поздней версии. Пакет учитывает настройку языковой версии проекта. Если целевая платформа по умолчанию использует более раннюю версию C#, установите для LangVersion значение 12.0 или более позднее. Пакет также предоставляет внутренние типы совместимости для .NET Framework 4.6.2 и netstandard2.0.
Удалите любую рукописную копию клиента MTP перед добавлением пакета, чтобы избежать дублирования внутренних типов.
Выбор модели запуска
Чтобы запустить тестовое приложение как внешний процесс, используйте MtpServerClient.LaunchAsync. Клиент отвечает за этот дочерний процесс и завершает его при завершении работы, если это необходимо.
Чтобы настроить внешнее тестовое приложение для определенной среды, задайте MtpServerClientOptions.EnvironmentVariables перед запуском. Дочерний процесс наследует среду родительского процесса с заданными значениями, применёнными к дочернему процессу. Эти параметры не изменяют среду для внутрипроцессного хоста.
Каждый клиент управляет одним тестовым приложением. Для нескольких тестовых приложений создайте клиент для каждого приложения и агрегируйте их результаты в вашем инструменте.
Если встроенный хост или среда UI-потока не могут использовать Process.Start, используйте MtpServerClient.LaunchInProcessAsync. Метод запускает тестовое приложение с помощью асинхронного обратного вызова в текущем процессе. Не блокируйте поток запуска, так как обратный вызов и клиент совместно используют этот процесс.
Обе модели запуска используют транспорт TCP обратного цикла. Браузер WebAssembly не может создать необходимый прослушиватель, поэтому запуск в процессе вызывает.PlatformNotSupportedException Внутрипроцессный запуск не поддерживает альтернативный транспорт для WebAssembly.
Завершение работы клиента
Чтобы завершить работу клиента и запущенного приложения, не блокируя вызывающий поток, вызовите IMtpServerClient.ShutdownAsync(). Асинхронный метод позволяет избежать проблем с отзывчивостью потока пользовательского интерфейса и взаимных блокировок во встроенных хостах.
Для внутрипроцессного узла MtpServerClientOptions.ServerShutdownTimeout определяет, сколько времени завершение работы ожидает после того, как клиент закрывает транспорт. Значение по умолчанию — 30 секунд. Прочитайте ServerExitCode после завершения работы, чтобы получить код выхода обратного вызова. Значение остается null во время выполнения приложения или при сбое приложения вместо возврата кода выхода.
Объявить состояние клиента
Начиная с MTP 2.4.1, задайте параметр nullable MtpServerClientOptions.IsStateful, чтобы описать, как клиент использует обновления тестового узла:
| Value | Декларация |
|---|---|
true |
Клиент сохраняет адресуемый набор тестовых узлов для сеанса и сохраняет последнее состояние каждого узла, пока очередное обновление не изменит его, как это делает обозреватель тестов IDE. |
false |
Клиент потребляет обновления, не сохраняя это состояние тестового узла. |
null (по умолчанию) |
Клиент не заявляет о поддержке этой возможности. Начальное рукопожатие пропускает поле. |
Клиент отправляет явное логическое значение через экспериментальную функцию протокола capabilities.testing.isStateful. Сохранение состояния не зависит от времени существования подключения и возможностей сервера MultiRequestSupport. Клиент не становится клиентом с сохранением состояния только потому, что он отправляет несколько запросов по одному подключению.
MTP 2.4.0 использует логический параметр, не допускающий NULL, со значением по умолчанию false. Чтобы сохранить незаявленную функцию, используйте MTP 2.4.1 или более позднюю версию.
Во фреймворке или расширении используйте экспериментальную IClientInfo службу для проверки объявления. Читайте clientInfo.Capabilities.IsStateful для консервативного логического результата, который равен false, когда возможность не объявлена. В MTP 2.4.1 или более поздней версии ClientCapabilitiesExtensions.GetIsStateful() сохраняет различие между необъявленными, явным образом не имеющими состояния и клиентами с состоянием.
Согласование совместимости протокола
Начиная с MTP 2.4, согласование протокола в режиме сервера не зависит от версии пакета NuGet MTP. Клиент объявляет поддерживаемые версии протокола, а сервер выбирает общую версию. Если ни одна поддерживаемая версия не совпадает, тестовое приложение завершается с кодом выхода ProtocolError (12) вместо продолжения работы с несовместимым контрактом обмена данными.
Для проверки сообщений используйте версионированную JSON-схему server-mode 1.0. Сериализаторы System.Text.Json и Jsonite используют одну и ту же схему и формат ошибок.