NET nanoFramework | nanoFramework Documentation

¿Te gustaría aprender .NET Empresarial Desde Cero?
Tenemos el diplomado que necesitas. ¡Haz clic aquí!

Arquitectura de .NET nanoFramework

Primero, una introducción a la arquitectura .NET nanoFramework. Esto es .NET C # ejecutándose directamente sobre el metal en pequeños chips como ESP32. Cuando escribo pequeño , piense en SRAM de 520 Kb, procesador de reloj de 240 MHz, pocos Mb de memoria flash. Y eso es para todo: el código, la asignación de memoria, todo.

.NET nanoFramework incrusta un nano CLR para cada hardware compatible que interpretará y ejecutará su código compilado .NET. También encontrará bibliotecas de clases, como System.Text. Como puede adivinar, las bibliotecas son versiones mínimas en comparación con las que puede encontrar en .NET 5. Mínimo, lo que significa que un subconjunto de la API completa está disponible, solo las más comunes, menos métodos y constructores sobrecargados. Hay bibliotecas de clases adicionales para proporcionar acceso a GPIO, SPI, I2C. Las API para estos protocolos son muy similares a las definidas y ofrecidas por .NET IoT , lo que ayuda a los desarrolladores a reutilizar código desde .NET 5, por ejemplo, a .NET nanoFramework.

Como desarrollador, tiene la experiencia de desarrollo completa a la que está acostumbrado con Visual Studio 2019 a través de una extensión .NET nanoFramework que tendrá que instalar para admitir el sistema de proyecto específico, la compilación, la depuración y la implementación en los dispositivos.

Arquitectura nanoFramework

No existe un sistema operativo real per se en esos pequeños procesadores, solo un sistema operativo en tiempo real (RTOS) para subprocesos y elementos básicos como Azure RTOS . Cada fabricante de chips es libre de elegir los RTOS que admite y .NET nanoFramework admite una gran variedad de ellos.

Cada plataforma específica se basa en un C SDK nativo de Espressif, STM, NXP o TI, por ejemplo. Esos SDK se representan en la arquitectura como capa de abstracción de hardware . Luego, una segunda capa llamada Capa de abstracción de plataforma, también completamente escrita en C, permite crear una API estándar para el nano CLR mientras se conecta con cada SDK de HAL específico.

La mayoría de las plataformas deben actualizarse con un cargador de arranque específico y también las capas inferiores, incluidos los controladores nano CLR, HAL, PAL y potenciales específicos. Algunas de las plataformas ya tienen su propio gestor de arranque, por lo que este no es un componente obligatorio.

El resto de las capas superiores donde se encuentran las clases de ensamblajes clásicos, incluido mscorlib, son ensamblajes de C # y se pueden actualizar a través de Visual Studio con solo presionar F5 para ejecutar su aplicación. Es importante agregar que existe una posibilidad de interoperabilidad nativa con el componente C / C ++. La parte nativa deberá implementarse como el resto del código C. El código nativo expone las funciones a su código C #.

Para compilar el código C #, se utiliza la cadena de compilación normal para .NET C # produciendo archivos DLL / EXE y PDB que son similares a una compilación de .NET 5 C #. El archivo PDB contiene la información de depuración. Luego, se realiza una operación posterior a la compilación para transformar la DLL / EXE en un archivo ejecutable portátil ligero (PE) y el archivo PDB en un archivo PDBX. Este archivo PE es el que se carga en el dispositivo y se ejecuta. Por supuesto, puede cargar varios archivos PE, un ejemplo típico sería un ejecutable y las bibliotecas dependientes, incluido mscorlib. Es importante tener esto en cuenta, entenderemos por qué más adelante.

La experiencia que tiene como desarrollador al ejecutar el código y depurar en el dispositivo es la misma que para cualquier aplicación .NET. Puede establecer un punto de interrupción, obtener los datos variables, intervenir, establecer la siguiente declaración.

Extensión nanoFramework Visual Studio con depuración

Los problemas a resolver para una plataforma de prueba unitaria adecuada

Volviendo a lo que queremos como desarrolladores para ejecutar nuestras pruebas, hay bastantes problemas que resolver para .NET nanoFramework:

  • ¿Cómo ejecutar pruebas donde necesitas descubrir el código en nano?
  • ¿Cómo ejecutar pruebas en una máquina con Windows?
  • ¿Cómo ejecutar pruebas en un dispositivo real?
  • ¿Cómo tener funciones que pueda llamar antes y después de la prueba? Normalmente, para configurar el hardware correctamente.
  • ¿Cómo ejecutar la prueba tanto en la máquina con Windows como en el hardware exactamente de la misma manera?
  • ¿Cómo integrar todo esto con Visual Studio 2019?
  • ¿Cómo ejecutar pruebas en una tubería para el aseguramiento de la calidad?
  • ¿Cómo tener una cobertura de código de prueba adecuada?
  • ¿Cómo hacerlo tan simple como crear un proyecto de prueba de xUnit en Visual Studio?

El marco de prueba

Si bien esos problemas son genéricos, aplicados a .NET nanoFramework, podemos ver lo que estaba disponible como bloques de construcción para usar.

El primero es el hecho de que .NET nanoFramework tiene algunas capacidades System.Reflection con soporte para atributos simples. Por lo tanto, la decoración familiar y de otro método podría usarse como un mecanismo para reconocer qué métodos contienen pruebas.[Fact]

Por supuesto, hay soporte para el manejo de excepciones y excepciones, por lo que también se pueden usar las funciones familiares de Assert. Entonces, juntando todo, el modelo de tener una función nula que ejecutará la prueba y generará una excepción si hay un resultado inesperado funcionará.

Para manejar el hecho de que algunos métodos deben ejecutarse en la configuración y limpiar el dispositivo al final, se puede manejar con atributos específicos.

Podemos tener el siguiente ejemplo cuando todo está reunido para esta parte de la resolución de cómo se verá el marco de prueba:

using nanoFramework.TestFramework;
using System;
using System.Diagnostics;

namespace NFUnitTest1
{
    [TestClass]
    public class Test1
    {
        static private int _global = 0;
        static private int[] _array;

        [TestMethod]
        public void TestMethod1()
        {
            Assert.NotEqual(0, _global);
            Assert.Equal(_array[0], 1);
            Assert.Throws(typeof(ArgumentNullException), () =>
            {
                Debug.WriteLine("Throwing a ArgumentNullException");
                throw new ArgumentNullException();
            });
            string str = "nanoFramework Rocks!";
            Assert.StartsWith("nano", str);
        }

        [TestMethod]
        public void AnotherTest()
        {
            Assert.True(false, "This is on purpose");
        }

        [Setup]
        public void SetupTest()
        {
            _global = 42;
            _array = new int[3] { 1, 4, 9 };
            Assert.Equal(42, _global);
        }

        [Cleanup]
        public void LetsClean()
        {
            Assert.NotEmpty(_array);
            _array = null;
            Assert.Null(_array);
        }
    }
}

Reutilizaremos este ejemplo más adelante y veremos cómo ejecutarlo en la máquina de desarrollo de Windows o en un dispositivo, recopilar los resultados, mostrarlos en Visual Studio 2019 y tener información de cobertura de código.

El lanzador de pruebas unitarias

La pregunta clave ahora es encontrar una manera de lanzar esas pruebas. Para esta parte, usaremos el reflejo presente en .NET nanoFramework y capturaremos todas las posibles excepciones para verificar si una prueba pasa o falla.

La parte central del lanzador se ve así:

Assembly test = Assembly.Load("NFUnitTest");

Type[] allTypes = test.GetTypes();

foreach (var type in allTypes)
{
    if (type.IsClass)
    {
        var typeAttribs = type.GetCustomAttributes(true);
        foreach (var typeAttrib in typeAttribs)
        {
            if (typeof(TestClassAttribute) == typeAttrib.GetType())
            {
                var methods = type.GetMethods();
                // First we look at Setup
                RunTest(methods, typeof(SetupAttribute));
                // then we run the tests
                RunTest(methods, typeof(TestMethodAttribute));
                // last we handle Cleanup
                RunTest(methods, typeof(CleanupAttribute));
            }
        }
    }
}

private static void RunTest(
    MethodInfo[] methods,
    Type attribToRun)
{
    long dt;
    long totalTicks;

    foreach (var method in methods)
    {
        var attribs = method.GetCustomAttributes(true);

        foreach (var attrib in attribs)
        {
            if (attribToRun == attrib.GetType())
            {
                try
                {
                    dt = DateTime.UtcNow.Ticks;
                    method.Invoke(null, null);
                    totalTicks = DateTime.UtcNow.Ticks - dt;

                    Debug.WriteLine($"Test passed: {method.Name}, {totalTicks}");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"Test failed: {method.Name}, {ex.Message}");
                }

            }
        }
    }
}

