Перейти к основному содержимому

Практикум WPF — тестирование API и unit-тесты

Разработчику Тестировщику

Практикум, шаг 5 из 6. Проверяем API и ViewModel без ручного клика по UI. Подробнее про ASP.NET-тесты — 4516; контекст десктопа — 112.md.


Три уровня проверки TaskDesk

УровеньЧто проверяемИнструмент
Ручной HTTPКонтракт, коды, JSONSwagger, Postman, curl
Интеграция APIPipeline, маршруты, сериализацияxUnit + WebApplicationFactory
Unit ViewModelКоманды, валидация, состояниеxUnit + Moq ITaskRepository

UI-тесты WPF (FlaUI, WinAppDriver) в базовом маршруте не обязательны — MVVM переносит логику в тестируемые классы.


Ручная проверка в Postman

Коллекция TaskDesk API:

  1. Environment — переменная baseUrl = http://localhost:5100.
  2. GET {{baseUrl}}/api/v1/tasks — пустой массив или список.
  3. POST {{baseUrl}}/api/v1/tasks — body raw JSON:
{
"title": "Интеграционный тест",
"status": "Todo"
}
  1. Tests (скрипт Postman):
pm.test("Status 201", () => pm.response.code === 201);
pm.test("Has id", () => pm.response.json().id);
pm.collectionVariables.set("taskId", pm.response.json().id);
  1. PUT / DELETE с {{taskId}}.

Негативные кейсы:

  • POST с пустым title400;
  • GET с несуществующим GUID → 404.
Swagger как быстрая альтернатива

В Development откройте /swagger, нажмите Try it out на Tasks — тот же контракт без импорта коллекции.


Интеграционные тесты API

dotnet new xunit -n TaskDesk.Api.Tests -o tests/TaskDesk.Api.Tests
dotnet add tests/TaskDesk.Api.Tests reference src/TaskDesk.Api
dotnet add tests/TaskDesk.Api.Tests package Microsoft.AspNetCore.Mvc.Testing
public class TasksApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;

public TasksApiTests(WebApplicationFactory<Program> factory) =>
_client = factory.CreateClient();

[Fact]
public async Task PostTask_ReturnsCreated()
{
var response = await _client.PostAsJsonAsync(
"/api/v1/tasks",
new { title = "Test task", status = "Todo" });

Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var dto = await response.Content.ReadFromJsonAsync<TaskDto>();
Assert.NotEqual(Guid.Empty, dto!.Id);
}

[Fact]
public async Task GetTasks_AfterPost_ContainsItem()
{
await _client.PostAsJsonAsync("/api/v1/tasks",
new { title = "Listed", status = "Todo" });

var list = await _client.GetFromJsonAsync<List<TaskDto>>("/api/v1/tasks");
Assert.Contains(list!, t => t.Title == "Listed");
}
}

WebApplicationFactory<Program> поднимает in-memory хост — без реального порта и браузера. Убедитесь, что в TaskDesk.Api есть public partial class Program { } для visibility.


Unit-тесты ViewModel

dotnet new xunit -n TaskDesk.Client.Tests -o tests/TaskDesk.Client.Tests
dotnet add tests/TaskDesk.Client.Tests reference src/TaskDesk.Client
dotnet add tests/TaskDesk.Client.Tests package Moq
public class TaskListViewModelTests
{
[Fact]
public async Task AddTask_WithEmptyTitle_DoesNotCallRepository()
{
var repo = new Mock<ITaskRepository>();
var vm = new TaskListViewModel(repo.Object) { NewTitle = " " };

await vm.AddTaskCommand.ExecuteAsync(null);

repo.Verify(r => r.CreateAsync(It.IsAny<TaskItem>(), default), Times.Never);
}

[Fact]
public async Task AddTask_ValidTitle_CallsRepositoryAndClearsTitle()
{
var repo = new Mock<ITaskRepository>();
repo.Setup(r => r.CreateAsync(It.IsAny<TaskItem>(), default))
.ReturnsAsync((TaskItem t, CancellationToken _) => t);

var vm = new TaskListViewModel(repo.Object) { NewTitle = "Deploy" };
await vm.AddTaskCommand.ExecuteAsync(null);

repo.Verify(r => r.CreateAsync(It.Is<TaskItem>(x => x.Title == "Deploy"), default), Times.Once);
Assert.Equal("", vm.NewTitle);
}
}

ViewModel не создаёт окно — тест выполняется за миллисекунды в CI.


Unit-тест InMemoryTaskStore

[Fact]
public void Delete_RemovesExistingItem()
{
var store = new InMemoryTaskStore();
var item = new TaskItem { Title = "X" };
store.Add(item);

var deleted = store.Delete(item.Id);

Assert.True(deleted);
Assert.Null(store.GetById(item.Id));
}

Матрица «что ломается где»

ДефектПоймает
Неверный route [HttpGet]Интеграционный тест 404
Забыли EnsureSuccessStatusCode в клиентеРучной POST + UI error
CanExecute не обновляетсяUnit-тест ViewModel
Race при ObservableCollectionРедко — нужен UI-тест; на API не влияет

Запуск всех тестов

dotnet test TaskDesk.sln

В CI (GitHub Actions):

- name: Test
run: dotnet test --no-build --verbosity normal

Чек-лист шага 5

  • Postman или Swagger проходит CRUD сценарий.
  • Минимум 2 интеграционных теста на API.
  • Минимум 2 unit-теста на TaskListViewModel с Moq.
  • dotnet test зелёный локально.

Дальше: Итоговый проект TaskDesk.


См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").