Compartir a través de


Agregar pruebas unitarias

Ahora que los viewModels y los servicios se encuentran en una biblioteca de clases independiente, puede crear fácilmente pruebas unitarias. Agregar proyectos de prueba unitaria le permite comprobar que los viewModels y los servicios se comportan según lo esperado sin depender de la capa de interfaz de usuario ni de las pruebas manuales. Puede ejecutar pruebas unitarias automáticamente como parte del flujo de trabajo de desarrollo, asegurándose de que el código sigue siendo confiable y fácil de mantener.

Creación de un proyecto de prueba unitaria

  1. Haga clic con el botón derecho en la solución en el Explorador de soluciones.
  2. Seleccione Agregar>nuevo proyecto....
  3. Elija la plantilla Aplicación de prueba unitaria de WinUI y seleccione Siguiente.
  4. Asigne al proyecto el nombre WinUINotes.Tests y seleccione Crear.

Agregar referencias de proyecto

  1. Haga clic con el botón derecho en el proyecto WinUINotes.Tests y seleccione Agregar>referencia de proyecto....
  2. Compruebe el proyecto WinUINotes.Bus y seleccione Aceptar.

Creación de implementaciones falsas para pruebas

Para las pruebas, cree implementaciones falsas del servicio de archivos y las clases de almacenamiento que realmente no escriben en disco. Fakes son implementaciones ligeras que simulan el comportamiento de las dependencias reales con fines de prueba.

  1. En el proyecto WinUINotes.Tests , cree una carpeta denominada Fakes.

  2. Agregue un archivo FakeFileService.cs de clase en la carpeta Fakes:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Storage;
    using WinUINotes.Services;
    
    namespace WinUINotes.Tests.Fakes
    {
        internal class FakeFileService : IFileService
        {
            private Dictionary<string, string> fileStorage = [];
    
            public async Task CreateOrUpdateFileAsync(string filename, string contents)
            {
                if (fileStorage.ContainsKey(filename))
                {
                    fileStorage[filename] = contents;
                }
                else
                {
                    fileStorage.Add(filename, contents);
                }
    
                await Task.Delay(10); // Simulate some async work
            }
    
            public async Task DeleteFileAsync(string filename)
            {
                if (fileStorage.ContainsKey(filename))
                {
                    fileStorage.Remove(filename);
                }
    
                await Task.Delay(10); // Simulate some async work
            }
    
            public bool FileExists(string filename)
            {
                if (string.IsNullOrEmpty(filename))
                {
                    throw new ArgumentException("Filename cannot be null or empty", nameof(filename));
                }
    
                if (fileStorage.ContainsKey(filename))
                {
                    return true;
                }
    
                return false;
            }
    
            public IStorageFolder GetLocalFolder()
            {
                return new FakeStorageFolder(fileStorage);
            }
    
            public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync()
            {
                await Task.Delay(10);
                return GetStorageItemsInternal();
            }
    
            public async Task<IReadOnlyList<IStorageItem>> GetStorageItemsAsync(IStorageFolder storageFolder)
            {
                await Task.Delay(10);
                return GetStorageItemsInternal();
            }
    
            private IReadOnlyList<IStorageItem> GetStorageItemsInternal()
            {
                return fileStorage.Keys.Select(filename => CreateFakeStorageItem(filename)).ToList();
            }
    
            private IStorageItem CreateFakeStorageItem(string filename)
            {
                return new FakeStorageFile(filename);
            }
    
            public async Task<string> GetTextFromFileAsync(IStorageFile file)
            {
                await Task.Delay(10);
    
                if (fileStorage.ContainsKey(file.Name))
                {
                    return fileStorage[file.Name];
                }
    
                return string.Empty;
            }
        }
    }
    

    FakeFileService utiliza un diccionario en memoria (fileStorage) para simular operaciones de archivo sin tocar el sistema de archivos real. Entre las características clave se incluyen:

    • Simulación asincrónica: se usa Task.Delay(10) para imitar las operaciones reales de archivos asincrónicos.
    • Validación: produce excepciones para entradas no válidas, al igual que la implementación real.
    • Integración con clases de almacenamiento falsas: devuelve FakeStorageFolder e FakeStorageFile instancias que funcionan conjuntamente para simular la API de Almacenamiento de Windows
  3. Agregue FakeStorageFolder.cs:

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices.WindowsRuntime;
    using Windows.Foundation;
    using Windows.Storage;
    using Windows.Storage.FileProperties;
    using Windows.Storage.Search;
    
    namespace WinUINotes.Tests.Fakes
    {
        internal class FakeStorageFolder : IStorageFolder
        {
            private string name;
            private Dictionary<string, string> fileStorage = [];
    
            public FakeStorageFolder(Dictionary<string, string> files)
            {
                fileStorage = files;
            }
    
            public FileAttributes Attributes => throw new NotImplementedException();
            public DateTimeOffset DateCreated => throw new NotImplementedException();
            public string Name => name;
            public string Path => throw new NotImplementedException();
    
            public IAsyncOperation<StorageFile> CreateFileAsync(string desiredName)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CreateFileAsync(string desiredName, CreationCollisionOption options)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFolder> CreateFolderAsync(string desiredName)
            {
                throw new NotImplementedException();
            }
    
            // Only partial implementation shown for brevity
            ...
        }
    }
    

    FakeStorageFolder toma el diccionario de almacenamiento de archivos en su constructor, lo que le permite trabajar con el mismo sistema de archivos en memoria que FakeFileService. La mayoría de los miembros de interfaz lanzan NotImplementedException, ya que solo es necesario implementar las propiedades y los métodos realmente utilizados por las pruebas.

    Puede ver la implementación completa de FakeStorageFolder en el repositorio de código de GitHub para este tutorial.

  4. Agregue FakeStorageFile.cs:

    using System;
    using System.IO;
    using System.Runtime.InteropServices.WindowsRuntime;
    using Windows.Foundation;
    using Windows.Storage;
    using Windows.Storage.FileProperties;
    using Windows.Storage.Streams;
    
    namespace WinUINotes.Tests.Fakes
    {
        public class FakeStorageFile : IStorageFile
        {
            private string name;
    
            public FakeStorageFile(string name)
            {
                this.name = name;
            }
    
            public string ContentType => throw new NotImplementedException();
            public string FileType => throw new NotImplementedException();
            public FileAttributes Attributes => throw new NotImplementedException();
            public DateTimeOffset DateCreated => throw new NotImplementedException();
            public string Name => name;
            public string Path => throw new NotImplementedException();
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncOperation<StorageFile> CopyAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
            {
                throw new NotImplementedException();
            }
    
            public IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace)
            {
                throw new NotImplementedException();
            }
    
            // Only partial implementation shown for brevity
            ...
        }
    }
    

    FakeStorageFile representa archivos individuales en el sistema de almacenamiento falso. Almacena el nombre de archivo y proporciona la implementación mínima necesaria para las pruebas. Al igual que FakeStorageFolder, solo implementa los miembros que realmente son utilizados por el código que se está probando.

    Puede ver la implementación completa de FakeStorageFolder en el repositorio de código de GitHub para este tutorial.