En resumen, el lanzador carga un ensamblado que debe llamarse NFUnitTest, luego encuentra todas las clases de prueba, todos los métodos de prueba y los ejecuta todos. Primero todos los de Configuración, luego los de TestMethod y finalmente los de Limpieza. Hasta ahora, se ha optado por imponer NFUnitTest como el nombre del ensamblado que contendrá las pruebas. La reflexión presente en .NET nanoFramework no permite aún encontrar todos los ensamblajes posibles y probarlos todos. Tampoco existe la posibilidad de pasar algunos argumentos de la línea de comandos a la función principal. Esos son elementos que estamos buscando implementar en el futuro.

Mientras se carga el ensamblaje NFUnitTest , el nano CLR también carga todos los ensamblajes dependientes necesarios. Por lo tanto, las pruebas pueden cubrir tantas dependencias como desee y se pueden cargar en el dispositivo. Veremos más adelante sobre el desafío de encontrarlos y cargarlos en el dispositivo o ejecutarlos en una máquina Windows normal.

Debug.WriteLine se utiliza para generar los resultados de las pruebas. Es una forma bastante simple y viene con algunos desafíos: tendremos que asegurarnos de que podemos recopilar la salida del dispositivo. Como desafío, se realiza una optimización mientras se compila .NET en modo de lanzamiento, todos los Debug. * Se eliminan puramente de la compilación. El mismo comportamiento se aplica a .NET nanoFramework ya que la cadena de herramientas utilizada es la misma. Por lo tanto, este componente específico siempre debe compilarse y distribuirse como una versión de depuración para asegurarse de que Debug. * Siempre esté disponible. El otro desafío está en el lado del análisis, recopilar la información correcta y asegurarse de que el resto del contenido no utilice este patrón.

La segunda parte, y más complicada, es poder recopilar esta salida desde el dispositivo y en una máquina con Windows. La buena noticia aquí es que ya existe este mecanismo distribuido con la extensión .NET nanoFramework para Visual Studio 2019.

Aplicación nanoCLR Win32

Si bien expliqué que existe un mecanismo para cargar y ejecutar código, recopilar la información de depuración en un dispositivo, todavía no expliqué cómo ejecutar .NET nanoFramework en una máquina con Windows. Los ensamblados no pueden simplemente cargarse incluso en un dominio de aplicación diferente y ejecutarse. Eso fallará porque .NET nanoFramework tiene su propia biblioteca de clases base y CLR, sin mencionar HAL y PAL.

De manera similar, con lo que sucede con cualquiera de las plataformas de hardware que admitimos, tenemos una compilación para Windows 32. Incluye un CLR, BCL y otros espacios de nombres. El mecanismo de carga del ensamblaje es ligeramente diferente al que se ejecuta en un microcontrolador, pero, aparte de eso, todo el resto es exactamente el mismo código. La compilación de Windows es un ejecutable que acepta parámetros desde la línea de comandos, como cualquier aplicación de consola típica, y así es como se cargan los ensamblados.

Esto hace que sea muy conveniente de usar en escenarios como ejecutar pruebas unitarias, usar en la canalización de Azure DevOps y otros similares.

Extensibilidad del adaptador de Visual Studio

Visual Studio Test ofrece extensibilidad para descubrir, ejecutar las pruebas y recopilar los resultados en Visual Studio a través de la extensibilidad del adaptador . También se puede utilizar desde la línea de comandos o en una canalización con el ejecutable vstest.console.exe .

Mirando la arquitectura de destino y resumiendo lo que hemos visto antes, ahora tenemos lo siguiente:

Arquitectura de prueba unitaria nanoFramework

A la derecha, la aplicación nanoCLR Win32 que se carga con el lanzador de pruebas unitarias, el Marco de pruebas y, por supuesto, los ensamblajes a probar. La salida va a la consola.

El TestAdapter debería poder recopilar el resultado de la ejecución del ensamblado de prueba y procesarlo. Ante esto, el primer desafío es descubrir las pruebas. Veamos cómo se hace esto.

Prueba de descubrimiento

La interfaz ITestDiscover tiene un método DiscoverTests que obviamente está aquí para descubrir las pruebas. Como puede recordar de la arquitectura .NET nanoFramework, el código C # se compila a través de la canalización de compilación normal que produce un archivo DLL / EXE y PDB.

