jcansdale / TestDriven.Net-Issues

Issue tracking for TestDriven.Net
https://github.com/jcansdale/TestDriven.Net-Issues/issues
24 stars 2 forks source link

Add support for spelunking Visual Studio services and MEF exports 'Test With > In-Proc (VS SDK)' #90

Open jcansdale opened 7 years ago

jcansdale commented 7 years ago

This functionality is now in Test Driven.Net 4.0-alpha as 'Test With > In-Proc (VS SDK)' http://testdriven.net/download.aspx

Previous versions of TestDriven.Net included support for executing tests inside the Visual Studio process using 'Test With > In-Proc'. The DTE object could be passed to the test method like this.

image

Because the tests were executed inside a new app domain, this only really worked well for pure COM objects (like DTE). Objects that interoped with .NET code would have trouble crossing the app domain barrier into the default app domain where Visual Studio's .NET code base based.

I've added a new test runner that's currently called 'Ghost', but will probably replace the 'In-Proc' test runner. This runner executes code inside the default app domain, which makes calling Visual Studio objects that use .NET possible (as well as pure COM).

image

As well as allowing the DTE object to be passed in, any number of .NET services or MEF exports can also be passed.

For service objects, the service type will be inferred from the interface name (using naming conventions and a few special cases). Please let me know if there's any I've missed!

// Test the SVsExtensionManager service
void ExtensionManager(IVsExtensionManager em)
{
    foreach (var ex in em.GetEnabledExtensions())
    {
        Console.WriteLine(ex.Header.Name);
    }
}

When passing in MEF exports, the [Import] attribute is optional but [ImportMany] is required when passing multiple exports.

// Test a MEF import
void PeekBroker([Import] IPeekBroker peekBroker)
{
    Console.WriteLine(peekBroker);
}
// Test many MEF imports
void FindAll([ImportMany] EditorOptionDefinition[] eods)
{
    foreach (var eod in eods)
    {
        Console.WriteLine($"{eod}: {eod.ValueType}");
    }
}
// MEF imports with metadata is also supported
void MarginProvider([ImportMany] Lazy<IWpfTextViewMarginProvider,
        IDictionary<string, object>>[] margins)
{
    foreach (var margin in margins)
    {
        Console.WriteLine(margin.Value);
        foreach (var kv in margin.Metadata)
        {
            Console.WriteLine($"   {kv.Key}: {kv.Value}");
        }
    }
}

If code needs to be executed on the Main thread, you can use an async method with SwitchToMainThreadAsync:

async Task FindNavigationItems(IServiceProvider sp)
{
    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    // code than requires the main thread
}

I'm planning to make the following possible as an alternative (useful for a few services that must be retrieved on the Main thread):

[STAThread]
void RunOnMainThread(IVsRequireMainThread rmt)
{
    // code than requires the main thread
}

Let me know what you think and if you find any issues. 😄

jcansdale commented 7 years ago

Here's a build that supports running on the Main thread by tagging the target method with [STAThread].

TestDriven.NET-4.0.0_Extraterrestrial_Beta3.zip

image

kzu commented 7 years ago

This feature is awesome for anyone doing VS extensibility work. Thanks!!!