Did you watch the Australian Open Final? It was a great game. At the beginning I thought Hewitt had it all figured out. With Safin not able to capitalize on his fast and impeccable services, I was putting my money on Hewitt (figuratively speaking). I am glad I did not do it for real. Anyway it was a great game and ultimately the best player won. 25 aces were served up in this game and 72%(18/25) came from Safin. Moral of the story if you get your services right you are promised a good game even if it means the player you support looses.
In Visual Studio you can sort of apply the same theory. When your Visual Studio packages are proffering services that other packages can use ensure that they are top notch services. So how do you make them top notch and law abiding citizens of Visual Studio? Read on to see what some considerations you need to make and how you can verify that these recommendations are adhered to. Remember that these are just recommendations and not enforced. If you have already released your products with some issues you don’t have to change it unless you really want to. Before I delve further lets just detour and see what services are?
Visual Studio services are the primary means by which functionality is shared among VS Packages and between the environment and its installed VS Packages. A service is a contract for a set of one or more interfaces implemented by a COM object. Visual Studio offers two layers of services: environment-level services and package-level services. Environment-level services are proffered natively by the Visual Studio environment and are available environment-wide. These include services proffering basic environment functionality (e.g. SVsShell), selection tracking and persistence management (e.g SVsShellMonitorSelection and SVsRunningDocumentTable), and other basic services. In addition to environment level services, VS Packages share functionality by proffering services of their own. These package-level services include functionality proffered by core application VS Packages, such as Visual Basic .NET and Visual C++ .NET, but they can also include services proffered by third-party VS Packages.
When designing services for Visual Studio you need to ensure that they abide by certain rules that the Visual Studio team recommends.
§ Services should be implemented as Singletons so querying for the same service more than once should give back the same instance.
§ You should be able to query the service for IUnknown.
§ You should be able to query the service for all supported interfaces, and specifically return E_NOINTERFACE if the interface requested is not supported.
§ If the service can be availed from a different thread make sure you have registered an appropriate marshalling component. Almost all services provided by Visual Studio authored by the Visual Studio team register a proxy stub component.
Our proposal is to get a list of services from the development team. The development team should additionally be able to provide following information:
§ All the interfaces that can be obtained from the service
§ What state the IDE should be in before the service is available.
§ Once you have all the details. You now need a testing framework in place to invoke and test the services. See below in the Design section for Framework components.
§ Service Wrapper Interfaces
§ You need service wrapper interfaces. Define these public interfaces that the test cases can use to exercise a targeted service. The method signatures should closely shadow (but not duplicate) the actual service methods. The method signatures should be in terms of more inherent data types as opposed to VSIP types. This ensures that the test case has no dependency on any VSIP level component. Splitting your service wrapper interface into a separate type library or managed assembly allows you to consume this both in the package code and test case. Ensure that these interfaces are registered for COM or COM Interop if using managed assembly.
§ Additionally add an interface that defines the common functionality that is to be tested for on all services. The service test automation object should minimally implement this interface. See code below:
§ If you are using managed code make sure you register the type information for COM Interop. Use regasm.exe with /codebase and /tlb flags.
Visual Studio Service Common Verification (C#)
/// This interface is implemented by the root wrapper object that
/// all other proxies derive from. All the methods will return an
/// appropriate HRESULT
public interface ITestServiceCommon
//This method verifies that you can query for the service
//This method verifies that the service instance returned by querying it
//always the same instance.
//This method verifies if you can QI back for the service from the IUnknown that
//you get from the service.
//This method verifies you can get to all the interfaces that are supported by the
Visual Studio Service Specific Verification (C#)
/// This interface is implemented by the wrapper automation objects
/// that invokes and verifies the SVsToolsOptions service particularly new
/// methods added to the 8.0 version. All the methods return an appropriate
public interface ITestSVsCodeDefView
//Verifies if you can show the Code definition tool window
//Verifies if you can hide the Code def tool window
//Verifies if the tool window is visible
//Sets a text file as context for a custom editor registered by the service package.
//Gets the refresh delay for context to update
int GetRefreshDelay(out ulong pcMilliseconds);
//Forces the idle processing to happen
//Verifies if the given tool window IVsTextView is code def view. The implementation
//uses CodeDef window's text view.
int IsCodeDefView(out int pfIsCodeDefView);
//uses a text view other than the code def window itself.
int IsCodeDefView2(out int pfIsCodeDefView);
§ Service Test Pacakge
§ Next you will need a way to return an appropriate automation object that your test scripts can drive. So you will need a test Visual Studio package that gets loaded when your test script requests a service test helper object via call to DTE.GetObject.
§ You need to create a component that implements your equivalent of ITestServiceCommon and service specific wrapper interface. This is the object that gets returned when service test helper object is requested from IVsPacakge::GetAutomationObject implementation. See sample code snippet below. Don’t forget to reference the service wrapper typelibrary or assembly.
Visual Studio Service Test Automation Helper (C#)
/// This is the automation object returned by the package to test the SVsCodeDefView
/// service. If you want to add methods to it first add it in the ITestSVsCodeDefView interface
/// then implement it here. Testcase will get this object so if you need to make sure
/// that the interface is only exposing intrinsic data types as opposed to types that
/// are actually required by the real service.
public class SVsCodeDefViewWrapper : ITestServiceCommon, ITestSVsCodeDefView
<<……Implement interface methods>>
§ Test Case
§ The test case should just reference the service wrapper typelibrary or assembly.
§ Get the DTE global automation object from the running instance of Visual Studio.
§ Call DTE.GetObject(<STRING_IDENTIFIER_SERVICE_WRAPPER>) cast it to the ITestServiceCommon or service specific interfaces and call all the methods.
This is what the Visual Studio test team uses to test Visual Studio services. Hope you can leverage this and ship a great product.