public static List<TestCase> FindTestCases(string source)
{
    List<TestCase> testCases = new List<TestCase>();

    var nfprojSources = FindNfprojSources(source);
    if (nfprojSources.Length == 0)
    {
        return testCases;
    }

    var allCsFils = GetAllCsFileNames(nfprojSources);

    Assembly test = Assembly.LoadFile(source);
    AppDomain.CurrentDomain.AssemblyResolve += App_AssemblyResolve;
    AppDomain.CurrentDomain.Load(test.GetName());

    Type[] allTypes = test.GetTypes();
    foreach (var type in allTypes)
    {
        if (type.IsClass)
        {
            var typeAttribs = type.GetCustomAttributes(true);
            foreach (var typeAttrib in typeAttribs)
            {
                if (typeof(TestClassAttribute).FullName == typeAttrib.GetType().FullName)
                {
                    var methods = type.GetMethods();
                    // First we look at Setup
                    foreach (var method in methods)
                    {
                        var attribs = method.GetCustomAttributes(true);

                        foreach (var attrib in attribs)
                        {
                            if (attrib.GetType().FullName == typeof(SetupAttribute).FullName ||
                            attrib.GetType().FullName == typeof(TestMethodAttribute).FullName ||
                            attrib.GetType().FullName == typeof(CleanupAttribute).FullName)
                            {
                                var testCase = GetFileNameAndLineNumber(allCsFils, type, method);
                                testCase.Source = source;
                                testCase.ExecutorUri = new Uri(TestsConstants.NanoExecutor);
                                testCase.FullyQualifiedName = $"{type.FullName}.{testCase.DisplayName}";
                                testCase.Traits.Add(new Trait("Type", attrib.GetType().Name.Replace("Attribute","")));
                                testCases.Add(testCase);
                            }
                        }
                    }

                }
            }
        }
    }

    return testCases;
}
private static Assembly App_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string dllName = args.Name.Split(new[] { ',' })[0] + ".dll";
    string path = Path.GetDirectoryName(args.RequestingAssembly.Location);
    return Assembly.LoadFrom(Path.Combine(path, dllName));
}

Para que sea más fácil de procesar al principio, hay algunas convenciones que tendremos que seguir para el proyecto de pruebas. Todos los proyectos de .NET nanoFramework son archivos nfproj, no archivos csproj. La razón se debe a la falta de Target Framework Moniker (TFM). La segunda convención utilizada es que los directorios binRelease o binDebug deben ser hijos de esos archivos nfproj.

El descubrimiento ocurre con los archivos de origen que se pasan al Adaptador de prueba como nombres de ruta completos. Para identificar un proyecto .NET nanoFramework, se busca un archivo nfproj en el árbol de directorios y se buscan todos los archivos cs asociados. Esas convenciones simplifican el proceso de búsqueda.

Ahora, a pesar de que .NET nanoFramework tiene su propio mscorlib, sigue siendo un ensamblado .NET, por lo tanto, podemos aplicar algo de la magia de .NET: ¡usando la reflexión sobre los ensamblajes nanoFramework para descubrir posibles pruebas!

Debido a que la versión de mscorlib es diferente a la que se ejecuta en el código principal, primero debemos crear un dominio de aplicación, cargando el ensamblado y sus dependencias. Y aquí, hay otro truco que es más conveniente para lo que estamos tratando de lograr: al construir un ensamblado de nanoFramework .NET, todas sus dependencias entran en la carpeta de compilación. Cargando las dependencias, es una simple cuestión de cargar cada ensamblado que está presente en esta carpeta. Eso simplifica mucho el proceso.

Una vez que haya cargado el ensamblaje, puede usar la reflexión y descubrir todos los métodos posibles a través de los atributos de prueba.

En esta etapa, también es un poco complicado encontrar los números de línea para cada prueba específica. Según la convención anterior, todos los archivos CS son parte de un subdirectorio y, como para algunos de los otros marcos de pruebas de .NET, se realiza un análisis de archivos para encontrar el número de línea correcto. Este es un compromiso necesario ya que realmente no podemos ejecutar el código .NET nanoFramework en el dominio de la aplicación. Eso fallará debido a la falta de HAL y PAL.

La lista de pruebas se puede devolver a Visual Studio. Y tomando nuestro ejemplo anterior, tendremos esto en la ventana del Explorador de pruebas:

descubrimiento de prueba nanoFramework

Los rasgos se utilizan para identificar el tipo de métodos de prueba y facilitar la búsqueda de los de configuración y limpieza.

Ejecutor de prueba