Obtenga más información en los documentos:

Escritura de una prueba unitaria sencilla

  1. Cambie el nombre UnitTest1.cs a NoteTests.cs y actualícelo:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using WinUINotes.Tests.Fakes;
    
    namespace WinUINotes.Tests
    {
        [TestClass]
        public partial class NoteTests
        {
            [TestMethod]
            public void TestCreateUnsavedNote()
            {
                var noteVm = new ViewModels.NoteViewModel(new FakeFileService());
                Assert.IsNotNull(noteVm);
                Assert.IsTrue(noteVm.Date > DateTime.Now.AddHours(-1));
                Assert.IsTrue(noteVm.Filename.EndsWith(".txt"));
                Assert.IsTrue(noteVm.Filename.StartsWith("notes"));
                noteVm.Text = "Sample Note";
                Assert.AreEqual("Sample Note", noteVm.Text);
                noteVm.SaveCommand.Execute(null);
                Assert.AreEqual("Sample Note", noteVm.Text);
            }
        }
    }
    

    En esta prueba se muestra cómo realizar una prueba unitaria del NoteViewModel usando el FakeFileService. La prueba crea un nuevo NoteViewModel, comprueba su estado inicial (la fecha es reciente, el nombre de archivo sigue el patrón esperado), establece el texto en la nota, ejecuta el comando save y confirma que el texto persiste. Dado que el servicio de archivos falso se usa en lugar de la implementación real, la prueba se ejecuta rápidamente sin ninguna E/S de archivo real y se puede ejecutar repetidamente sin efectos secundarios.

Obtenga más información en los documentos:

Ejecución de las pruebas

  1. Abra la ventana Explorador de pruebas en Visual Studio (Explorador de pruebas).>
  2. Seleccione Ejecutar todas las pruebas para ejecutar la prueba unitaria.
  3. Compruebe que se supera la prueba.

Ahora tienes una arquitectura testeable donde puedes probar los ViewModels y los servicios independientemente de la interfaz de usuario.

Resumen

En esta serie de tutoriales, ha aprendido a:

  • Cree un proyecto de biblioteca de clases independiente (proyecto bus) para contener los viewModels y los servicios, lo que permite realizar pruebas unitarias independientes de la capa de interfaz de usuario.
  • Implemente el patrón MVVM mediante el MVVM Toolkit, aprovechando los atributos ObservableObject, [ObservableProperty] y [RelayCommand] para reducir el código repetitivo.
  • Use generadores de origen para crear automáticamente las notificaciones de cambio de propiedad y las implementaciones de comandos.
  • Use [NotifyCanExecuteChangedFor] para actualizar automáticamente la disponibilidad de comandos cuando cambien los valores de propiedad.
  • Integre la inserción de dependencias mediante Microsoft.Extensions.DependencyInjection para administrar el ciclo de vida de ViewModels y los servicios.
  • Cree una IFileService interfaz e implementación para controlar las operaciones de archivo de una manera verificable.
  • Configure el contenedor DI en App.xaml.cs y recupere ViewModels del proveedor de servicios en tus páginas.
  • Implemente el WeakReferenceMessenger para habilitar un acoplamiento flexible entre componentes, permitiendo que las páginas respondan a eventos de ViewModel sin referencias directas.
  • Cree clases de mensaje que hereden de ValueChangedMessage<T> para transportar datos entre componentes.
  • Cree implementaciones falsas de dependencias para realizar pruebas sin tocar el sistema de archivos real.
  • Escriba pruebas unitarias mediante MSTest para comprobar el comportamiento de ViewModel independientemente de la capa de interfaz de usuario.

Esta arquitectura proporciona una base sólida para crear aplicaciones WinUI que se pueden mantener y probar con una separación clara de los problemas entre la interfaz de usuario, la lógica de negocios y las capas de acceso a datos. Puede descargar o ver el código de este tutorial desde el repositorio de GitHub.

Pasos siguientes

Ahora que comprende cómo implementar MVVM con el kit de herramientas de MVVM y la inserción de dependencias, puede explorar temas más avanzados:

  • Mensajería avanzada: explore patrones de mensajería adicionales, incluidos los mensajes de solicitud/respuesta y los tokens de mensaje para el control selectivo de mensajes.
  • Validación: agregue la validación de entrada a viewModels mediante anotaciones de datos y las características de validación del kit de herramientas de MVVM.
  • Comandos asincrónicos: obtenga más información sobre la ejecución asincrónica de comandos, la compatibilidad con la cancelación y los informes de progreso con AsyncRelayCommand.
  • Pruebas avanzadas: explore escenarios de prueba más avanzados, como probar el control de mensajes, la ejecución de comandos asincrónicos y las notificaciones de cambio de propiedades.
  • Colecciones observables: use ObservableCollection<T> eficazmente y explore ObservableRangeCollection<T> para las operaciones masivas.