Back to "(Simple) Test di unità La parte del blog 1 | Services"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Moq Unit Testing xUnit

(Simple) Test di unità La parte del blog 1 | Services

Sunday, 25 August 2024

Introduzione

In questo post io ' comincerò ad aggiungere l'unità di test per questo sito . Questo non sarà un tutorial completo sull'unidado di test, ma piuttosto una serie di post su come io '\ sto aggiungendo l'Unità di Test a questo sito In questo post provo a testare alcuni servizi prendendo in giro il DbContext; questo è per evitare schannaggi specifici alla DB.

Perché l'unità di test?

L'unità di test è un modo per testare individualmente i componenti del vostro codice in isolamento. È utile per una serie di ragioni:

  1. Isolisce ogni componente del vostro codice rendendo semplice vedere qualsiasi problema in alcune aree specifiche.
  2. È un modo per documentare il vostro codice.

Quali altri tipi di test ci sono?

Ci sono un certo numero di altri tipi di test che si possono fare. Eccone alcuni:

  1. Integrazione Testing - Testing how different components of your code work together. In ASPM SK2NET potremmo usare strumenti come Verify per testare il risultato dei punti finali e confrontarli con i risultati attendibili.
  2. Finire-to-Fincere l'esame - Esame dell'intera applicazione dal punto di vista dell'utenteM SK3 della prospettiva di . Questo si può fare con strumenti come Selenium.
  3. Testazione del rendimento - Testare come funziona la vostra applicazione sotto carico. Questo può essere fatto con strumenti come Apache JMeter, PostMan. La mia opzione preferita è comunque uno strumento chiamato k6.
  4. Probazione di sicurezza - Probazionare quanto sia sicura la vostra applicazione. Questo può essere fatto con strumenti come OWASP ZAP, Burp Suite, Nessus.
  5. Test per l'utente finale - Testo di come funziona la vostra applicazione per lo utente finale. Questo può essere fatto con strumenti come Test dell'utente, UserZoom, Userlytics.

La configurazione del progetto di prova

I'm userà xUnit per i miei test. Questo è usato da default nei progetti di ASPM SK2NET CoreMSC3 I 'm usarà anche Moq per simulare il DbContext insieme a

  • MoqQueryable - Questo ha delle estenzioni utili per far finta degli oggetti IQueryables.
  • Moq.EntityFrameworkCore - Questo ha delle estenzioni utili per copiare gli oggetti del DbContextoM SK2

Spostare il DbContexto

Per prepararmi, ho aggiunto un'interfaccia per il mio DbContext. Per poter imitare il Db Context nei miei test.

namespace Mostlylucid.EntityFramework;

public interface IMostlylucidDBContext
{
    public DbSet<CommentEntity> Comments { get; set; }
    public DbSet<BlogPostEntity> BlogPosts { get; set; }
    public DbSet<CategoryEntity> Categories { get; set; }

    public DbSet<LanguageEntity> Languages { get; set; }
    
    public DatabaseFacade Database { get; }
    
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);

}

È molto semplice, solo esposto i nostri DBSet e il metodo SaveChangesAsync.

Io. Don't Usare un modello di riposito nel mio codice. Questo perché l'entity Framework Core è già un modello deposito. Uso una strata di servizio per interagire con il DbContextoM SK2 Questo perchè non voglio ' abstraire il potere dell'entité Framework CoreMSC4

Poi aggiungiamo una nuova classe. Mostlylucid.Test il progetto con un metodo di estensione per organizzare la nostra ricerca:

public static class MockDbSetExtensions
{
    public static Mock<DbSet<T>> CreateDbSetMock<T>(this IEnumerable<T> sourceList) where T : class
    {
        // Use the MockQueryable.Moq extension method to create the mock
        return sourceList.AsQueryable().BuildMockDbSet();
    }

    // SetupDbSet remains the same, just uses the updated CreateDbSetMock
    public static void SetupDbSet<T>(this Mock<IMostlylucidDBContext> mockContext, IEnumerable<T> entities,
        Expression<Func<IMostlylucidDBContext, DbSet<T>>> dbSetProperty) where T : class
    {
        var dbSetMock = entities.CreateDbSetMock();
        mockContext.Setup(dbSetProperty).Returns(dbSetMock.Object);
    }
}

Voi' vedrete che si usa il MockQueryable.Moq metodo di estensione per creare il mock. che poi configura i nostri oggetti IQueryabili e IAsyncQueryable.

La configurazione del test

Un principio fondamentale dell'esame delle unità è che ogni test dovrebbe essere un lavoro 'unit' e non dipendere dal risultato di nessun altro test.

Nella nostra nuova BlogServiceFetchTests class abbiamo creato il nostro contesto di prova nel constructore:

  public BlogServiceFetchTests()
    {
        // 1. Setup ServiceCollection for DI
        var services = new ServiceCollection();
        // 2. Create a mock of IMostlylucidDbContext
        _dbContextMock = new Mock<IMostlylucidDBContext>();
        // 3. Register the mock of IMostlylucidDbContext into the ServiceCollection
        services.AddSingleton(_dbContextMock.Object);
        // Optionally register other services
        services.AddScoped<IBlogService, EFBlogService>(); // Example service that depends on IMostlylucidDbContext
        services.AddLogging(configure => configure.AddConsole());
        services.AddScoped<MarkdownRenderingService>();
        // 4. Build the service provider
        _serviceProvider = services.BuildServiceProvider();
    }

I' l'ho commentato abbastanza intensamente così che possiate vedere cosa sta succedendo. ServiceCollection che è una raccolta di servizi che possiamo usare nei nostri test. Poi creiamo un modello del nostro IMostlylucidDBContext e lo registrano nel ServiceCollection. Registriamo poi tutti gli altri servizi di cui abbiamo bisogno per i nostri test. Infine costruiamo il ServiceProvider che possiamo usare per ottenere i nostri servizi da.

Scrivere il test

Ho iniziato aggiungendo una sola classe di test, alla precedente. BlogServiceFetchTests class. Questa è una classe di prova per il Post per ottenere i metodi dei miei EFBlogService Classe.

Ogni test usa un test comune. SetupBlogService Il metodo per ottenere una nuova popolazione. EFBlogService oggetto. Per poter testare il servizio in isolamento.

    private IBlogService SetupBlogService(List<BlogPostEntity>? blogPosts = null)
    {
        blogPosts ??= BlogEntityExtensions.GetBlogPostEntities(5);

        // Setup the DbSet for BlogPosts in the mock DbContext
        _dbContextMock.SetupDbSet(blogPosts, x => x.BlogPosts);

        // Resolve the IBlogService from the service provider
        return _serviceProvider.GetRequiredService<IBlogService>();
    }

BlogEntityExtensions

Questa è una semplice classe di estensione che ci dà un numero di copollati. BlogPostEntity Objecti. Per poter testare il nostro servizio con un certo numero di oggetti.

 public static List<BlogPostEntity> GetBlogPostEntities(int count, string? langName = "")
    {
        var langs = LanguageExtensions.GetLanguageEntities();

        if (!string.IsNullOrEmpty(langName)) langs = new List<LanguageEntity> { langs.First(x => x.Name == langName) };

        var langCount = langs.Count;
        var categories = CategoryEntityExtensions.GetCategoryEntities();
        var entities = new List<BlogPostEntity>();

        var enLang = langs.First();
        var cat1 = categories.First();

        // Add a root post to the list to test the category filter.
        var rootPost = new BlogPostEntity
        {
            Id = 0,
            Title = "Root Post",
            Slug = "root-post",
            HtmlContent = "<p>Html Content</p>",
            PlainTextContent = "PlainTextContent",
            Markdown = "# Markdown",
            PublishedDate = DateTime.ParseExact("2025-01-01T07:01", "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture),
            UpdatedDate = DateTime.ParseExact("2025-01-01T07:01", "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture),
            LanguageEntity = enLang,
            Categories = new List<CategoryEntity> { cat1 }
        };
        entities.Add(rootPost);
        for (var i = 1; i < count; i++)
        {
            var langIndex = (i - 1) % langCount;
            var language = langs[langIndex];
            var postCategories = categories.Take(i - 1 % categories.Count).ToList();
            var dayDate = (i + 1 % 30 + 1).ToString("00");
            entities.Add(new BlogPostEntity
            {
                Id = i,
                Title = $"Title {i}",
                Slug = $"slug-{i}",
                HtmlContent = $"<p>Html Content {i}</p>",
                PlainTextContent = $"PlainTextContent {i}",
                Markdown = $"# Markdown {i}",
                PublishedDate = DateTime.ParseExact($"2025-01-{dayDate}T07:01", "yyyy-MM-ddTHH:mm",
                    CultureInfo.InvariantCulture),
                UpdatedDate = DateTime.ParseExact($"2025-01-{dayDate}T07:01", "yyyy-MM-ddTHH:mm",
                    CultureInfo.InvariantCulture),
                LanguageEntity = new LanguageEntity
                {
                    Id = language.Id,
                    Name = language.Name
                },
                Categories = postCategories
            });
        }

        return entities;
    }

Potete vedere che tutto quello che fa è restituire un certo numero di post del blog con lingue e categorie. Tuttavia aggiungiamo sempre un oggetto 'rootM SK2 che ci permette di essere in grado di affidarci ad un oggetto conosciuto nei nostri test .

I test

Ogni test è progettato per testare un aspetto dei risultati del posto.

Per esempio, nei due sotto testiamo semplicemente che possiamo ottenere tutti i posti e che possiamo avere posti per lingua.

    [Fact]
    public async Task TestBlogService_GetBlogsByLanguage_ReturnsBlogs()
    {
        var blogService = SetupBlogService();

        // Act
        var result = await blogService.GetPostsForLanguage(language: "es");

        // Assert
        Assert.Single(result);
    }

    [Fact]
    public async Task TestBlogService_GetAllBlogs_ReturnsBlogs()
    {
        var blogs = BlogEntityExtensions.GetBlogPostEntities(2);
        var blogService = SetupBlogService(blogs);
        // Act
        var result = await blogService.GetAllPosts();

        // Assert
        Assert.Equal(2, result.Count());
    }

Test per fallimento

Un concetto importante nell'unità di test è 'failure di test' dove si stabilisce che il codice non funziona nel modo in cui ci si aspetta.

Nei test successivi, testamo prima che il nostro codice di pagaggio funzioni come previsto. Poi testiamo che se chiediamo più pagine di quelle che abbiamo, otteniamo un risultato vuoto ( e non un erroreM SK3

    [Fact]
    public async Task TestBlogServicePagination_GetBlogsByCategory_ReturnsBlogs()
    {
        var blogPosts = BlogEntityExtensions.GetBlogPostEntities(10, "en");
        var blogService = SetupBlogService(blogPosts);

        // Act
        var result = await blogService.GetPagedPosts(2, 5);

        // Assert
        Assert.Equal(5, result.Posts.Count);
    }

    [Fact]
    public async Task TestBlogServicePagination_GetBlogsByCategory_FailsBlogs()
    {
        var blogPosts = BlogEntityExtensions.GetBlogPostEntities(10, "en");
        var blogService = SetupBlogService(blogPosts);

        // Act
        var result = await blogService.GetPagedPosts(10, 5);

        // Assert
        Assert.Empty(result.Posts);
    }

Conclusione

Questo è un semplice inizio per il nostro test di unità. Nel prossimo post ci adderemo test per altri servizi e punti finali. Ci guardiamo anche a come possiamo testare i nostri punti terminali usando l'Integrazione TestingM SK4

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.