La interfaz ITestExecutor es aquella a través de la cual se ejecutan las pruebas y se emiten los resultados. Las pruebas se pueden iniciar a través de Visual Studio o desde la herramienta de línea de comandos. Cuando se inicia a través de la línea de comando, el descubrimiento debe ocurrir primero. Cuando se ejecuta a través de Visual Studio 2019, solo se debe ejecutar la parte de las pruebas, el descubrimiento se realiza a través de la extensibilidad del adaptador.

La complejidad aquí es múltiple y diferentes de las pruebas se deben ejecutar en el dispositivo o en la aplicación nanoCLR Win32. En cualquiera de estos casos, encontrar todos los archivos PE y cargarlos no es un gran desafío, ya que están todos en el mismo directorio. Pero espere, aquí hay algo importante: no puede ejecutar nanoCLR en un HAL / PAL que no esté diseñado para ello. La razón es que las llamadas entre el código C # y sus contrapartes nativas tienen que coincidir, de lo contrario sucederán cosas malas. Recuerde, el C # “conectado” al código C a través de esa capa de interoperabilidad. Cambiar una de las firmas de función y la llamada a su contraparte fallará o producirá resultados inesperados. Entonces es importante verificar esas versiones específicas especialmente para el hardware. Pero hemos decidido hacer una excepción (hasta cierto punto) para la aplicación nanoCLR Win32.

El desafío específico con nanoCLR Win32 es encontrar la aplicación y poder cargar siempre la última versión para asegurarnos de que siempre estamos ejecutando la última versión de HAL / PAL. Comprenderá el mecanismo de distribución general al leer la sección NuGet. El paquete NuGet proporciona el lanzador de pruebas unitarias, el marco de pruebas y una versión de nanoCLR Win32.

El mecanismo utilizado se basa en la posibilidad, ya que estamos usando la misma cadena de compilación que cualquier otro código .NET C #, de tener objetivos específicos. Encontrará el archivo de destinos en el repositorio . Mientras construye, el sistema verificará si la última versión está presente y la instalará si es necesario.

Si bien será demasiado largo explicar en detalle todo el mecanismo implementado para cargar el código en el dispositivo, ejecutarlo y recopilar los resultados a través del canal de depuración, desde el código , los pasos son los siguientes: descubrir el dispositivo, depurar y la conexión a un dispositivo se realiza por puerto serie.

Cada dispositivo se actualiza con un cargador de arranque y el nanoCLR. Responden a un paquete de “ping”. Una vez que se consulta un dispositivo y responde correctamente, es seguro asumir que tenemos un dispositivo nanoFramework .NET válido en el otro extremo y que está ejecutando el motor de depuración. El siguiente paso es borrar el área de implementación de la memoria del dispositivo y verificar que el dispositivo vuelva a su estado inicializado.

Una vez alcanzado este estado, comprobamos las versiones de todos los ensamblajes y la compatibilidad con el dispositivo. Esto se hace descompilando el DLL / EXE y verificando la versión. Una vez más, estamos usando el truco de que .NET nanoFramework es código .NET real y se construye con la cadena de compilación normal.

foreach (string assemblyPath in allPeFiles)
{
    // load assembly in order to get the versions
    var file = Path.Combine(workingDirectory, assemblyPath.Replace(".pe", ".dll"));
    if (!File.Exists(file))
    {
        // Check with an exe
        file = Path.Combine(workingDirectory, assemblyPath.Replace(".pe", ".exe"));
    }

    var decompiler = new CSharpDecompiler(file, decompilerSettings); ;
    var assemblyProperties = decompiler.DecompileModuleAndAssemblyAttributesToString();
    // read attributes using a Regex
    // AssemblyVersion
    string pattern = @"(?<=AssemblyVersion("")(.*)(?="")])";
    var match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase);
    string assemblyVersion = match[0].Value;
    // AssemblyNativeVersion
    pattern = @"(?<=AssemblyNativeVersion("")(.*)(?="")])";
    match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase);
    // only class libs have this attribute, therefore sanity check is required
    string nativeVersion = "";
    if (match.Count == 1)
    {
        nativeVersion = match[0].Value;
    }
    assemblyList.Add(new DeploymentAssembly(Path.Combine(workingDirectory, assemblyPath), assemblyVersion, nativeVersion));
}

El siguiente paso es cargar los ensamblados en el dispositivo e iniciar el proceso de depuración. Esto encontrará automáticamente el lanzador de pruebas unitarias que iniciará el mecanismo. Lo que queda es recopilar la salida del motor de depuración para un análisis más detallado:

