How a QA engineer saved an entire day by linking AutoTests in Visual Studio and Test IT

The modern working tools of the developer are a dozen different applications: an IDE, a test system, various frameworks, version control and containerization systems, office applications, and much more.

And often we, without noticing it, spend precious time transferring data from one working system to another. But why don’t we take up the optimization of the workflow, even in small things? Five minutes, multiplied by 5 times a day, will give us a total of more than one working day per month, time that can be spent much more useful than the performance of routine work. So we came to the creation of an extension for Visual Studio, which allows us to automate the process of creating automatic tests in our Test IT system.

Continuing the story of webhooksand how they help to connect many services at work, we present to you our story about creating an extension for our working IDE - Visual Studio. Welcome to cat!

This article is a guest post from the guys at Test IT.



The chatbot discussed in the previous article is, of course, good, but so far it serves us only to obtain information about the current status of autotests. And if we set up tighter integration with the Test IT system, we can expand the list of tests of the current project on the portal automatically, without the need to add them manually.

Previously, for this we used a hastily deployed library and a console application that, at startup, looked at the current solution, found a test project, extracted a list of autotests from its assembly, and sent them to our API. It would seem that you can leave it that way, because the solution works. But constantly procrastinating an entire project (even if it is one method that asynchronously calls the library) is very difficult, and the eternal dependencies of the projects among themselves are also not a sign of good taste.

So we came to the creation of the extension for Visual Studio, and now we will tell a little about how we made it.

Technical side


We will naturally develop the extension in Visual Studio itself. After all, no one knows about the IDE as much as the IDE itself. First, make sure that Visual Studio has all the components necessary for creating extensions. We find and launch the Visual Studio Installer and check the "Development of Visual Studio Extensions" module:



One check is enough for us to install all the necessary libraries for work. The information about the installation for the development of extensions of Visual Studio should be approximately the following situation: The



preparatory part is over, we can proceed to creating a new project and immerse ourselves in the architecture and structure of the extension template.

We’ll expand the new extension by finding the list of available solutions using the phrase “VSIX” and choosing VSIX Project (C #, of course):



After the deployment, we are met at first glance by the simple architecture of the extension template:



In this example, source.extension.vsixmanifest is a common manifest that describes the basic properties of the extension, such as the product name, author, version, description, extension icon, target version of the IDE and many others. By the way, it is these properties that are viewed both in the extension store and in the Visual Studio extension installer.

VSIXProject3Package.cs (mask: {ProjectName} Package.cs), in turn, is an initializer class that registers all available commands and resource schemas. We will get to know the teams now.

Let's create a new team that opens a WPF window with extension parameters: we need to store data about the target project, the location and name of its assembly, connection to the server and other information somewhere.

We are looking for a new element, called quite logical Command .



Many may wonder: why do we use Command when the Async Tool Window and Tool Window are suitable for the description of the task, according to the name? Everything is quite simple: when creating these two elements, we will deploy a markup file to xaml, a cs file with the same name, and also a command. Everything would be fine, like we need it, but in this case the Tool Window (it doesn’t matter if it is asynchronous) deploys a window template that integrates into Visual Studio itself. At the output, we get one of the windows, which is expanded by default instead of the Debug window. Of course, all this is customizable, but we need a third-party window. That is why we do not use the Tool Window, but add a regular WPF window.

I think the details of creating a new window with standard elements can be omitted, since it is boring, uninteresting and weakly related to the title of the article. The only recommendation that you can leave here: Visual Studio, when adding a new element, will not show you the WPF window in the list of options, so the quickest and easiest option is to create such a window separately from the extension project and then transfer it to the current project (do not forget to fix the space names).



So, after creating a new team, which we called OpenSettingsCommand, the legendary studio code generation creates a team class and a vsct format file that stores both the markup of our extension and the mapping of commands and the buttons that call them. It is highly desirable to rewrite the markup on your own implementation: created automatically, it will place your elements in the "Extensions" menu. In this case, we rewrote this markup by creating a group of two teams and placing it directly on the toolbar. An example of a command and markup can be found in our repository .

In the created class, we can notice the Execute method, with which everything starts when this command is called. Actually, it is here that we will register the initialization of the new WPF-window.

Envdte


And we smoothly approached the use of the Visual Studio SDK, namely, the EnvDTE library. This COM library allows us to work with objects and elements of Visual Studio: get a list of active solutions and projects, work with syntax highlighting, active windows, read project code and much more. In fact, if you plunge into the documentation of this library, you can find for yourself quite a lot of useful functions. In this example, we use it precisely to get a list of projects in an active solution.

Despite the small free code base, we only need a few lines of code to get a list of projects in the active solution:

ThreadHelper.ThrowIfNotOnUIThread();
var activeVS = (DTE)Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE))
 ?? throw new InvalidOperationException("DTE not found");
