Мой любимый проект — это архив текстов песен, он все еще находится в стадии разработки, и весь код с открытым исходным кодом на 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 ответ
Во-первых, вот несколько быстрых наблюдений за вашей реализацией:
- Он не занимается неисправными или неисправными случаями:
- Что делать, если одна из служб выйдет из строя?
- Что делать, если одна из служб довольно медленно реагирует?
- Он не использует одновременные асинхронные вызовы
- Как я вижу
artistsService
звонок не зависит от предыдущего сервисного звонка - Вы можете запускать их одновременно или можете еще больше усилить это:
- Как я вижу
var recentLyrics =_lyricsService.GetRecentLyricsAsync();
var topTenFemailArtists = _artistsService.GetTopTenFemaleArtistsByLyricsCountAsync();
await Task.WhenAll(recentLyrics, topTenFemailArtists);
viewModel.Lyrics = await recentLyrics;
viewModel.FemaleArtists = await topTenFemailArtists;
- Он полагается на неявную маршрутизацию
- Сделайте это явным через
HttpGetAttribute
(1), то вы можете и должны проверить и этот аспект:
- Сделайте это явным через
[HttpGet, Route("Home/Index")]
public async Task<IActionResult> Index()
viewModel
не совсем хорошее имя для вашей переменной.- Оно использует Венгерская нотация чего следует по возможности избегать.
- Попробуйте разделить получение данных и создание ответа.
- Это может вам очень помочь во время отладки.
Теперь давайте рассмотрим ваш тест
- Прежде всего: нейминг. Пожалуйста, попробуйте использовать структуру Given-When-Then, чтобы описать, при каких обстоятельствах какой метод должен себя вести.
- В вашем случае, например:
GivenAFlawlessArtists_AndAFlawlessLyricsServices_WhenTheIndexActionIsCalled_ThenItFetchesDataFromTheServices_AndPopulatesTheResponseWithTheResults
- Дано безупречные исполнители и безупречные тексты песен
- когда действие Index называется
- потом он извлекает данные из Сервисов и заполняет ответ Результатами
- Он описывает, чем вы занимаетесь, кроме определенных условий.
- В вашем случае, например:
- Как вы уже упоминали, генерацию образцов данных можно и нужно извлечь.
- В
homeController
не очень хорошее имя. В общем, вы можете назвать этоSUT
. Это сокращает следующее: Тестируемая система.- Это помогает читателю вашего кода оставаться сосредоточенным.
- Возможно, имеет смысл провести глубокое сравнение вашей модели просмотра.
- Чтобы убедиться, что данные не изменяются / не маскируются / не подделываются действием контроллера.