You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Visual Studio Test Explorer同步命令行/程序启动的测试结果

问题

我在一个包含约200个测试用例的.NET测试项目上卡了好几天,Visual Studio里直接运行测试一切正常,但想通过自定义CRM程序启动测试时遇到了两个问题:

  • 尝试DTE/Marshal调用方式时,出现大量无效类错误,无法正常工作
  • 改用Process.Start执行命令行测试(vstest.console.exe或dotnet test),测试能正常运行,但Visual Studio的Test Explorer完全不同步,所有测试始终显示为未运行的蓝色状态

我试过的命令行命令:

  1. 使用vstest.console.exe:
"C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\TestPlatform\VSTest.Console.exe" "MY_TEST_PROJECT\bin\Debug\net6.0\WebTests.dll" /TestAdapterPath:"MY_PACKAGES_PATH\packages" /Logger:console;verbosity=detailed

测试正常执行,cmd能看到进度,但Test Explorer无变化。

  1. 使用dotnet test:
dotnet test MY_TEST_PROJECT\WebTests.csproj

同样测试正常,但UI不更新。

此外还尝试过:

  • 重装NUnit3TestAdapter
  • 清理并重建解决方案
  • 删除ComponentMountCache
  • 使用dotnet vstest命令

测试项目的csproj配置:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <IsPackable>false</IsPackable>

    <PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<ItemGroup>
    <PackageReference Include="DocumentFormat.OpenXml" Version="2.10.0" />
    <PackageReference Include="HtmlAgilityPack" Version="1.11.59" />
    <PackageReference Include="iTextSharp" Version="5.5.13.2" />
    <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="NUnit" Version="3.13.3" />
    <PackageReference Include="NUnit.Analyzers" Version="3.6.1">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
    <PackageReference Include="Selenium.WebDriver" Version="4.16.2" />
    <PackageReference Include="Selenium.Support" Version="4.16.2" />
    <PackageReference Include="SeleniumExtras.WaitHelpers" Version="1.0.2" />
    <PackageReference Include="Spire.PDF" Version="10.3.16" />
    <PackageReference Include="Microsoft.TestPlatform" Version="17.8.0" />
</ItemGroup>

<ItemGroup>
    <ProjectReference Include="..MY_LIBRARY\MY_LIBRARY.csproj" />
</ItemGroup>

<ItemGroup>
    <Reference Include="Microsoft.Web.Administration">
        <HintPath>\\MY_ASSEMBLIES\Microsoft.Web.Administration.dll</HintPath>
    </Reference>
    <Reference Include="System.Management">
        <HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Management.dll</HintPath>
    </Reference>
</ItemGroup>

<ItemGroup>
    <Compile Update="Properties\Resources.Designer.cs">
        <DesignTime>True</DesignTime>
        <AutoGen>True</AutoGen>
        <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
</ItemGroup>

<ItemGroup>
    <EmbeddedResource Update="Properties\Resources.resx">
        <Generator>ResXFileCodeGenerator</Generator>
        <LastGenOutput>Resources.Designer.cs</LastGenOutput>
    </EmbeddedResource>
</ItemGroup>
</Project>

另外有一段DTE代码,理论上能触发测试,但它会随机获取当前运行的Visual Studio实例,而我经常同时打开多个VS窗口:

Sub RunAllTestsInVisualStudio()
Dim dte As EnvDTE80.DTE2 = Nothing
Try
    dte = DirectCast(Marshal.GetActiveObject("VisualStudio.DTE"), EnvDTE80.DTE2)
    If dte IsNot Nothing Then
        dte.ExecuteCommand("TestExplorer.ShowTestExplorer")
        dte.ExecuteCommand("Build.CleanSolution")
        Thread.Sleep(5000)
        dte.Solution.SolutionBuild.Build(True)
        'ClearTestResults()
        dte.ExecuteCommand("TestExplorer.RunAllTests")
    End If
Finally
    If dte IsNot Nothing Then Marshal.ReleaseComObject(dte)
End Try
End Sub

需要解决的核心问题:如何通过自定义程序启动测试,同时让Test Explorer同步更新测试结果?


解决方案

方案1:修复DTE调用,指定目标VS实例

原DTE代码的问题是Marshal.GetActiveObject随机返回VS实例,可通过WMI查询进程+解决方案路径定位目标实例:

Imports System.Management
Imports EnvDTE80
Imports System.Runtime.InteropServices

Sub RunTestsInTargetVSInstance(targetSolutionPath As String)
    Dim searcher As New ManagementObjectSearcher("root\CIMV2", "SELECT * FROM Win32_Process WHERE Name = 'devenv.exe'")

    For Each queryObj As ManagementObject In searcher.Get()
        Dim cmdLine As String = queryObj("CommandLine").ToString()
        ' 匹配加载目标解决方案的VS进程
        If cmdLine.Contains(targetSolutionPath) Then
            Dim processId As Integer = Convert.ToInt32(queryObj("ProcessId"))
            Dim dte As DTE2 = GetDteByProcessId(processId)
            If dte IsNot Nothing Then
                dte.ExecuteCommand("TestExplorer.ShowTestExplorer")
                dte.ExecuteCommand("Build.CleanSolution")
                ' 等待构建完成再启动测试,替代Thread.Sleep
                WaitForBuildCompletion(dte.Solution.SolutionBuild)
                dte.ExecuteCommand("TestExplorer.RunAllTests")
                Marshal.ReleaseComObject(dte)
                Exit For
            End If
        End If
    Next
