Cómo un ingeniero de control de calidad ahorró un día entero al vincular AutoTests en Visual Studio y Test IT

Las herramientas de trabajo modernas del desarrollador son una docena de aplicaciones diferentes: un IDE, un sistema de prueba, varios marcos, sistemas de control de versiones y contenedores, aplicaciones de oficina y mucho más.

Y a menudo, sin darnos cuenta, pasamos un tiempo precioso transfiriendo datos de un sistema de trabajo a otro. Pero, ¿por qué no emprendemos la optimización del flujo de trabajo, incluso en cosas pequeñas? Cinco minutos, multiplicados por 5 veces al día, nos darán un total de más de un día hábil por mes, tiempo que puede pasar mucho más útil que el desempeño del trabajo de rutina. Entonces llegamos a la creación de una extensión para Visual Studio, que nos permite automatizar el proceso de creación de pruebas automáticas en nuestro sistema Test IT.

Continuando la historia de los webhooksy cómo ayudan a conectar muchos servicios en el trabajo, le presentamos nuestra historia sobre la creación de una extensión para nuestro IDE de trabajo: Visual Studio. ¡Bienvenido a cat!

Este artículo es una publicación invitada de los chicos de Test IT.



El chatbot discutido en el artículo anterior es, por supuesto, bueno, pero hasta ahora solo nos sirve para obtener información sobre el estado actual de las pruebas automáticas. Y si configuramos una integración más estrecha con el sistema Test IT, podemos expandir la lista de pruebas del proyecto actual en el portal automáticamente, sin la necesidad de agregarlas manualmente.

Anteriormente, para esto utilizamos una biblioteca desplegada rápidamente y una aplicación de consola que, al inicio, buscó la solución actual, encontró un proyecto de prueba, extrajo una lista de pruebas automáticas de su ensamblaje y las envió a nuestra API. Parece que puedes dejarlo así, porque la solución funciona. Pero postergar constantemente todo un proyecto (incluso si es un método que llama asíncronamente a la biblioteca) es muy difícil, y las dependencias eternas de los proyectos entre sí tampoco son una señal de buen gusto.

Así que llegamos a la creación de la extensión para Visual Studio, y ahora contaremos un poco sobre cómo lo hicimos.

Lado técnico


Desarrollaremos naturalmente la extensión en Visual Studio. Después de todo, nadie conoce el IDE tanto como el IDE mismo. Primero, asegúrese de que Visual Studio tenga todos los componentes necesarios para crear extensiones. Encontramos y ejecutamos el instalador de Visual Studio y verificamos el módulo "Desarrollo de extensiones de Visual Studio":



una verificación es suficiente para que podamos instalar todas las bibliotecas necesarias para el trabajo. La información sobre la instalación para el desarrollo de extensiones de Visual Studio debe ser aproximadamente la siguiente situación: La



parte preparatoria ha terminado, podemos proceder a crear un nuevo proyecto y sumergirnos en la arquitectura y estructura de la plantilla de extensión.

Ampliaremos la nueva extensión al encontrar la lista de soluciones disponibles utilizando la frase "VSIX" y eligiendo Proyecto VSIX (C #, por supuesto):



Después de la implementación, nos encontramos a primera vista por la arquitectura simple de la plantilla de extensión:



en este ejemplo, source.extension.vsixmanifest es un manifiesto común que describe las propiedades básicas de la extensión, como el nombre del producto, autor, versión, descripción, icono de extensión, versión de destino del IDE y muchos otros. Por cierto, son estas propiedades las que se ven tanto en el almacén de extensiones como en el instalador de extensiones de Visual Studio.

VSIXProject3Package.cs (máscara: {ProjectName} Package.cs), a su vez, es una clase de inicializador que registra todos los comandos y esquemas de recursos disponibles. Conoceremos a los equipos ahora.

Creemos un nuevo equipo que abra una ventana de WPF con parámetros de extensión: necesitamos almacenar datos sobre el proyecto de destino, la ubicación y el nombre de su ensamblaje, la conexión al servidor y otra información en alguna parte.

Estamos buscando un nuevo elemento, llamado Comando bastante lógico .



Muchos se preguntarán: ¿por qué usamos Command cuando la ventana de herramientas Async y la ventana de herramientas son adecuadas para la descripción de la tarea, de acuerdo con el nombre? Todo es bastante simple: al crear estos dos elementos, implementaremos un archivo de marcado en xaml, un archivo cs con el mismo nombre y también un comando. Todo estaría bien, como lo necesitamos, pero en este caso la Ventana de herramientas (no importa si es asíncrona) implementa una plantilla de ventana que se integra en Visual Studio. En la salida, obtenemos una de las ventanas, que se expande por defecto en lugar de la ventana Depuración. Por supuesto, todo esto es personalizable, pero necesitamos una ventana de terceros. Es por eso que no usamos la Ventana de herramientas, sino que agregamos una ventana WPF normal.

Creo que los detalles de crear una nueva ventana con elementos estándar pueden omitirse, ya que es aburrido, poco interesante y poco relacionado con el título del artículo. La única recomendación que puede dejar aquí: Visual Studio al agregar un nuevo elemento no le mostrará la ventana WPF en la lista de opciones, por lo que la opción más rápida y fácil es crear dicha ventana por separado del proyecto de extensión y luego transferirla al proyecto actual (no olvide arreglar el espacio nombres).



Entonces, después de crear un nuevo equipo, que llamamos OpenSettingsCommand, la legendaria generación de código de estudio crea una clase de equipo y un archivo de formato vsct que almacena tanto el marcado de nuestra extensión como la asignación de comandos y los botones que los llaman. Es muy conveniente volver a escribir el marcado en su propia implementación: creado automáticamente, colocará sus elementos en el menú "Extensiones". En este caso, reescribimos este marcado creando un grupo de dos equipos y colocándolo directamente en la barra de herramientas. Un ejemplo de un comando y marcado se puede encontrar en nuestro repositorio .

En la clase creada, podemos notar el método Execute, con el que todo comienza cuando se llama a este comando. En realidad, es aquí donde registraremos la inicialización de la nueva ventana WPF.

Envdte


Y nos acercamos sin problemas al uso del SDK de Visual Studio, a saber, la biblioteca EnvDTE. Esta biblioteca COM nos permite trabajar con objetos y elementos de Visual Studio: obtener una lista de soluciones y proyectos activos, trabajar con resaltado de sintaxis, ventanas activas, leer código de proyecto y mucho más. De hecho, si se sumerge en la documentación de esta biblioteca, puede encontrar muchas funciones útiles. En este ejemplo, lo usamos precisamente para obtener una lista de proyectos en una solución activa.

A pesar de la pequeña base de código libre, solo necesitamos unas pocas líneas de código para obtener una lista de proyectos en la solución activa:

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         

De manera tan fácil y natural, podemos recopilar información detallada sobre todos los proyectos activos.

Tome de esta lista los nombres de proyecto (propiedad Name) y la ruta a los archivos csproj (de repente, FullName): esto es suficiente para que le pidamos al usuario el proyecto deseado de la lista de posibles y lo asignemos al directorio para buscar ensamblajes.

El siguiente paso es conectar la biblioteca, cuyas tareas son analizar el ensamblaje, recopilar pruebas automáticas y publicarlas en el portal Test IT. Omitiremos los detalles de la creación de la biblioteca, pero proporcionaremos una clase interesante que puede obtener la lista de pruebas automáticas del ensamblado cargado:

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);
        }
    }

El código fuente completo de la biblioteca siempre se puede ver en nuestro repositorio .

Entonces, volvamos a la extensión. Para iniciar la lógica de nuestra biblioteca, debe agregar un nuevo comando, en el método Ejecutar del cual escribimos la llamada a la biblioteca, pasándole los atributos de las clases y métodos de prueba, así como los parámetros de extensión guardados:

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);

Nota importante: Omitimos el registrador en la biblioteca para poder escribir información técnica directamente en la ventana de salida de mensajes de Visual Studio. La biblioteca no debe estar vinculada solo a este IDE, es importante que dejemos la oportunidad de usarla en cualquier situación.

resultados


Como resultado, después del desarrollo, obtuvimos algo como esta extensión:



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



Vamos a abrir una solución de ejemplo que contiene un proyecto con pruebas unitarias, y probar nuestra extensión en él. Al cargar un proyecto, podemos notar inmediatamente un nuevo botón en el panel:



tome de inmediato la clave secreta API de su cuenta personal y prepare un nuevo proyecto en nuestra plataforma: a





continuación, volvamos a la extensión. Abramos nuestra ventana con los parámetros de extensión y completemos los campos:



Pasamos la lista de proyectos actuales al diseñador de ventanas, amablemente proporcionada por la biblioteca Visual Studio SDK, y cargamos las opciones "Project dll" después de seleccionar el proyecto: el servicio buscó todos los archivos dll en el ensamblaje del proyecto "UnitTestProject "Y nos mostró las posibles opciones de biblioteca. Guardamos la configuración y ejecutamos la funcionalidad principal de nuestra extensión.

En la ventana de salida, después de unos segundos, vemos lo siguiente:



A quién le importa cómo hicimos la salida de los mensajes: el código fuente del registrador se puede encontrar aquí .

En nuestro ejemplo, hubo 3 pruebas unitarias y 3 pruebas de integración. Suena como la verdad. Comprueba que hay un portal:



Conclusión


Hoy examinamos los conceptos básicos de la creación de extensiones para Visual Studio utilizando el ejemplo de una extensión para publicar una lista de pruebas automáticas en la plataforma Test IT. Las opciones de extensión están limitadas solo por su imaginación: puede implementar un chat con un equipo, puede crear notificaciones en caso de un colapso del proyecto en su sucursal, incluso puede, como en nuestras fantasías sobre el Marco de Bot , agregar un botón de pedido de pizza a la oficina.

¡Sueña, crea, ahorra tiempo y sigue siendo un especialista creativo!

Sobre el Autor



Mikhail Pirogovsky es un desarrollador de .NET. El material fue escrito con el equipo de Test IT. En nuestro grupo en Facebook, hablamos sobre trabajar en control de calidad, pruebas, herramientas y más.

All Articles