device.DebugEngine.OnMessage += (message, text) =>
{
    _logger.LogMessage(text, Settings.LoggingLevel.Verbose);
    output.Append(text);
    if (text.Contains(Done))
    {
        isFinished = true;
    }
};

En el lado nanoCLR Win32, el ejecutable debe encontrarse en las diversas rutas según la descripción anterior, y la compilación garantizará que se instalen las últimas versiones. Todos los ensamblados deben cargarse como argumentos en la línea de comando. Ahora, como se trata de un proceso externo, se utiliza una clase de proceso para capturar los flujos de salida y error que provienen del ejecutable nanoCLR.

private Process _nanoClr;
_nanoClr = new Process();
_nanoClr.StartInfo = new ProcessStartInfo(nanoClrLocation, parameter)
{
    WorkingDirectory = workingDirectory,
    UseShellExecute = false,
    RedirectStandardError = true,
    RedirectStandardOutput = true
};
_nanoClr.OutputDataReceived += (sender, e) =>
{
    if (e.Data == null)
    {
        outputWaitHandle.Set();
    }
    else
    {
        output.AppendLine(e.Data);
    }
};

_nanoClr.ErrorDataReceived += (sender, e) =>
{
    if (e.Data == null)
    {
        errorWaitHandle.Set();
    }
    else
    {
        error.AppendLine(e.Data);
    }
};

_nanoClr.Start();

_nanoClr.BeginOutputReadLine();
_nanoClr.BeginErrorReadLine();
// wait for exit, no worries about the outcome
_nanoClr.WaitForExit(runTimeout);

El código es lo que necesita crear, capturar el resultado e iniciar el proceso. Preste atención al método WaitForSalir que esperará a que el proceso salga o lo matará después de que se agote el tiempo de espera. Esto es algo importante a tener en cuenta y discutiremos esta parte cuando analicemos el. sección runsettings .

En ambos casos, una vez que se completa la ejecución de la prueba, es necesario analizar la cadena de salida. El analizador extraerá los mensajes “Prueba aprobada:”, “Prueba fallida:”, los nombres de los métodos, la hora y cualquier excepción presente allí, junto con todo lo que se haya generado en la sección de depuración para que sea muy fácil de usar para los desarrolladores.

Una vez que presione el botón de reproducción en la ventana Explorador de pruebas, obtendrá rápidamente los resultados de la prueba de nuestro caso de prueba:

La prueba de nanoFramework falló

Para cada caso de prueba, obtendrá la vista detallada:

Detalles de la prueba nanoFramework

Y los detalles:

La prueba nanoFramework detalla los resultados

Y, por supuesto, como puede esperar, la anotación en el código para las pruebas aprobadas y perdidas. Obtendrá esto para todas las clases en las que tenga pruebas para todas las fuentes que Visual Studio descubrirá siguiendo la ruta del código.

Cobertura del código de prueba nanoFramework

archivos .runsettings

Un buen mecanismo también es el archivo .runsettings que se puede usar para pasar configuraciones específicas al Adaptador de prueba. El archivo se ve así en nuestro caso:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
   <!-- Configurations that affect the Test Framework -->
   <RunConfiguration>
       <MaxCpuCount>1</MaxCpuCount>
       <ResultsDirectory>.TestResults</ResultsDirectory><!-- Path relative to solution directory -->
       <TestSessionTimeout>120000</TestSessionTimeout><!-- Milliseconds -->
       <TargetFrameworkVersion>Framework40</TargetFrameworkVersion>
   </RunConfiguration>
   <nanoFrameworkAdapter>
       <Logging>None</Logging>
       <IsRealHardware>False</IsRealHardware>
   </nanoFrameworkAdapter>
</RunSettings>

Hay algunos trucos en este archivo. Primero, la versión del marco de destino se establece en Framework40 . Como esta cadena de herramientas de compilación se utiliza para compilar el código nanoFramework, esto activará el descubrimiento de Visual Studio.

El segundo truco es el tiempo de espera de la sesión, algunas pruebas, como algunas pruebas de subprocesos que estamos ejecutando, pueden ser largas y luego puede jugar con esta configuración para evitar que se detengan las pruebas. También es el mecanismo utilizado para hacer que la ejecución de la prueba se detenga internamente.

La sección nanoFrameworkAdapter contiene la configuración específica que se puede pasar al Adaptador de prueba. Aquí es donde puede ajustar IsRealHardware de False a True para ejecutarlo desde Win32 CLR o en un dispositivo real. Eso es lo único que necesita cambiar. Presionar F5 o ejecutarlo en una línea de comando o en una tubería será exactamente el mismo proceso y con total transparencia con el Win32 CLR o el dispositivo real.

Este .runsettings debe estar presente en el mismo directorio que el archivo nfproj por convención para que Visual Studio 2019 lo encuentre y el descubrimiento de prueba se pueda procesar automáticamente.

Para un uso de línea de comando o de canalización, el archivo puede estar en cualquier lugar, debe pasarse como un argumento.

Ejecución de la prueba en la canalización de Azure DevOps

Como parte de cada RP, como en casi todos los proyectos serios de hoy en día, existen controles de calidad. .NET nanoFramework usa Azure DevOps y ejecutar las pruebas es tan simple como agregar una tarea en Azure Pipline yaml. Puede ver un ejemplo de resultado de la compilación de mscorlib aquí . Notará que hay 43 pruebas fallidas. La razón es que una vez que se ha implementado el marco de pruebas unitarias, se han migrado más de 2000 pruebas de .NET Microframework. Este proceso ha traído junto con las pruebas unitarias, oportunidades para descubrir casos extremos y errores introducidos con el tiempo. Tan pronto como se solucionen y se aprueben todas las pruebas, una falla en la tarea de prueba unitaria evitará que la fusión actúe como una puerta de calidad.

La tarea de Azure DevOps tiene este aspecto:

- task: VSTest@2
  condition: and( succeeded(), ${{ parameters.runUnitTests }}, ne( variables['StartReleaseCandidate'], true ) )
  displayName: 'Running Unit Tests'
  continueOnError: true
  inputs:
    testSelector: 'testAssemblies'
    testAssemblyVer2: |
      ***NFUnitTest*.dll
      ***Tests*.dll
      ***Tests*.dll
      !***TestAdapter*.dll
      !***TestFramework*.dll
      !**obj**
    searchFolder: '$(System.DefaultWorkingDirectory)'
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'
    diagnosticsEnabled: true
    vsTestVersion: toolsInstaller
    codeCoverageEnabled: true
    runSettingsFile: '${{ parameters.unitTestRunsettings }}'

Esta es una tarea de compilación muy estándar que utiliza VS Test, que ejecutará vstest.console.exe. Los parámetros son la ruta a los diferentes elementos de configuración, incluido el .runsettings, como se explicó en la parte anterior.

El resultado es un archivo TRX: y la tarea pasa o falla.Results File: D:a_temp.TestResultsTestResultsVssAdministrator_WIN-C3AUQ572MAF_2021-03-10_13_06_15.trx

Este archivo TRX se puede usar para enviar los resultados a la placa de Azure DevOps, por ejemplo, o cualquier otra plataforma como SonarCloud utilizada por .NET nanoFramework para el análisis de código estático.

NuGet: la forma amigable para desarrolladores

Ahora hemos visto la cadena completa y todos los componentes que se necesitan, junto con algunos de los mecanismos detrás de todo esto que hacen que todo esto funcione. Ahora, no queremos pedirles a los desarrolladores que clonen un proyecto para realizar la prueba. La idea central, desde el principio y relacionada con lo que queremos como desarrolladores, es poder usarlo sin problemas y eso es totalmente transparente. Entonces, para este propósito, se proporciona un paquete NuGet. Este paquete incluye el lanzador de pruebas unitarias (compilado en la versión Debug), la biblioteca del marco de prueba (compilado en la versión), la aplicación nanoCLR Win32 y el mecanismo de actualización para nanoCLR Win32.

Plantilla de proyecto de Visual Studio

Ahora, para que sea absolutamente simple para el desarrollador, una vez que tenga instalada la extensión .NET nanoFramework Visual Studio 2019 , puede crear un proyecto de prueba unitaria en blanco para .NET nanoFramework utilizando la plantilla de proyecto correspondiente.

nanoFramework crear proyecto de prueba unitaria

Al igual que con cualquier plantilla de proyecto a la que estamos acostumbrados, creará automáticamente el tipo correcto de proyecto, agregará el NuGet, colocará el archivo .runsettings en su lugar y establecerá el nombre de ensamblaje correcto.

Archivos de proyecto nanoFramework

Y ya está listo para escribir sus pruebas de .NET nanoFramework.

El problema del huevo y la gallina

Como mencionamos antes, .NET nanoFramework tiene su propia biblioteca de clases base, o mscorlib y amigos. Una de las motivaciones para crear todo el marco de pruebas unitarias para .NET nanoFramework fue poder reutilizar, adaptar y agregar pruebas. Una vez que el marco estuvo en su lugar, ocurrió la migración de más de 2000 pruebas de .NET Microframework. Este trabajo representó 84K líneas de códigos añadidos a mscorlib. Ayudó a descubrir algunos casos extremos y ahora se están arreglando todos ellos.