var activeProjects = new List<Project>();
activeProjects.AddRange(activeVS.Solution.Projects.Cast<Project>());
//  , activeProjects         

So easily and naturally we can collect detailed information about all active projects.

Take from this list the names of the projects (property Name) and the path to the csproj files (suddenly, FullName): this is quite enough for us to ask the user the desired project from the list of possible ones and map it to the directory for searching assemblies.

The next step is to connect the library, the tasks of which are to analyze the assembly, collect autotests and publish them on the Test IT portal. We’ll omit the details of creating the library, but we’ll provide an interesting class that can get the list of autotests from the loaded assembly:

public class AutotestsService
    {
        public IList<AutotestModel> GetAutotestsFromAssembly<TTestClassAttribute, TTestMethodAttribute>(Assembly assembly, Guid projectId, string repositoryLink)
            where TTestClassAttribute : Attribute
            where TTestMethodAttribute : Attribute
        {
            MethodInfo[] testMethods = GetAutotestFromAssembly<TTestClassAttribute, TTestMethodAttribute>(assembly);

            List<AutotestModel> allModels = new List<AutotestModel>();
            foreach (MethodInfo method in testMethods)
            {
                AutotestModel autotest = new AutotestModel()
                {
                    ExternalId = method.Name,
                    LinkToRepository = repositoryLink,
                    ProjectId = projectId,
                    Name = GetAutotestName(method.Name),
                    Classname = method.DeclaringType.Name,
                    Namespace = GetAutotestNamespace(method)
                };

                allModels.Add(autotest);
            }

            return allModels;
        }

        private static MethodInfo[] GetAutotestFromAssembly<TTestClassAttribute, TTestMethodAttribute>(Assembly assembly)
            where TTestClassAttribute : Attribute
            where TTestMethodAttribute : Attribute
        {
            return assembly.GetTypes()
                .Where(c => c.IsDefined(typeof(TTestClassAttribute)))
                .SelectMany(t => t.GetMethods())
                .Where(m => m.IsDefined(typeof(TTestMethodAttribute)))
                .ToArray();
        }

        private string GetAutotestName(string autotestExternalId)
        {
            StringBuilder autotestName = new StringBuilder();

            for (int i = 0; i < autotestExternalId.Length; i++)
            {
                if (char.IsUpper(autotestExternalId[i]) && i != 0)
                    autotestName.Append(' ');
                autotestName.Append(autotestExternalId[i]);
            }

            return autotestName.ToString();
        }

        private string GetAutotestNamespace(MethodInfo testMethod)
        {
            return testMethod.DeclaringType.FullName
                .Replace($".{testMethod.DeclaringType.Name}", string.Empty);
        }
    }

The entire source code of the library can always be viewed in our repository .

So, back to the extension. To start the logic of our library, you need to add a new command, in the Execute method of which we write the library call, passing it the attributes of the test classes and methods, as well as the saved extension parameters:

var settings = Properties.Settings.Default;
var executor = new LinkExecutor();
await executor.Execute<TestClassAttribute, TestMethodAttribute>(
    settings.Domain,
    settings.SecretKey,
    settings.ProjectNameInTestIT,
    settings.RepositoryLink ?? string.Empty,
    settings.AssemblyPath,
    Logger);

Important note: We skip logger into the library to be able to write technical information directly to the Visual Studio message output window. The library should not be tied only to this IDE, it is important for us to leave the opportunity to use it in any situation.

results


As a result, after development, we got something like this extension:



. , Visual Studio, , Visual Studio Visual Studio Visual Studio Visual Studio. , .



Let's open an example solution containing a project with Unit tests, and test our extension on it. When loading a project, we can immediately notice a new button on the panel:



Immediately take the secret API key from your personal account and prepare a new project on our platform:





Next, let's return to the extension. Let's open our window with the extension parameters and fill in the fields:



We passed the list of current projects to the window designer, kindly provided by the Visual Studio SDK library, and we loaded the “Project dll” options after selecting the project: the service looked for all the dll files in the assembly of the UnitTestProject project ”And showed us possible library options. We save the settings and run the main functionality of our extension.

In the output window, after a few seconds, we see the following:



Who cares how we made the output of messages - the source code of the logger can be found here .

In our example, there were 3 Unit tests and 3 integration tests. Sounds like the truth. Check that there is a portal:



Conclusion


Today we examined the basics of creating extensions for Visual Studio using the example of an extension for publishing a list of autotests on the Test IT platform. Extension options are limited only by your imagination: you can implement a chat with a team, you can create notifications in case of a project breakdown on your branch, you can even add a pizza order button to the office , as in our fantasies about the Bot Framework .

Dream, create, save time and always remain creative specialists!

about the author



Mikhail Pirogovsky is a .NET developer. The material was written with the Test IT team. In our group on Facebook, we talk about working in QA, testing, tools and more.

All Articles