Этого достаточно для модульного тестирования базового контроллера?

Мой любимый проект – это архив текстов песен, он все еще находится в стадии разработки, и весь код с открытым исходным кодом на GitHub.

У меня есть местная ветка git tests/add-controller-tests где я хочу добавить несколько модульных тестов на свои контроллеры. Я целенаправленно оставил свои контроллеры базовыми, например, вот мой HTTP GET Index действие на HomeController:

namespace Bejebeje.Mvc.Controllers
{
  using System.Diagnostics;
  using System.Threading.Tasks;
  using Bejebeje.Models.Lyric;
  using Bejebeje.Services.Services.Interfaces;
  using Microsoft.AspNetCore.Mvc;
  using Models;

  public class HomeController : Controller
  {
    private readonly IArtistsService _artistsService;

    private readonly ILyricsService _lyricsService;

    public HomeController(
      IArtistsService artistsService,
      ILyricsService lyricsService)
    {
      _artistsService = artistsService;
      _lyricsService = lyricsService;
    }

    public async Task<IActionResult> Index()
    {
      IndexViewModel viewModel = new IndexViewModel();

      viewModel.Lyrics = await _lyricsService
        .GetRecentLyricsAsync();

      viewModel.FemaleArtists = await _artistsService
        .GetTopTenFemaleArtistsByLyricsCountAsync();

      return View(viewModel);
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
      ErrorViewModel viewModel = new ErrorViewModel
      {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
      };

      return View(viewModel);
    }
  }
}

А вот мой юнит-тест (всего один):

namespace Bejebeje.Mvc.Tests.Controllers
{
  using System.Collections.Generic;
  using System.Threading.Tasks;
  using Bejebeje.Models.Artist;
  using Bejebeje.Models.Lyric;
  using FluentAssertions;
  using Microsoft.AspNetCore.Mvc;
  using Moq;
  using Mvc.Controllers;
  using NUnit.Framework;
  using Services.Services.Interfaces;

  [TestFixture]
  public class HomeControllerTests
  {
    [Test]
    public async Task Index_ReturnsAViewResult_WithAnIndexViewModel()
    {
      // arrange
      IEnumerable<ArtistItemViewModel> tenFemaleArtists = new List<ArtistItemViewModel>
      {
        new ArtistItemViewModel
        {
          FirstName = "A1",
          LastName = "A1",
          ImageAlternateText = "A1",
          ImageUrl = "A1",
          PrimarySlug = "A1",
        },
        new ArtistItemViewModel
        {
          FirstName = "A2",
          LastName = "A2",
          ImageAlternateText = "A2",
          ImageUrl = "A2",
          PrimarySlug = "A2",
        },
        new ArtistItemViewModel
        {
          FirstName = "A3",
          LastName = "A3",
          ImageAlternateText = "A3",
          ImageUrl = "A3",
          PrimarySlug = "A3",
        },
        new ArtistItemViewModel
        {
          FirstName = "A4",
          LastName = "A4",
          ImageAlternateText = "A4",
          ImageUrl = "A4",
          PrimarySlug = "A4",
        },
        new ArtistItemViewModel
        {
          FirstName = "A5",
          LastName = "A5",
          ImageAlternateText = "A5",
          ImageUrl = "A5",
          PrimarySlug = "A5",
        },
        new ArtistItemViewModel
        {
          FirstName = "A6",
          LastName = "A6",
          ImageAlternateText = "A6",
          ImageUrl = "A6",
          PrimarySlug = "A6",
        },
        new ArtistItemViewModel
        {
          FirstName = "A7",
          LastName = "A7",
          ImageAlternateText = "A7",
          ImageUrl = "A7",
          PrimarySlug = "A7",
        },
        new ArtistItemViewModel
        {
          FirstName = "A8",
          LastName = "A8",
          ImageAlternateText = "A8",
          ImageUrl = "A8",
          PrimarySlug = "A8",
        },
        new ArtistItemViewModel
        {
          FirstName = "A9",
          LastName = "A9",
          ImageAlternateText = "A9",
          ImageUrl = "A9",
          PrimarySlug = "A9",
        },
        new ArtistItemViewModel
        {
          FirstName = "A10",
          LastName = "A10",
          ImageAlternateText = "A10",
          ImageUrl = "A10",
          PrimarySlug = "A10",
        }
      };

      Mock<IArtistsService> mockArtistsService = new Mock<IArtistsService>();

      mockArtistsService
        .Setup(x => x.GetTopTenFemaleArtistsByLyricsCountAsync())
        .ReturnsAsync(tenFemaleArtists);

      IEnumerable<LyricItemViewModel> tenRecentLyrics = new List<LyricItemViewModel>
      {
        new LyricItemViewModel
        {
          Title = "L1",
          LyricPrimarySlug = "L1",
          ArtistId = 1,
          ArtistName = "L1",
          ArtistPrimarySlug = "L1",
          ArtistImageUrl = "L1",
          ArtistImageAlternateText = "L1",
        },
        new LyricItemViewModel
        {
          Title = "L2",
          LyricPrimarySlug = "L2",
          ArtistId = 2,
          ArtistName = "L2",
          ArtistPrimarySlug = "L2",
          ArtistImageUrl = "L2",
          ArtistImageAlternateText = "L2",
        },
        new LyricItemViewModel
        {
          Title = "L3",
          LyricPrimarySlug = "L3",
          ArtistId = 3,
          ArtistName = "L3",
          ArtistPrimarySlug = "L3",
          ArtistImageUrl = "L3",
          ArtistImageAlternateText = "L3",
        },
        new LyricItemViewModel
        {
          Title = "L4",
          LyricPrimarySlug = "L4",
          ArtistId = 4,
          ArtistName = "L4",
          ArtistPrimarySlug = "L4",
          ArtistImageUrl = "L4",
          ArtistImageAlternateText = "L4",
        },
        new LyricItemViewModel
        {
          Title = "L5",
          LyricPrimarySlug = "L5",
          ArtistId = 5,
          ArtistName = "L5",
          ArtistPrimarySlug = "L5",
          ArtistImageUrl = "L5",
          ArtistImageAlternateText = "L5",
        },
        new LyricItemViewModel
        {
          Title = "L6",
          LyricPrimarySlug = "L6",
          ArtistId = 6,
          ArtistName = "L6",
          ArtistPrimarySlug = "L6",
          ArtistImageUrl = "L6",
          ArtistImageAlternateText = "L6",
        },
        new LyricItemViewModel
        {
          Title = "L7",
          LyricPrimarySlug = "L7",
          ArtistId = 7,
          ArtistName = "L7",
          ArtistPrimarySlug = "L7",
          ArtistImageUrl = "L7",
          ArtistImageAlternateText = "L7",
        },
        new LyricItemViewModel
        {
          Title = "L8",
          LyricPrimarySlug = "L8",
          ArtistId = 8,
          ArtistName = "L8",
          ArtistPrimarySlug = "L8",
          ArtistImageUrl = "L8",
          ArtistImageAlternateText = "L8",
        },
        new LyricItemViewModel
        {
          Title = "L9",
          LyricPrimarySlug = "L9",
          ArtistId = 9,
          ArtistName = "L9",
          ArtistPrimarySlug = "L9",
          ArtistImageUrl = "L9",
          ArtistImageAlternateText = "L9",
        },
        new LyricItemViewModel
        {
          Title = "L10",
          LyricPrimarySlug = "L10",
          ArtistId = 10,
          ArtistName = "L10",
          ArtistPrimarySlug = "L10",
          ArtistImageUrl = "L10",
          ArtistImageAlternateText = "L10",
        },
      };

      Mock<ILyricsService> mockLyricsService = new Mock<ILyricsService>();

      mockLyricsService
        .Setup(x => x.GetRecentLyricsAsync())
        .ReturnsAsync(tenRecentLyrics);

      HomeController homeController = new HomeController(mockArtistsService.Object, mockLyricsService.Object);

      // act
      IActionResult actionResult = await homeController.Index();

      // assert
      ViewResult view = actionResult.Should().BeOfType<ViewResult>().Subject;
      IndexViewModel viewModel = view.Model.Should().BeOfType<IndexViewModel>().Subject;
      viewModel.FemaleArtists.Should().HaveCount(10);
      viewModel.Lyrics.Should().HaveCount(10);
    }
  }
}