Pero, ¿por qué es este un problema de la gallina y el huevo? Bueno, mirando lo que escribí anteriormente, Unit Test Platform se distribuye a través de un paquete NuGet. Este paquete incluye Unit Test Launcher y Test Framework que, por razones obvias, se construyen con una versión específica de mscorlib.

Ahora, cuando desee ejecutar las pruebas unitarias de mscorlib, desea que se ejecuten en la versión que está compilando en ese momento, no en una versión anterior. Significa que todos los elementos deben ser como referencia del proyecto. Las pruebas deben ser una referencia de proyecto de mscorlib, Unit Test Launcher y Test Framework también. Debido a que .NET nanoFramework está organizado en diferentes repositorios, el Test Framework uno lo está y esto facilita agregarlo como un submódulo git del mscorlib.

Como esto aún requiere una versión específica de nanoCLR Win32, se ha agregado un proyecto de prueba falso, que contiene el paquete NuGet y, a medida que se construye, la última versión de nanoCLR Win32 se extrae de la compilación. Como mscorlib también está utilizando una implementación nativa de C, eso permite ejecutar las pruebas con la versión siempre actualizada, todas compiladas desde la fuente, incluidas las canalizaciones de Azure DevOps.

Que sigue

Ahora que este marco de prueba unitario .NET nanoFramework es real y completamente utilizable. Incluso si, como se explicó al principio, .NET Microframework tiene una plataforma de prueba específica, el código utilizado para ejecutar esas pruebas se puede migrar en gran parte y adaptarse para ejecutarse en nanoFramework. Esos casos de prueba se migrarán a las respectivas bibliotecas de clases a las que pertenecen. La idea es tener una cobertura decente del 80% + para todo el código .NET nanoFramework.

En este momento, el desafío es poder tener un informe de cobertura de código adecuado. Herramientas como Coverlet no se pueden usar por las razones mencionadas anteriormente sobre la pila de ejecución y las dependencias construidas. .NET nanoFramework mscorlib no está cargado correctamente en ninguna de esas herramientas, incluida la cobertura del código de prueba VS. Esto requerirá más trabajo y un analizador PE para analizar más profundamente la ruta de ejecución. Hay opciones para esto, una de ellas es la herramienta de procesamiento de metadatos, que se utiliza hoy para analizar los ensamblados generados por Roslyn y producir los archivos PE que se cargan en nanoCLR. La cobertura del código será parte del Adaptador de prueba en lugar de la extensión de prueba de DataCollector VS. La razón es que es difícil separar las pruebas del análisis de la ejecución en sí.

¿Dónde empezar .NET nanoFramework?

Para comenzar con .NET nanoFramework, el primer paso es obtener uno de los dispositivos compatibles, como un ESP32. Hay una lista de tableros de referencia y apoyados por la comunidad . Luego debes seguir la guía paso a paso para instalar la extensión de Visual Studio 2019, flashear tu dispositivo, crear tu primer proyecto y ejecutarlo.

Tener su propio código C # .NET ejecutándose en uno de esos dispositivos integrados es cuestión de minutos y muy sencillo.

Para crear un proyecto de prueba unitaria, los pasos se explican en esta publicación, es cuestión de seleccionar el tipo de proyecto de prueba unitaria para nanoFramework, escribe algunas líneas y ¡listo! Ni siquiera necesita un dispositivo real para comenzar a jugar con el marco de prueba unitario.

Si necesita ayuda, la comunidad de .NET nanoFramework es muy activa y utiliza los canales de Discord . La contribución para mejorar .NET nanoFramework en el C nativo, el C # y el lado de la documentación son más que bienvenidos. La página principal de .NET nanoFramework le proporcionará todos los enlaces que necesita.

Te esperamos en los siguientes artículos en donde hablaremos más acerca de estos temas, los cuales hoy en día son de vital importancia en el mundo de la tecnología.

¿Te gustaría aprender .NET Empresarial Desde Cero?
Tenemos el diplomado que necesitas. ¡Haz clic aquí!

About Author

NGuerrero

Post Siguiente

0 0 votos
Article Rating
Suscribir
Notificar de
guest
0 Comments
Comentarios.
Ver todos los comentarios
0
¿Te gusta este articulo? por favor comentax