1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文看懂.Net Core后端單元測試!

        共 8141字,需瀏覽 17分鐘

         ·

        2021-03-16 21:45

        • 1. 前言

        • 2. 為什么需要單元測試

          • 2.1 防止回歸

          • 2.2 減少代碼耦合

        • 3. 基本原則和規(guī)范

          • 3.1 3A原則

          • 3.2 盡量避免直接測試私有方法

          • 3.3 重構(gòu)原則

          • 3.4 避免多個斷言

          • 3.5 文件和方法命名規(guī)范

        • 4. 常用類庫介紹

          • 4.1 xUnit/MsTest/NUnit

          • 4.2 Moq

          • 4.3 AutoFixture

        • 5. 實踐中結(jié)合Visual Studio的使用

          • 5.1 如何在Visual Studio中運行單元測試

          • 5.2 如何在Visual Studio中查看單元測試覆蓋率

        • 6. 實踐中常見場景的Mock

          • 6.1 DbSet

          • 6.2 HttpClient

          • 6.3 ILogger

        • 7. 拓展

          • 7.1 TDD介紹

        1. 前言

        單元測試一直都是"好處大家都知道很多,但是因為種種原因沒有實施起來"的一個老大難問題。具體是否應(yīng)該落地單元測試,以及落地的程度, 每個項目都有自己的情況。

        本篇為個人認為"如何更好地寫單元測試", 即更加偏向?qū)嵺`向中夾雜一些理論的分享。

        下列示例的單元測試框架為xUnit, Mock庫為Moq

        2. 為什么需要單元測試

        優(yōu)點有很多, 這里提兩點我個人認為的很明顯的好處

        2.1 防止回歸

        通常在進行新功能/模塊的開發(fā)或者是重構(gòu)的時候,測試會進行回歸測試原有的已存在的功能,以驗證以前實現(xiàn)的功能是否仍能按預(yù)期運行。
        使用單元測試,可在每次生成后,甚至在更改一行代碼后重新運行整套測試, 從而可以很大程度減少回歸缺陷。

        2.2 減少代碼耦合

        當(dāng)代碼緊密耦合或者一個方法過長的時候,編寫單元測試會變得很困難。當(dāng)不去做單元測試的時候,可能代碼的耦合不會給人感覺那么明顯。為代碼編寫測試會自然地解耦代碼,變相提高代碼質(zhì)量和可維護性。

        3. 基本原則和規(guī)范

        3.1 3A原則

        3A分別是"arrange、act、assert", 分別代表一個合格的單元測試方法的三個階段

        • 事先的準備

        • 測試方法的實際調(diào)用

        • 針對返回值的斷言

        一個單元測試方法可讀性是編寫測試時最重要的方面之一。在測試中分離這些操作會明確地突出顯示調(diào)用代碼所需的依賴項、調(diào)用代碼的方式以及嘗試斷言的內(nèi)容.

        所以在進行單元測試的編寫的時候, 請使用注釋標(biāo)記出3A的各個階段的, 如下示例

        Copy
        [Fact]
        public async Task VisitDataCompressExport_ShouldReturnEmptyResult_WhenFileTokenDoesNotExist()
        {
        // arrange
        var mockFiletokenStore = new Mock<IFileTokenStore>();
        mockFiletokenStore
        .Setup(it => it.Get(It.IsAny<string>()))
        .Returns(string.Empty);

        var controller = new StatController(
        mockFiletokenStore.Object,
        null);

        // act
        var actual = await controller.VisitDataCompressExport("faketoken");

        // assert
        Assert.IsType<EmptyResult>(actual);
        }

        3.2 盡量避免直接測試私有方法

        盡管私有方法可以通過反射進行直接測試,但是在大多數(shù)情況下,不需要直接測試私有的private方法, 而是通過測試公共public方法來驗證私有的private方法。

        可以這樣認為:private方法永遠不會孤立存在。更應(yīng)該關(guān)心的是調(diào)用private方法的public方法的最終結(jié)果。

        3.3 重構(gòu)原則

        如果一個類/方法,有很多的外部依賴,造成單元測試的編寫困難。那么應(yīng)該考慮當(dāng)前的設(shè)計和依賴項是否合理。是否有部分可以存在解耦的可能性。選擇性重構(gòu)原有的方法,而不是硬著頭皮寫下去.

        3.4 避免多個斷言

        如果一個測試方法存在多個斷言,可能會出現(xiàn)某一個或幾個斷言失敗導(dǎo)致整個方法失敗。這樣不能從根本上知道是了解測試失敗的原因。

        所以一般有兩種解決方案

        • 拆分成多個測試方法

        • 使用參數(shù)化測試, 如下示例

        Copy
        [Theory]
        [InlineData(null)]
        [InlineData("a")]
        public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
        {
        // arrange
        var stringCalculator = new StringCalculator();

        // act
        Action actual = () => stringCalculator.Add(input);

        // assert
        Assert.Throws<ArgumentException>(actual);
        }

        當(dāng)然如果是對對象進行斷言, 可能會對對象的多個屬性都有斷言。此為例外。

        3.5 文件和方法命名規(guī)范

        文件名規(guī)范

        一般有兩種。比如針對UserController下方法的單元測試應(yīng)該統(tǒng)一放在UserControllerTest或者UserController_Test

        單元測試方法名

        單元測試的方法名應(yīng)該具有可讀性,讓整個測試方法在不需要注釋說明的情況下可以被讀懂。格式應(yīng)該類似遵守如下

        Copy
        <被測試方法全名>_<期望的結(jié)果>_<給予的條件>

        // 例子
        [Fact]
        public void Add_InputNullOrAlphabetic_ThrowsArgumentException()
        {
        ...
        }

        4. 常用類庫介紹

        4.1 xUnit/MsTest/NUnit

        編寫.Net Core的單元測試繞不過要選擇一個單元測試的框架, 三大單元測試框架中

        • MsTest是微軟官方出品的一個測試框架

        • NUnit沒用過

        • xUnit是.Net Foundation下的一個開源項目,并且被dotnet github上很多倉庫(包括runtime)使用的單元測試框架

        三大測試框架發(fā)展至今已是大差不差, 很多時候選擇只是靠個人的喜好。

        個人偏好xUnit簡潔的斷言

        Copy
        // xUnit
        Assert.True()
        Assert.Equal()

        // MsTest
        Assert.IsTrue()
        Assert.AreEqual()

        客觀地功能性地分析三大框架地差異可以參考如下

        https://anarsolutions.com/automated-unit-testing-tools-comparison

        4.2 Moq

        官方倉庫

        • https://github.com/moq/moq4

        Moq是一個非常流行的模擬庫, 只要有一個接口它就可以動態(tài)生成一個對象, 底層使用的是Castle的動態(tài)代理功能.

        基本用法

        在實際使用中可能會有如下場景

        Copy
        public class UserController
        {
        private readonly IUserService _userService;

        public UserController(IUserService userService)
        {
        _userService = userService;
        }

        [HttpGet("{id}")]
        public IActionResult GetUser(int id)
        {
        var user = _userService.GetUser(id);

        if (user == null)
        {
        return NotFound();
        }
        else
        {
        ...
        }
        }
        }

        在進行單元測試的時候, 可以使用Moq_userService.GetUser進行模擬返回值

        Copy
        [Fact]
        public void GetUser_ShouldReturnNotFound_WhenCannotFoundUser()
        {
        // arrange
        // 新建一個IUserService的mock對象
        var mockUserService = new Mock<IUserService>();
        // 使用moq對IUserService的GetUs方法進行mock: 當(dāng)入?yún)?33時返回null
        mockUserService
        .Setup(it => it.GetUser(233))
        .Return((User)null);
        var controller = new UserController(mockUserService.Object);

        // act
        var actual = controller.GetUser(233) as NotFoundResult;

        // assert
        // 驗證調(diào)用過userService的GetUser方法一次,且入?yún)?33
        mockUserService.Verify(it => it.GetUser(233), Times.AtMostOnce());
        }

        4.3 AutoFixture

        官方倉庫

        • https://github.com/AutoFixture/AutoFixture

        AutoFixture是一個假數(shù)據(jù)填充庫,旨在最小化3A中的arrange階段,使開發(fā)人員更容易創(chuàng)建包含測試數(shù)據(jù)的對象,從而可以更專注與測試用例的設(shè)計本身。

        基本用法

        直接使用如下的方式創(chuàng)建強類型的假數(shù)據(jù)

        Copy
        [Fact]
        public void IntroductoryTest()
        {
        // arrange
        Fixture fixture = new Fixture();

        int expectedNumber = fixture.Create<int>();
        MyClass sut = fixture.Create<MyClass>();

        // act
        int result = sut.Echo(expectedNumber);

        // assert
        Assert.Equal(expectedNumber, result);
        }

        上述示例也可以和測試框架本身結(jié)合,比如xUnit

        Copy
        [Theory, AutoData]
        public void IntroductoryTest(
        int expectedNumber, MyClass sut)
        {
        // act
        int result = sut.Echo(expectedNumber);

        // assert
        Assert.Equal(expectedNumber, result);
        }

        5. 實踐中結(jié)合Visual Studio的使用

        Visual Studio提供了完備的單元測試的支持,包括運行. 編寫. 調(diào)試單元測試。以及查看單元測試覆蓋率等。

        5.1 如何在Visual Studio中運行單元測試

        5.2 如何在Visual Studio中查看單元測試覆蓋率

        如下功能需要Visual Studio 2019 Enterprise版本,社區(qū)版不帶這個功能。

        如何查看覆蓋率

        • 在測試窗口下,右鍵相應(yīng)的測試組

        • 點擊如下的"分析代碼覆蓋率"

        6. 實踐中常見場景的Mock

        主要

        6.1 DbSet

        使用EF Core過程中,如何mock DbSet是一個繞不過的坎。

        方法一

        參考如下鏈接的回答進行自行封裝

        https://stackoverflow.com/questions/31349351/how-to-add-an-item-to-a-mock-dbset-using-moq

        方法二(推薦)

        使用現(xiàn)成的庫(也是基于上面的方式封裝好的)

        倉庫地址:

        • https://github.com/romantitov/MockQueryable

        使用范例

        Copy
        // 1. 測試時創(chuàng)建一個模擬的List<T>
        var users = new List<UserEntity>()
        {
        new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
        ...
        };

        // 2. 通過擴展方法轉(zhuǎn)換成DbSet<UserEntity>
        var mockUsers = users.AsQueryable().BuildMock();

        // 3. 賦值給給mock的DbContext中的Users屬性
        var mockDbContext = new Mock<DbContext>();
        mockDbContext
        .Setup(it => it.Users)
        .Return(mockUsers);

        6.2 HttpClient

        使用RestEase/Refit的場景

        如果使用的是RestEase或者Refit等第三方庫,具體接口的定義本質(zhì)上就是一個interface,所以直接使用moq進行方法mock即可。

        并且建議使用這種方式。

        IHttpClientFactory

        如果使用的是.Net Core自帶的IHttpClientFactory方式來請求外部接口的話,可以參考如下的方式對IHttpClientFactory進行mock

        https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/

        6.3 ILogger

        由于ILogger的LogError等方法都是屬于擴展方法,所以不需要特別的進行方法級別的mock。
        針對平時的一些使用場景封裝了一個幫助類, 可以使用如下的幫助類進行Mock和Verify

        Copy
        public static class LoggerHelper
        {
        public static Mock<ILogger<T>> LoggerMock<T>() where T : class
        {
        return new Mock<ILogger<T>>();
        }

        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, string containMessage, Times times)
        {
        loggerMock.Verify(
        x => x.Log(
        level,
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((o, t) => o.ToString().Contains(containMessage)),
        It.IsAny<Exception>(),
        (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
        times);
        }

        public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, Times times)
        {
        loggerMock.Verify(
        x => x.Log(
        level,
        It.IsAny<EventId>(),
        It.IsAny<It.IsAnyType>(),
        It.IsAny<Exception>(),
        (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
        times);
        }
        }

        使用方法

        Copy
        [Fact]
        public void Echo_ShouldLogInformation()
        {
        // arrange
        var mockLogger = LoggerHelpe.LoggerMock<UserController>();
        var controller = new UserController(mockLogger.Object);

        // act
        controller.Echo();

        // assert
        mockLogger.VerifyLog(LogLevel.Information, "hello", Times.Once());
        }

        7. 拓展

        7.1 TDD介紹

        TDD是測試驅(qū)動開發(fā)(Test-Driven Development)的英文簡稱. 一般是先提前設(shè)計好單元測試的各種場景再進行真實業(yè)務(wù)代碼的編寫,編織安全網(wǎng)以便將Bug扼殺在在搖籃狀態(tài)。

        此種開發(fā)模式以測試先行,對開發(fā)團隊的要求較高, 落地可能會存在很多實際困難。詳細說明可以參考如下

        https://www.guru99.com/test-driven-development.html

        參考鏈接

        • https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices

        • https://www.kiltandcode.com/2019/06/16/best-practices-for-writing-unit-tests-in-csharp-for-bulletproof-code/

        • https://github.com/AutoFixture/AutoFixture







        回復(fù) 【關(guān)閉】學(xué)關(guān)
        回復(fù) 【實戰(zhàn)】獲取20套實戰(zhàn)源碼
        回復(fù) 【被刪】學(xué)
        回復(fù) 【訪客】學(xué)
        回復(fù) 【小程序】學(xué)獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
        回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
        回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
        回復(fù) 【加群】加入dotnet微信交流群

        臥槽:微信又能免費提現(xiàn)了!


        副業(yè)剛需:這個開源小程序外賣紅包項目,有人月入5000+!


        瀏覽 91
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亞洲高潮特黃A片視頻一區 | 日韩成人电影一区二区 | 人人艹逼| 口述交换做爰全过程 | 国产新婚疯狂做爰视频 | 免费在线国产精品 | 免费JJzz在在线播放国产 | 粉嫩人妻| 色戒完整电影高清在线观看 | aaa视频 |