End Sub

Private Function GetDteByProcessId(processId As Integer) As DTE2
    Dim dte As DTE2 = Nothing
    Dim rot As IRunningObjectTable = Nothing
    Dim enumMoniker As IEnumMoniker = Nothing
    Dim monikers(0) As IMoniker

    Try
        GetRunningObjectTable(0, rot)
        rot.EnumRunning(enumMoniker)
        enumMoniker.Reset()

        While enumMoniker.Next(1, monikers, Nothing) = 0
            Dim name As String = ""
            monikers(0).GetDisplayName(Nothing, Nothing, name)
            ' 匹配格式:!VisualStudio.DTE.17.0:进程ID
            If name.StartsWith("!VisualStudio.DTE") AndAlso name.EndsWith($":{processId}") Then
                Dim obj As Object = Nothing
                rot.GetObject(monikers(0), obj)
                dte = DirectCast(obj, DTE2)
                Exit While
            End If
        End While
    Catch ex As Exception
        ' 可添加日志记录逻辑
    Finally
        ' 释放COM资源
        If enumMoniker IsNot Nothing Then Marshal.ReleaseComObject(enumMoniker)
        If rot IsNot Nothing Then Marshal.ReleaseComObject(rot)
        If monikers(0) IsNot Nothing Then Marshal.ReleaseComObject(monikers(0))
    End Try
    Return dte
End Function

Private Sub WaitForBuildCompletion(solutionBuild As SolutionBuild)
    solutionBuild.Build(True)
    While solutionBuild.BuildState = vsBuildState.vsBuildStateInProgress
        System.Threading.Thread.Sleep(500)
    End While
End Sub

' 导入所需COM接口
<ComImport, Guid("00000010-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Private Interface IRunningObjectTable
    Sub _VtblGap1_6()
    Function EnumRunning(<Out> ByRef ppenumMoniker As IEnumMoniker) As Integer
    Function GetObject(ByVal pmkObjectName As IMoniker, <Out> ByRef ppunkObject As Object) As Integer
End Interface

<ComImport, Guid("0000000E-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Private Interface IEnumMoniker
    Function Next(ByVal celt As Integer, <Out> ByRef rgelt As IMoniker, <Out> ByRef pceltFetched As Integer) As Integer
    Sub Skip(ByVal celt As Integer)
    Sub Reset()
    Function Clone(<Out> ByRef ppenum As IEnumMoniker) As Integer
End Interface

<DllImport("ole32.dll")>
Private Shared Function GetRunningObjectTable(ByVal reserved As Integer, <Out> ByRef prot As IRunningObjectTable) As Integer
End Function

方案2:让命令行测试结果同步到Test Explorer

命令行测试无法同步是因为Test Explorer只监听内部会话,可通过生成trx结果文件+DTE导入实现同步:

  1. 修改vstest.console.exe命令,添加trx日志参数:
"C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\TestPlatform\VSTest.Console.exe" "MY_TEST_PROJECT\bin\Debug\net6.0\WebTests.dll" /TestAdapterPath:"MY_PACKAGES_PATH\packages" /Logger:trx;LogFileName=TestResults.trx
  1. 测试完成后,通过DTE命令导入结果:
' 假设已获取目标DTE实例
dte.ExecuteCommand("TestExplorer.ImportTestResults", "PATH_TO\TestResults.trx")

方案3:使用Test Platform编程接口直接启动测试

直接引用Test Platform的客户端包,在自定义程序中启动测试并同步结果:

  1. 添加NuGet包引用:
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="17.8.0" />
<PackageReference Include="Microsoft.TestPlatform.Client" Version="17.8.0" />
  1. 编写测试启动代码:
Imports Microsoft.VisualStudio.TestPlatform.Client
Imports Microsoft.VisualStudio.TestPlatform.ObjectModel
Imports Microsoft.VisualStudio.TestPlatform.ObjectModel.Client

Sub RunTestsWithTestPlatform(testDllPath As String, adapterPath As String, dte As DTE2)
    Dim testPlatformClient As ITestPlatform = TestPlatformFactory.GetTestPlatform()
    Dim runSettings As String = "<RunSettings><RunConfiguration><TargetPlatform>x64</TargetPlatform></RunConfiguration></RunSettings>"
    
    Dim testRunRequest As ITestRunRequest = testPlatformClient.CreateTestRunRequest(
        New List(Of String) From {testDllPath},
        runSettings,
        New List(Of String) From {adapterPath}
    )

    ' 监听测试进度
    AddHandler testRunRequest.TestRunChanged, Sub(sender, args)
        If args.TestRunStatistics IsNot Nothing Then
            Console.WriteLine($"已完成 {args.TestRunStatistics.ExecutedTests} 个测试")
        End If
    End Sub

    ' 启动测试并在完成后导入结果到Test Explorer
    testRunRequest.OnTestRunComplete += Sub(sender, args)
        If args.TestRunResult IsNot Nothing AndAlso args.TestRunResult.Attachments.Any() Then
            Dim trxPath = args.TestRunResult.Attachments.First(Function(a) a.Uri.AbsolutePath.EndsWith(".trx")).Uri.AbsolutePath
            dte.ExecuteCommand("TestExplorer.ImportTestResults", trxPath)
        End If
    End Sub

    testRunRequest.ExecuteAsync()
End Sub

内容的提问来源于stack exchange,提问作者Yisroel M. Olewski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:39:53