Kiszolgáló mód

Az MTP-kiszolgáló mód lehetővé teszi, hogy egy szerkesztő, IDE, egyéni teszt felhasználói felület vagy más teszteszköz elindítson egy tesztalkalmazást, és a JSON-RPC-n keresztül vezesse a felderítést és a végrehajtást. Használja a szerver módot, ha az eszközének tesztmetaadatokra, a kiválasztott tesztek futtatására, előrehaladási frissítésekre vagy eredmény-artefaktumokra van szüksége a terminálkimenet elemzése nélkül. Normál parancssori és CI-futtatások esetén futtassa közvetlenül a tesztalkalmazást, vagy használja dotnet test.

Állandó kapcsolat nélküli egyszeri felderítéshez használja a --list-tests json elemet. Meglévő VSTest-ügyfélhez lásd a Programozott tesztfelderítés és -végrehajtás című témakört.

Indítsa el a nyilvános JSON-RPC-kiszolgálót a --server jsonrpc vagy --server használatával.

Important

A .NET SDK egy külön belső bináris protokollt használ.--server dotnettestcli Ne adja át ezt az értéket. dotnet test konfigurálja az átviteli és protokollargumentumokat.

Csak forrásként használt ügyfélprogram használata

Az MTP 2.4 egy canonical JSON-RPC-ügyfelet biztosít. Adja hozzá a Microsoft. Testing.Platform.ServerMode.Client.Sources csomag. A csomag futásidejű szerelvény hozzáadása helyett C#-forrást injektál a projektbe.

A csak forrásoldali kialakítás a következőket biztosítja:

  • Nincs futtatókörnyezeti DLL vagy további üzembehelyezési függőség.
  • Natív AOT-kompatibilis, tükröződésmentes szerializálás.
  • Az MTP-kiszolgálóval azonos adattárból származó protokolltípusok és szerializálási kód.

Az injektált MtpServerClient, IMtpServerClientés a protokolltípusok inicializálhatják a kapcsolatot, felderíthetik a teszteket, futtathatnak teszteket, kérhetik a kiszolgáló kilépését, és jelentést tehetnek a tesztcsomópont frissítéséről. Az összes injektált típus megmarad internal a fogyasztó szerelvényben. A kliens kezeli a folyamat indítását, az adatátvitelt, az üzenetek keretezését és a szerializálást; nem kell saját kezűleg megvalósítania a JSON-RPC-t.

A csomag lefordítja a C# forrást. Ha Visual Basicből vagy más .NET-nyelvből szeretné használni az ügyfélprogramot, vegyen fel hivatkozást egy olyan C#-osztálytárra, amely az injektált típusok köré épített saját nyilvános API-t tesz elérhetővé, ahogy az alábbi példa mutatja.

Tesztek felderítése és futtatása a .NET-ben

Ez a példa a csak forrást tartalmazó klienscsomag 2.5.2-es verzióját használja, és a .NET 10-et célozza meg. Először hozzon létre egy MTP-kompatibilis tesztalkalmazást, majd adja meg a kliensnek a lefordított .dll vagy futtatható fájl elérési útját.

A megosztott C# segédprogram felderíti a teszteket, kinyomtatja az egyes csomópontok szerializálható metaadatait, és futtatja a felderített teszteket a UID alapján. Egy részhalmaz futtatásához válassza ki a szükséges UID-ket a felderítési frissítésekből, és csak ezeket az UID-ket adja át a RunTestsAsync-nak. Ne használjon megjelenített neveket tesztazonosítóként.

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();
        }
    }
}

A segéd egy külön tesztelési folyamatot indít el a felderítéshez és a végrehajtáshoz, így nincs szükség a(z) MtpServerCapabilities.MultiRequestSupport elemre. Egy folyamat újbóli felhasználásához ellenőrizze ezt a képességet InitializeAsync után, mielőtt több kérést ad ki. A felderítés kérése előtt ellenőrizze a(z) SupportsDiscovery elemet is.

Important

A csak forrásoldali kliens indítási segédprogramjai egy JSON-RPC kapcsolatot kezelnek. A 2.5.2-es verzióban nem fogadják el és irányítják a vezérlőalapú bővítmények által használt további kapcsolatokat. A Windows rendszeren történő TRX-jelentéskészítés például elindíthat egy vezérlőt, amelynek passzív kapcsolata az aktív tesztgazda elé érkezik. Olyan kapcsolat, amelyben a(z) MultiConnectionProvider értéke true, csak a frissítéseket és artefaktumokat küldi le; ne küldjön felderítési vagy futtatási kéréseket erre a kapcsolatra. A példa elutasítja ezt a kapcsolatot.

Vezérlőalapú jelentések esetén futtassa a tesztalkalmazást a CLI-n keresztül, vagy dotnet test használjon olyan eszköz-klienset, amely kezeli a protokoll további kapcsolatait. Az egykapcsolatos ügyfél továbbra is fogadhat olyan artefaktumokat, amelyeket az aktív tesztgazda közzétesz.