Что касается тестов, касающихся контроллера, есть ли что-нибудь еще, что я должен проверить? Кроме того, любые другие предложения по именованию или повышению читабельности …

В моем тесте я могу извлечь код, который строит списки в метод, есть ли что-нибудь еще?

1 ответ
1

Во-первых, вот несколько быстрых наблюдений за вашей реализацией:

  1. Он не занимается неисправными или неисправными случаями:
    • Что делать, если одна из служб выйдет из строя?
    • Что делать, если одна из служб довольно медленно реагирует?
  2. Он не использует одновременные асинхронные вызовы
    • Как я вижу artistsService звонок не зависит от предыдущего сервисного звонка
    • Вы можете запускать их одновременно или можете еще больше усилить это:
var recentLyrics =_lyricsService.GetRecentLyricsAsync();
var topTenFemailArtists = _artistsService.GetTopTenFemaleArtistsByLyricsCountAsync();

await Task.WhenAll(recentLyrics,  topTenFemailArtists);

viewModel.Lyrics = await recentLyrics;
viewModel.FemaleArtists = await topTenFemailArtists;
  1. Он полагается на неявную маршрутизацию
    • Сделайте это явным через HttpGetAttribute (1), то вы можете и должны проверить и этот аспект:
[HttpGet, Route("Home/Index")]
public async Task<IActionResult> Index()
  1. viewModel не совсем хорошее имя для вашей переменной.
  2. Попробуйте разделить получение данных и создание ответа.
    • Это может вам очень помочь во время отладки.

Теперь давайте рассмотрим ваш тест

  1. Прежде всего: нейминг. Пожалуйста, попробуйте использовать структуру Given-When-Then, чтобы описать, при каких обстоятельствах какой метод должен себя вести.
    • В вашем случае, например: GivenAFlawlessArtists_AndAFlawlessLyricsServices_WhenTheIndexActionIsCalled_ThenItFetchesDataFromTheServices_AndPopulatesTheResponseWithTheResults
    • Дано безупречные исполнители и безупречные тексты песен
    • когда действие Index называется
    • потом он извлекает данные из Сервисов и заполняет ответ Результатами
    • Он описывает, чем вы занимаетесь, кроме определенных условий.
  2. Как вы уже упоминали, генерацию образцов данных можно и нужно извлечь.
  3. В homeController не очень хорошее имя. В общем, вы можете назвать это SUT. Это сокращает следующее: Тестируемая система.
    • Это помогает читателю вашего кода оставаться сосредоточенным.
  4. Возможно, имеет смысл провести глубокое сравнение вашей модели просмотра.
    • Чтобы убедиться, что данные не изменяются / не маскируются / не подделываются действием контроллера.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *