Kiểm tra tự động:
Viết các kiểm tra là một phần quan trọng trong việc xây dựng ứng dụng. Kiểm tra mã của bạn giúp bạn tìm và tránh các lỗi và giúp dễ dàng cấu trúc lại mã của bạn sau này mà không vi phạm chức năng hoặc đưa ra các vấn đề mới.
Trong phần này chúng ta sẽ học cách viết unit tests và intergration tests trên ứng dụng Asp.Net Core. Các Unit Test là các thử nghiệm nhỏ đảm bảo rằng một phương pháp hay một khối logic thực hiện đúng.
Intergration Test là các thử nghiệm lớn hơn mô phỏng các kịch bản trong thế giới thực và kiểm tra nhiều lớp hoặc các phần trong ứng dụng của các bạn.
Khởi tạo một dự án Test:
Đó là một cách tốt nhất để tạo một dự án riêng cho các test của bạn, vì vậy chúng được tách biệt khỏi dự án của bạn. Dự án Test sẽ nằm trong solustion chứa dự án thật của bạn.
Nếu bạn đang ở trong dự án của mình, hãy trở về solustion và chạy lệnh sau để tạo một dự án Test mới:
dotnet new xunit -o AspNetCoreTodo.UnitTests
xUnit.Net là một khung kiểm tra phổ biến cho mã .NET có thể được sử dụng để viết các unit test và integration test. Giống như những thứ khác nó là một tập các gói NuGet có thể được cài trong bất kỳ dự án nào. Câu lệnh donet new xUnit đã bao gồm mọi thứ bạn cần.
Cấu trúc đường dẫn sẽ như sau:
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc…)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
Vì dự án Test sẽ sử dụng các lóp đươc định nghĩa trong dự án của bạn, bạn cần thêm một tham chiếu đến dự án Todo của bạn:
dotnet add reference ../AspNetCoreTodo/AspNetCoreTodo.csproj
Xóa tệp Unittest1.cs bây giò bạn đã sẵn sàng để viết unit test đầu tiên của bạn rồi.
Viết một Service Test:
Hãy xem logic trong phương thức AddItemAsync() của TodoItemService:
public async Task<bool> AddItemAsync(
TodoItem newItem, ApplicationUser user)
{
newItem.Id = Guid.NewGuid();
newItem.IsDone = false;
newItem.DueAt = DateTimeOffset.Now.AddDays(3);
newItem.UserId = user.Id;
_context.Items.Add(newItem);
var saveResult = await _context.SaveChangesAsync();
return saveResult == 1;
}
Phương thức này đưa ra một số quyết định hoặc giả định về mục mới trước khi nó được lưu vào cơ sở dữ liệu:
– Thuộc tính userId phải được đặt thành Id người dùng
– Các Item mới phải luôn chưa hoàn thành (IsDone = True)
– Tiêu đề của mục mới sẽ được gán từ newitem.Title
– Các Item mới phải luôn có thời gian đáo hạn là 3 ngày kể từ hiện tại
Hãy tưởng tượng nếu bạn hoặc ai đó tái cấu trúc phương thức AddItemAsync() và quên đi một phần của logic nghiệp vụ này. Hành vi ứng dụng của bạn có thể bị thay đổi mà bạn không nhận ra. Bạn có thể ngăn chặn điều này bằng cách viết một bài test kiểm tra logic nghiệp vụ này đã thay đổi hay chưa.
Để viết một Unit test xác minh trong TodoItemService, hãy tạo một lớp mới trong dự án Test của bạn:
AspNetCoreTodo.UnitTests/TodoItemServiceShould.cs
using System;
using System.Threading.Tasks;
using AspNetCoreTodo.Data;
using AspNetCoreTodo.Models;
using AspNetCoreTodo.Services;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace AspNetCoreTodo.UnitTests
{
public class TodoItemServiceShould
{
[Fact]
public async Task AddNewItemAsIncompleteWithDueDate()
{
// …
}
}
}
Thuộc tính [Fact] xuất phát từ gói xUnit.Net, và nó đánh dấu phương thức này là phương thức thử nghiệm.
TodoItemService yêu cầu ApplicationDbContext, cái mà được kết nối với cơ sở dữ liệu của bạn. Bạn sẽ không muốn sử dụng nó trong các Test. Thay vào đó, bạn có thể sử dụng cơ sở dữ liệu trong bộ nhớ Entity Framework Core trong mã kiểm tra của mình. Vì toàn bộ cơ sở dữ liệu tồn tại trong bộ nhớ nên nó sẽ bị xóa mỗi khi thử nghiệm được khởi động lại.
Sử dụng DbContextOptionsBuilder để định cấu hình cơ sở dữ liệu trong bộ nhớ và sau đó gọi tới AddItemAsync():
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: “Test_AddNewItem”).Options;
// Set up a context (connection to the “DB”) for writing
using (var context = new ApplicationDbContext(options))
{
var service = new TodoItemService(context);
var fakeUser = new ApplicationUser
{
Id = “fake-000”,
UserName = “fake@example.com”
};
await service.AddItemAsync(new TodoItem
{
Title = “Testing?”
}, fakeUser);
}
Dòng cuối cùng tạo ra một công việc mới gọi là Testing? Và yêu cầu dịch vụ lưu nó vào cơ sở dữ liệu trong bộ nhớ.
Để xác minh rằng logic nghiệp vụ đã chạy chính xác, hãy viết thêm một số mã bên dưới khối mã hiện tại:
// Use a separate context to read data back from the “DB”
using (var context = new ApplicationDbContext(options))
{
var itemsInDatabase = await context
.Items.CountAsync();
Assert.Equal(1, itemsInDatabase);
var item = await context.Items.FirstAsync();
Assert.Equal(“Testing?”, item.Title);
Assert.Equal(false, item.IsDone);
// Item should be due 3 days from now (give or take a second)
var difference = DateTimeOffset.Now.AddDays(3) – item.DueAt;
Assert.True(difference < TimeSpan.FromSeconds(1));
}
Kiểm tra đầu tiên là khẳng định không có nhiều hơn một mục được lưu vào cơ sở dữ liệu trong bộ nhớ. Giả sử điều đó là đúng, thử nghiệm sẽ truy xuất mục đã lưu bằng FirstAsync và sau đó xác nhận rằng các thuộc tính được đặt thành các giá trị dự kiến.
Khẳng định giá trị datetime là khó tại vì biểu thức so sánh sẽ trả về fault khi chúng khác nhau ở những mili giây. Thay vào đó kiểm tra xem giá trị DueAt cách giá trị mong đợi chưa tới một giây.
Chạy Test:
Trên Command chạy câu lệnh sau:
dotnet test
Lệnh kiểm tra sẽ quét dự án hiện tại để kiểm tra và chạy tất cả các Test mà nó tìm thấy. Bạn sẽ thấy đầu ra tương tự như sau:
Starting test execution, please wait…
Discovering: AspNetCoreTodo.UnitTests
Discovered: AspNetCoreTodo.UnitTests
Starting: AspNetCoreTodo.UnitTests
Finished: AspNetCoreTodo.UnitTests
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 1.9074 Seconds
Integration Testing:
So với các unit test, integration test có phạm vi lớn hơn nhiều, thực hiện toàn bộ ứng dụng thay vì một lớp hoặc một phương thức. Integration Test đảm bảo rằng tất cả các thành phần trong ứng dụng hoạt động đúng: Service, route, controller, và database…
Các Integration Test chậm hơn và liên quan tới nhiều unit test, do đó thông thường một dự án có nhiều unit test nhưng chỉ có một integration test.
Khởi tạo một dự án kiểm thử:
Trở lại thư mục solution của bạn và chạy câu lệnh command sau:
dotnet new xunit -o AspNetCoreTodo.IntegrationTests
Cấu trúc thư mục của bạn sẽ như sau:
AspNetCoreTodo/
AspNetCoreTodo/
AspNetCoreTodo.csproj
Controllers/
(etc…)
AspNetCoreTodo.UnitTests/
AspNetCoreTodo.UnitTests.csproj
AspNetCoreTodo.IntegrationTests/
AspNetCoreTodo.IntegrationTests.csproj
Tương tự như unit test bạn sẽ phải tham chiếu tới dự án chính:
dotnet add reference ../AspNetCoreTodo/AspNetCoreTodo.csproj
Bạn cũng cần phải thêm một gói Nuget Microsoft.AspNetCore.TestHost:
dotnet add package Microsoft.AspNetCore.TestHost
Xóa UnitTest1.cs và viết integration test của bạn.
Viết một integration test:
Có một vài thứ cần cấu hình trên máy chủ thử nghiệm trước mỗi thử nghiệm. Tạo một lớp mới TestFixture:
AspNetCoreTodo.IntegrationTests/TestFixture.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
namespace AspNetCoreTodo.IntegrationTests
{
public class TestFixture : IDisposable
{
private readonly TestServer _server;
public HttpClient Client { get; }
public TestFixture()
{
var builder = new WebHostBuilder()
.UseStartup<AspNetCoreTodo.Startup>()
.ConfigureAppConfiguration((context, config) =>
{
config.SetBasePath(Path.Combine(
Directory.GetCurrentDirectory(),
“..\\..\\..\\..\\AspNetCoreTodo”));
config.AddJsonFile(“appsettings.json”);
});
_server = new TestServer(builder);
Client = _server.CreateClient();
Client.BaseAddress = new Uri(“http://localhost:8888”);
}
public void Dispose()
{
Client.Dispose();
_server.Dispose();
}
}
}
Lớp này đảm nhiệm việc thiết lập TestServer và sẽ giúp giữ cho các Test gọn gàng hơn. Bây giờ bạn đã sẵn sàng để viết một integration Test, tạo một lớp mới gọi là TodoRouteShould:
AspNetCoreTodo.IntegrationTests/TodoRouteShould.cs
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace AspNetCoreTodo.IntegrationTests
{
public class TodoRouteShould : IClassFixture<TestFixture>
{
private readonly HttpClient _client;
public TodoRouteShould(TestFixture fixture)
{
_client = fixture.Client;
}
[Fact]
public async Task ChallengeAnonymousUser()
{
// Arrange
var request = new HttpRequestMessage(
HttpMethod.Get, “/todo”);
// Act: request the /todo route
var response = await _client.SendAsync(request);
// Assert: the user is sent to the login page
Assert.Equal(
HttpStatusCode.Redirect,
response.StatusCode);
Assert.Equal(
“http://localhost:8888/Account” +
“/Login?ReturnUrl=%2Ftodo”,
response.Headers.Location.ToString());
}
}
}
Thử nghiệm này đưa ra yêu cầu ẩn danh vào đường dẫn /todo và xác định trình duyệt được chuyển tới trang đăng nhập. Kịch bản này là tốt cho một integration test, vì nó liên quan tới nhiều thành phần của ứng dụng: Hệ thống định tuyến, controller,… Đây cũng là một thử nghiệm tốt vì nó đảm bảo bạn sẽ không bao giờ vô tình xóa thuộc tính [Authorize].
Chạy thử nghiệm:
Chạy thử nghiệm với dotnet test và bạn sẽ nhận được thông báo sau:
Starting test execution, please wait…
Discovering: AspNetCoreTodo.IntegrationTests
Discovered: AspNetCoreTodo.IntegrationTests
Starting: AspNetCoreTodo.IntegrationTests
Finished: AspNetCoreTodo.IntegrationTests
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.0588 Seconds
Kết luận:
Testing là một chủ đề rộng, và còn nhiều điều phải tìm hiểu. Ở đây ta không đề cập đến kiểm tra giao diện người dùng hoặc kiểm tra mã Javascript. Tuy nhiên bạn nên có các kỹ năng và kiến thức cơ bản về kiểm thử.