A C# és Visual Basic konzolprojektek egyaránt a C# segédprojektre hivatkoznak:

using TestClientSample;

if (args.Length != 1)
{
    Console.Error.WriteLine("Usage: DiscoverAndRun <test-application-path>");
    return 1;
}

return await TestClient.DiscoverAndRunAsync(args[0]);

Futtassa bármelyik konzolprojektet a tesztalkalmazás elérési útjával argumentumként:

dotnet run --project <client-project> -- <test-application-path>

A példa 0 értéket ad vissza, ha minden kiválasztott UID-nek van végleges eredménye, és minden nem felülírt végleges eredmény passed-et vagy skipped-t jelez, 2-at sikertelen vagy hiányzó végleges eredmények esetén, és 8-et, ha a felderítés nem ad vissza teszteket. Ezek a döntések a mintaügyfélhez tartoznak. A befejezett RunTestsAsync feladat azt jelenti, hogy a protokollkérelem befejeződött, nem pedig azt, hogy a tesztek átmentek. MtpRunResult.Artifacts futtatási artefaktumokat tartalmaz, nem pedig pass/fail ítéletet. A test-node frissítéseinek használatával állapítsa meg a teszteredményeket.

Több találathoz ugyanaz a UID tartozhat. A példa csak akkor hagyja figyelmen kívül a korábbi újrapróbálkozási kísérleteket, ha retry.is-supersededtrue; a UID-egyenlőség önmagában nem alapoz meg újrapróbálkozást. Egy később sikeres vagy kihagyott eredmény nem törölhet egy korábbi, nem felülírt hibát.

Frissítések és metaadatok feldolgozása

Iratkozzon fel a TestNodesUpdated elemre a felderítés vagy végrehajtás előtt. Olvassa ki az egyes frissítésekből a Changes elemet, és azonosítsa a csomópontokat a Uid alapján. Az ügyfél olyan kényelmi tulajdonságokkal rendelkezik, mint a DisplayName, ExecutionState, ErrorMessageés DurationInMilliseconds. Olvassa el a Node szótárt a többi szerializált mezővel kapcsolatban, beleértve a traits elemet és a keretrendszerspecifikus tulajdonságokat is.

Az MSTest tulajdonságokként teszi közzé a tesztkategóriákat és az egyéni TestProperty értékeket. A metaadatok elérhetősége a tesztelési keretrendszertől és attól függ, hogy mit tesz közzé a felderítés során. Az tetszőleges attribútumok és a memóriabeli IProperty implementációk nem lépik át automatikusan a folyamathatárt. A hordozható kulcs-érték metaadatok közzétételéhez használja a nyilvános TestMetadataProperty típust. A platform- és kompatibilitási rétegek más szerializálható mezőket is biztosíthatnak; a belső tulajdonságtípusok nem bővítmény API-k. További részletekért lásd: Tesztcsomópont tulajdonságai.

Ne számítson arra, hogy a JSON-RPC csomópont szótára megegyezzen a --list-tests json dokumentuméval. A transzportok különböző mezőelrendezéseket használnak. Emellett ne feltételezzük, hogy minden keretrendszer a VSTest-specifikus vstest.* mezőket bocsátja ki.

A kliens sorrendben dolgozza fel a frissítéseket, és meghívja a megfelelő kezelőket, mielőtt a felderítési vagy futtatási feladat befejeződik. Nincs szükség késleltetésre a végleges frissítés megvárásához. A kezelők szinkron módon futnak a kliens olvasási ciklusában, ezért ne blokkolja őket, és ne várja meg szinkron módon egy másik kliensművelet befejeződését. Irányítsa a nagy erőforrás-igényű feladatokat vagy a felhasználói felület frissítéseit a megfelelő szálra.

Adjon át egy megszakítási tokent a felderítési vagy futtatási metódusoknak, hogy a protokollon keresztül megszakítást kérhessen. Ha a tesztelési keretrendszer támogatja a faszűrőket, használjon DiscoverTestsWithFilterAsync vagy RunTestsWithFilterAsync egy MTP-faszűrő kifejezést a tesztek kiválasztásához anélkül, hogy először létrehoz egy UID-gyűjteményt. Ezek a metódusok nem fogadják el a VSTest --filter kifejezéseket.

Ügyfélkövetelményeknek való megfelelés

Használja a C# 12 vagy újabb verziót. A csomag tiszteletben tartja a projekt nyelvi verzió beállítását. Ha a cél keretrendszer alapértelmezés szerint egy korábbi C#-verzióra van beállítva, állítsa LangVersion értékét 12.0 vagy újabb verzióra. A csomag belső kompatibilitási típusokat is biztosít a .NET Framework 4.6.2-hez és a netstandard2.0-hoz.

A csomag hozzáadása előtt távolítsa el az MTP-ügyfél kézzel írt másolatát a duplikált belső típusok elkerülése érdekében.

Indítási modell kiválasztása

Ha külső folyamatként szeretné elindítani a tesztalkalmazást, használja MtpServerClient.LaunchAsynca következőt: . Az ügyfél birtokolja ezt a gyermekfolyamatot, és szükség esetén megszakítja azt a lebontás során.

