TestMethod: async Task TestSth() does not work wit

2020-06-16 10:19发布

问题:

I'm trying to run asynchronous test methods with .NET 4.0 BCL Async and MsTest.

It seems that this setup is not able to deal with [TestMethod] async Task TestSth() due to a missing entry in the test case explorer. After changing the signature to async void, I can run the the test case but with the wrong outcome (no errors will be reported at all).

I have seen an attemt at Running Async Task unit tests with TFS 2010 but I think there should be a prettier way to tackle the problem.

Any suggestions?

回答1:

You can only use the async keyword with an MSTest-referencing class library targeting .NET 4.5.

If you can't use .NET 4.5 for whatever reason, then you'll just have to live with waiting on the tasks manually.

And even if the production code (i.e. the code under test) can't use .NET 4.5, why can't the test project do so? If you already have VS 2012+ available to you, then .NET 4.5 will be installed on your development machine.



回答2:

Here's a workaround that is working for me. It was a bit tricky to figure out, but finally all unit tests against my .NET 4.0 libraries are being detected and showing up in Test Explorer, running and passing, and they're all written as normal async Task methods, without any special test runners, wrappers or third-party dependencies.

  1. Change the target Framework of your unit test project to .NET 4.5.
    • Yes, you must do this even if the project references that you're testing target .NET 4.0.
  2. Remove the Microsoft.Bcl, Microsoft.Bcl.Build and Microsoft.Bcl.Async NuGet package references from your unit test project. If you haven't added these references, then simply do not add them to your unit test project.
  3. Add System.Runtime.dll and System.Threading.Tasks.dll to your unit test project as linked files in the project's root directory.
    1. Right-mouse click your unit test project in Solution Explorer.
    2. Add > Existing Item...
    3. Browse to your solution's packages folder and locate the net40 package folder for Microsoft.Bcl; e.g., ...\packages\Microsoft.Bcl.1.1.10\lib\net40\
    4. Select All Files (*.*) in the file type drop down.
    5. Holding the Ctrl key, left-mouse click System.Runtime.dll and System.Threading.Tasks.dll to select them.
    6. Click the small drop-down arrow on the Add button. (Do not click the Add button.)
    7. In the Add button's drop-down, click Add As Link. Both assemblies are now visible at the root of your project.
      • You must leave the assembly links at the root of your project. Do not move them to a subfolder.
      • If your project is under source control, then you may notice that these linked files are marked as excluded (and if they aren't, they should be.) The NuGet packages folder, where these files reside, should not be checked into source control. Since they are merely linked files, anyone pulling down your changes should have no problems at all after restoring their NuGet packages.
  4. Select both of the linked assembly files in Solution Explorer (Ctrl + Left click) or simply perform the following steps on each file separately.
  5. Right-mouse click either of the selected files and select Properties. The Properties window opens.
  6. Set the Copy to Output Directory field to Copy if newer.

Your unit test project file should now contain something similar to the following.

<ItemGroup>
  <Content Include="..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll">
    <Link>System.Runtime.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
  <Content Include="..\..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll">
    <Link>System.Threading.Tasks.dll</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

And that's it!

Just keep in mind that your unit test project targets .NET 4.5 (or a higher version, if you'd like) and so unit tests can use async methods and any other .NET 4.5 features. There shouldn't be any conflicts with the .NET 4.0 assemblies that you're testing, but if you do find conflicts, it's probably because you've redefined some types for newer Framework/C# features and made them public, thus causing conflicts when you try to use those same types in your unit tests. The best solution is to simply make those types internal to the projects you're testing.

Edit:
After following those steps, you may get some build warnings:

All projects referencing My.csproj must install nuget package Microsoft.Bcl.Build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317569
{root}\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets

To avoid these warnings, simply edit the unit test project and add the following metadata element to each project reference that points to a project that references Microsoft.Bcl.Build.

<Properties>SkipValidatePackageReferences=true</Properties>

For example:

<ProjectReference Include="..\pcl\pcl.csproj">
  <Project>{664a9e98-fac7-4567-a046-0dde95fddb48}</Project>
  <Name>pcl</Name>
  <Properties>SkipValidatePackageReferences=true</Properties>
</ProjectReference>

The full explanation can be found in the noted .targets file included with the Microsoft.Bcl.Build package. Here's the full comment, for your convenience.

BclBuildValidateNugetPackageReferences

This target validates that any Nuget packages installed in the current project are also installed in projects referencing the current project.

This is necessary because Nuget packages contain more than just simple references. Installing the package ensures
1. The right set of references for the target framework are added
2. Config file transforms are applied
3. Project installation scripts are run

For all packages listed as installed for the current project in packages config, if the package ID matches one specified in @(ValidatePackages), ensure that the same package is installed in the referencing project.

This target can be disabled for a project reference by setting SkipValidatePackageReferences=true for the reference:

<ProjectReference Include="..\pcl\pcl.csproj">
  <Project>{664a9e98-fac7-4567-a046-0dde95fddb48}</Project>
  <Name>pcl</Name>
  <Properties>SkipValidatePackageReferences=true</Properties>
</ProjectReference>

This target can be disabled for all references to a project by adding the following:

<PropertyGroup>
  <SkipValidatePackageReferences>true</SkipValidatePackageReferences>
</PropertyGroup>