Link Search Menu Expand Document

.NET Web API - さん

Advanced

Nội dung

  1. Unit Tests
  2. Test Reports

Controllers và Services trong Application/Busines Layers là những thành phần trung tâm của .NET API services. Việc phát triển và thực thi các Test Cases cho những thành phần này đóng vai trò rất quan trọng trong việc đảm bảo chất lượng của một sản phẩm phần mềm.

Những loại hình Tests thường áp dụng trong microservices:

  • Unit Tests: đảm bảo từng components như controllers, services hoạt động đúng như mong muốn thông qua các đánh giá - Assertion.

  • Integration Tests: đảm bảo chức năng tương tác giữa components, hoặc tương tác với một số thành phần bên ngoài như cơ sở dữ liệu. Các Test Cases kiểm tra quá trình thực thi của API, truy cập dữ liệu, logging etc…

  • Functional Tests: áp dụng cho từng microservices, đảm bảo hoạt động tổng thể, input, output của một microservice đúng như yêu cầu.

  • Service Tests: áp dụng cho quá trình kiểm thử đầu-cuối (End-to-End). Đây là bước kiểm thử áp dụng cho toàn bộ một hệ thống ứng dụng, yêu cầu môi trường thực thi kiểm thử phải tương đồng với môi trường sản phẩm khi triển khai.

Trong giới hạn của bài viết này, chúng ta chỉ tập trung vào hoạt động Unit Tests áp dụng cho dịch vụ RemindersManagement. Những loại hình Test khác, chúng ta sẽ quay lại khi phát triển ứng dụng FriendReminders với nhiều microservices, cũng như hoàn thành bước chuẩn bị môi trường thực thi với hoạt động Tích hợp và Triển khai


Unit Tests

Mục đích của Unit Test là kiểm thử từng thành phần riêng rẽ bên trong một ứng dụng. Việc kiểm thử chỉ tập trung vào tính chất logic bên trong một thành phần, bỏ qua sự phụ thuộc - dependency của thành phần đó với những yếu tố hạ tầng cơ sở hay những thành phần liên quan.

Để thực hiện Unit Test, trong .NET chúng ta có sự hỗ trợ từ test framework phổ biến: xUnit.net, MSTest, Moq, or NUnit. Những frameworks này cung cấp hai nhóm chức năng phổ biến:

  • Giả lập (Mock) các yếu tố phụ thuộc, giúp một Unit Test chỉ tập trung vào logic của thành phần cần test
  • Cung cấp các phương thức đánh giá hay so sánh kết quả thực tế và kết quả mong đợi trong mỗi Unit Test case

Để thực hiện việc giả lập hiệu quả, trong quá trình phát triển việc đưa vào các yếu tố phụ thuộc trong mỗi thành phần cần được thiết kế hay quản lý một cách hiệu quả. Chúng ta có thể tham khảo thêm một số nguyên lý thiết kế phần mềm trợ giúp hoạt động này như Explicit Dependencies PrincipleDepdendency Injection.

Lấy một ví dụ cụ thể để giải thích việc giả lập này:

Trong dịch vụ RemindersManagament, ReminersManagement.API\Controllers, chúng ta cần thực hiện Unit Test trên thành phần RemindersController:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using RemindersManagement.API.Domain.Models;
using RemindersManagement.API.Domain.Services;

namespace RemindersManagement.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class RemindersController : ControllerBase
    {
        private readonly ILogger<RemindersController> _logger;
        private readonly IRemindersService _remindersService;

        public RemindersController(ILogger<RemindersController> logger, IRemindersService remindersService)
        {
            _logger = logger;
            _remindersService = remindersService;
        }

        // GET: api/reminders
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Reminder>>> GetReminders()
        {
          ...
        }

        // GET: api/reminder/5
        [HttpGet("{id}")]
        public async Task<ActionResult<Reminder>> GetReminder(Guid id)
        {
          ...
        }

        // PUT: api/reminders/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutReminder(Guid id, Reminder reminder)
        {
          ...
        }  

        // POST: api/reminders
        [HttpPost]
        public async Task<ActionResult<Reminder>> PostReminder(Reminder reminder)
        {
          ...
        }

        // DELETE: api/reminders/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<Reminder>> DeleteReminder(Guid id)
        {
          ...
        }
    }
}              

Đối tượng RemindersController phụ thuộc vào hai thành phần khác:

  • Logging: ILogger<ReminderController>
  • Service: IRemindersService

Thay vì đưa vào gián tiếp trong mỗi phương thức, các yếu tố phụ thuộc này được thể hiện trực tiếp và rõ ràng thông qua constructor RemindersController() và các tham số trong mỗi phương thức của đối tượng RemindersController. Đây chính là nội dung yêu cầu từ nguyên lý Explicit Dependencies Principle.

Các thành phần phụ thuộc được biểu diễn thông qua những interface và được thay thế bởi những lớp logic cụ thể trong quá trình thực thi - Dependency Injection. Quá trình thay thế được thực hiển bởi một framework gọi chung là Inversion of Control (IoC) container. .NET Core cung cấp sẵn một IoC Container mặc định, nhưng chúng ta cũng có thể áp dụng những IoC khác: Castle Windsor, StructureMap, Autofact…

Quay trở lại ví dụ về RemindersController, sau khi xác định được các thành phần phụ thuộc, chúng ta sử dụng thư viện Moq để tạo sự giả lập cho những yếu tố này.

Các bước thực hiện:

  • Tạo Unit Test project.

Trong folder RemindersManagement\RemindersManagement.UnitTests, tạo xUnit Test project với lệnh:

dotnet new xunit

Tạo các sub-folder:

mkdir -p Application/Controllers
mkdir -p Application/Services
  • Trong folder Application/Controllers, tạo Unit Test case: RemindersControllerTesst:
using Moq;
using Xunit;
using RemindersManagement.API.Domain.Services;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Collections.Generic;
using RemindersManagement.API.Domain.Models;
using System;
using RemindersManagement.API.Controllers;
using RemindersManagement.API.Domain.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace RemindersManagement.UnitTests.Application.Controllers
{
    public class RemindersControllerTest
    {
        private readonly Mock<IRemindersService> _remindersServiceMock;
        private readonly Mock<ILogger<RemindersController>> _loggerMock;

        public RemindersControllerTest()
        {
            _remindersServiceMock = new Mock<IRemindersService>();
            _loggerMock = new Mock<ILogger<RemindersController>>();
        }

        [Fact]
        public async Task List_Reminders_Success()
        {
            // Arrange
            var reminders = new List<Reminder>()
            {
                new Reminder()
                {
                    Id = Guid.NewGuid(),
                    Description = "Working",
                    Status = ReminderStatus.Doing
                },
                new Reminder()
                {
                    Id = Guid.NewGuid(),
                    Description = "Vanishing",
                    Status = ReminderStatus.Finished
                }
            };

            _remindersServiceMock
                .Setup(x => x.GetRemindersAsync())
                .Returns(Task.FromResult<IEnumerable<Reminder>>(reminders));

            // Act
            var remindersController = new RemindersController(_loggerMock.Object, _remindersServiceMock.Object);
            var listResult = await remindersController.GetReminders();

            // Assert
            Assert.NotNull(listResult);
            Assert.Equal((listResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK);
            Assert.Equal((((OkObjectResult)listResult.Result).Value as List<Reminder>).Capacity, reminders.Capacity);
        }        
    }
}

Trong constructor của lớp RemindersControllerTest, chúng ta sử dụng Moq để giả lập Logger và RemindersService. Sử dụng các giả lập này, Test Case List_Reminders_Success đánh giá logic thực thi của phương thức GetReminders. Thông thường mỗi Test Case bao gồm 3 bước:

  • Arrange: chuẩn bị dữ liệu và giả lập môi trường test
  • Act: thực thi phương thức cần kiểm tra
  • Assert:: đánh giá hành vi, trạng thái hoặc kết quả trả về

Trong ví dụ RemindersControllerTest, chúng ta kiểm tra danh sách trả về khác Null, có số lượng các phần tử theo như mong đợi, và HTTP Code Ok khi việc thực hiện controller method thành công.


Test Reports

Test Reports sử dụng để đánh giá kết quả của hoạt động Test. Test Reports cung cấp những thông tin bao gồm:

  • Số lượng, thời gian thực hiện Test Cases
  • Số lượng Test Cases Pass / Failed
  • Mật độ Test - tỉ lệ % code lines, branch logic được kiểm thử - Code Coverage

Để thực hiện Test Report cho dịch vụ RemindersManagement, chúng ta sử dụng thư viện Coverlet và công cụ ReportGenerator.

Coverlet là một thư viện mã nguồn mở cho phép thu thập thông tin về mật độ test và đưa ra kết quả báo cáo theo định dạng Cobertura.

  • Trong project folder RemindersManagement.UnitTests, cài đặt Coverlet theo lệnh:
dotnet add package coverlet.msbuild
  • Trong solution folder FriendReminders, thực hiện lệnh test:
dotnet test

Output cho biết số lượng Test Case thực hiện, số lượng Passed và thời gian thực hiện

Test run for /Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.UnitTests/bin/Debug/netcoreapp3.1/RemindersManagement.UnitTests.dll(.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 16.7.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test Run Successful.
Total tests: 18
     Passed: 18
 Total time: 5.6827 Seconds
  • Để kiểm tra thông tin về mật độ test:
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura

Output:

Test run for /Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.UnitTests/bin/Debug/netcoreapp3.1/RemindersManagement.UnitTests.dll(.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 16.7.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test Run Successful.
Total tests: 18
     Passed: 18
 Total time: 5.3884 Seconds

Calculating coverage result...
  Generating report '/Users/anh/Workspace/git/FriendReminders/Services/RemindersManagement/RemindersManagement.UnitTests/coverage.cobertura.xml'

+-------------------------+--------+--------+--------+
| Module                  | Line   | Branch | Method |
+-------------------------+--------+--------+--------+
| RemindersManagement.API | 54.92% | 43.75% | 54%    |
+-------------------------+--------+--------+--------+

+---------+--------+--------+--------+
|         | Line   | Branch | Method |
+---------+--------+--------+--------+
| Total   | 54.92% | 43.75% | 54%    |
+---------+--------+--------+--------+
| Average | 54.92% | 43.75% | 54%    |
+---------+--------+--------+--------+

Kết quả câu lệnh cho thấy file overage.cobertura.xml chứa thông tin về mật độ Test.

  • Trong trường hợp cần thông tin chi tiết trong mỗi class, method. Chúng ta sử dụng công cụ ReportGenerator.

Cài đặt ReportGenerator:

dotnet tool install -g dotnet-reportgenerator-globaltool
  • Tạo HTML Report dựa trên file overage.cobertura.xml:
reportgenerator "-reports:Services/RemindersManagement/RemindersManagement.UnitTests/coverage.cobertura.xml" "-targetdir:./HtmlReports"

Trong folder HtmlReports, file index.html cung cấp thông tin chi tiết về mật độ Unit Test thực hiện trong RemindersManagement:

Unit Test Coverage in RemindersManagement Mật độ Unit Test trong RemindersManagement

Lựa chọn ...RemindersController:

Details Unit Test Coverage in a Controller Class Mật độ Unit Test trong class RemindersController


Tài liệu tham khảo


Copyright © 2019-2022 Tuan Anh Le.