Ha egy külső tesztalkalmazást szeretne konfigurálni egy adott környezethez, állítsa be MtpServerClientOptions.EnvironmentVariables az indítás előtt. A gyermekfolyamat örökli a szülő környezetét, a megadott értékeknek a gyermekfolyamatban való alkalmazásával. Ezek a beállítások nem módosítják a folyamaton belüli gazda környezetét.

Minden kliens egy tesztalkalmazást vezérel. Több tesztalkalmazás esetén hozzon létre egy klienst minden alkalmazáshoz, és összesítse az eredményeket az eszközben.

Ha olyan beágyazott gazdagépen vagy UI-szálas környezetben dolgozik, amely nem tudja használni a Process.Start elemet, használja a MtpServerClient.LaunchInProcessAsync elemet. A metódus egy aszinkron visszahíváson keresztül futtatja a tesztalkalmazást az aktuális folyamatban. Ne blokkolja az indító szálat, mert a visszahívási függvény és a kliens ugyanabban a folyamatban fut.

Mindkét indítási modell egy visszacsatolási TCP-átvitelt használ. A böngészős WebAssembly nem tudja létrehozni a szükséges eseményfigyelőt, ezért a folyamaton belüli indítás PlatformNotSupportedException kivételt eredményez. A folyamaton belüli indítás nem biztosít alternatív WebAssembly-átvitelt.

A kliens leállítása

Az ügyfél és az elindított alkalmazás leállításához a hívó szál blokkolása nélkül hívja meg a következőt: IMtpServerClient.ShutdownAsync() Az aszinkron metódus elkerüli a felhasználói felület szálával kapcsolatos válaszkészségi és holtponti problémákat a beágyazott gazdagép-környezetekben.

Folyamaton belüli gazda esetén a MtpServerClientOptions.ServerShutdownTimeout azt szabályozza, hogy a leállítás mennyi ideig várjon azután, hogy az ügyfél lezárta a transportot. Az alapértelmezett érték 30 másodperc. A leállítás után olvassa el a ServerExitCode elemet a visszahívás kilépési kódjának lekéréséhez. Az érték megmarad null , amíg az alkalmazás fut, vagy ha az alkalmazás nem ad vissza egy kilépési kódot.

Az ügyfél állapottartásának deklarálása

Az MTP 2.4.1-től kezdve állítsa be a nullable MtpServerClientOptions.IsStateful beállítást annak leírására, hogy az ügyfél hogyan használja fel a test-node frissítéseket:

Value Nyilatkozat
true Az ügyfél megőrzi a munkamenethez tartozó, címzhető tesztcsomópontok készletét, és az egyes csomópontok utolsó ismert állapotát mindaddig megőrzi, amíg egy másik frissítés nem módosítja azt, ahogyan azt egy IDE tesztfelfedezője is teheti.
false Az ügyfél a tesztcsomópont állapotának megőrzése nélkül feldolgozza a frissítéseket.
null (alapértelmezett) Az ügyfél nem deklarálja a képességet. Az inicializálási kapcsolatfelvétel kihagyja a mezőt.

Az ügyfél explicit logikai értéket küld a kísérleti capabilities.testing.isStateful protokollképességen keresztül. Az állapottartás független a kapcsolat élettartamától és a kiszolgáló MultiRequestSupport képességétől. Az ügyfél nem válik állapotalapúvá csupán azért, mert több kérést küld ugyanazon a kapcsolaton keresztül.

Az MTP 2.4.0 egy nem null értékű logikai beállítást használ, amelynek alapértelmezett értéke a false. Egy be nem jelentett képesség megőrzéséhez használja az MTP 2.4.1-es vagy újabb verzióját.

Keretrendszerben vagy bővítményben a kísérleti IClientInfo szolgáltatással vizsgálja meg a deklarációt. Olvasson clientInfo.Capabilities.IsStateful egy konzervatív logikai eredményhez, amely false, ha a képesség nincs deklarálva. Az MTP 2.4.1-es vagy újabb verziójában a ClientCapabilitiesExtensions.GetIsStateful() megőrzi a nem deklarált, explicit módon állapot nélküli és állapottal rendelkező ügyfelek közötti különbséget.

Protokollkompatibilitás egyeztetése

Az MTP 2.4-től kezdve a kiszolgáló módú protokoll egyeztetése független az MTP NuGet-csomag verziójától. Az ügyfél deklarálja az általa támogatott protokollverziókat, és a kiszolgáló kiválaszt egy közös verziót. Ha nincs átfedés a támogatott verziók között, a tesztalkalmazás a ProtocolError kilépési kóddal (12) kilép ahelyett, hogy inkompatibilis kommunikációs szerződéssel folytatná.

Az üzenetek érvényesítéséhez használja a verziószámozott kiszolgáló módú 1.0 JSON-sémát . A System.Text.Json és a Jsonite szerializálók ugyanazt a sémát és hibaborítékot követik.

Lásd még