Day 21-Unit Test 应用於 Web APIs (情境及应用-1)

Unit Test 应用於 Web APIs-前言

现今大多数的软件工程都是以网路工程为主,那网路工程中又以 Web API 为单位做为开发的基石;因此,今天我们了解如何撰写 Web API 的单元测试,来提升软件开发的准确性。那首先,我们要介绍 Web API (假设部分在阅览的读者没有接触网路工程),Web API —— Web Application Programming Interface,白话文就是网路应用程序与网路应用程序之间沟通的桥梁,那 Web API 可依据使用者开发决定提供 XML、Json、GeoJson、File...等,以下几篇为我觉得不错的 Web API 参考文章,供各位参考:

那通常检测 Web API,都是在检测相对应的 Controller(以浏览器来看是一个对应的 Url),因应不同的 Controller 会有不同的服务如 HttpGet、HttpPost...等,那这次的范例是以 HttpPost 为例。


看程序码说故事 (Web API-1)

於是乎,我们就开始撰写 Web API 专案,那这次范例所采用的框架是 N-Tiers 框架,主要流程是

Controller -> IService (实作用 Service) -> Model

而今天的情境是有使用者登录商品的资料,登录完资料後,我们要登记 Log 并且给予这个资料一组 Unique GUID,程序码如下:

Controller 层:

namespace Products.Controllers
{
    [Route("api/[controller]")]
    public class ProductsController : Controller
    {
        private readonly ILogger Logger;
        private readonly IProductService ProductService;

        public ProductsController(ILogger inLogger, IProductService inProductService)
        {
            Logger = inLogger;
            ProductService = inProductService;
        }

        [HttpPost]
        public string Post(Product product)
        {
            Logger.Log("High", $"Adding a products with an id {product.ProductName}");

            var productGuid = ProductService.SaveProduct(product);

            return productGuid;
        }
    }
}

IService 层(服务介面层):

namespace Products.IService
{
    // 登记 Log 的服务
    public interface ILogger
    {
        public void Log(string level, string message);
    }

    // 产品 Product 的服务
    public interface IProductService
    {
        public string SaveProduct(Product product);
    }
}

Service 层(服务实作层):

namespace Products.Service
{
    // 登记 Log 的服务
    public class Logger : ILogger
    {
        public void Log(string level, string message)
        {
            // 撰写你要的功能,并非测试重点,所以不详细列述
        }
    }

    // 产品 Product 的服务
    public class ProductService : IProductService
    {
        public string SaveProduct(Product product)
        {
            // 撰写你要的功能,并非测试重点,所以不详细列述
        }
    }
}

Model 层(资料模型):

namespace Products.Models
{
    public class Product
    {
        // 产品序号
        public string ProductId;

        // 产品名称
        public string ProductName;

        // 产品现货数量
        public int QuantityAvailable;
    }
}

看程序码说故事 (Web API-2)

所以,我们要检测 HttpPost 是否正常运行,可思考几个关注点:

  1. GUID 回传值
  2. SaveProduct 是否呼叫与次数
  3. Log 是否呼叫与次数

列好了关注点之後,就可以开始撰写测试,如下:

[TestFixture]
public class ProductsControllerTests
{
    private ILogger Logger;
    private IProductService ProductService;

    private ProductsController ProductsController;

    [SetUp]
    public void SetUp()
    {
        Logger = Substitute.For<ILogger>();
        ProductService = Substitute.For<IProductService>();

        ProductsController = new ProductsController(Logger, ProductService);
    }

    [Test]
    public void DemoGuidTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        Assert.AreEqual(result, guid);
    }

    [Test]
    public void DemoSaveProductReceiveTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        ProductService.Received(1).SaveProduct(product);
    }

    [Test]
    public void DemoLogReceiveTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        Logger.Received(1).Log(default, default);
    }
}

若实务上,这三个测试都成功,则代表的确有呼叫到 Log 与 SaveProduct 各一次,并且有成功回传 GUID,这样就算是个良好的单元测试组。


文章故事情境参考来源:https://www.michalbialecki.com/2019/01/03/writing-unit-tests-with-nunit-and-nsubstitute/


<<:  Day7 Sideproject(作品集) from 0 to 1 - 业务流程

>>:  Day06-Gitlab runner 简介

Entity Framework Core

ORM(Object Relational Mapping) 将关联式资料库映射至物件导向的资料抽象...

Day 1 测试环境介绍与建立

前言 第一天不免俗的要介绍一下使用的测试环境,其实也不是偷懒,毕竟接下来的文章都会围绕在同一个环境下...

Day 04 | 渲染元件

要渲染 Livewire 元件也非常简单,主要会分成两种常用的方法,以下会分别对照 官方文件 来做示...

[Day1]前言:为何选择学习区块链?

关於我... hi~我是一个大三的学生,就读医学资讯。因为科系有学到一些资讯,加上系上的必修课需要...

会议结束是沟通的延续

一个会议的结束,依结论要做出的行动,或是待办事项没有人执行。这个会议结束,并没有实质意义。 通常结论...