這篇文章主要介紹了Net Core后端單元測試的實(shí)現(xiàn)方法,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
網(wǎng)站設(shè)計、網(wǎng)站建設(shè)服務(wù)團(tuán)隊是一支充滿著熱情的團(tuán)隊,執(zhí)著、敏銳、追求更好,是創(chuàng)新互聯(lián)的標(biāo)準(zhǔn)與要求,同時竭誠為客戶提供服務(wù)是我們的理念。創(chuàng)新互聯(lián)把每個網(wǎng)站當(dāng)做一個產(chǎn)品來開發(fā),精雕細(xì)琢,追求一名工匠心中的細(xì)致,我們更用心!單元測試一直都是"好處大家都知道很多,但是因為種種原因沒有實(shí)施起來"的一個老大難問題。具體是否應(yīng)該落地單元測試,以及落地的程度, 每個項目都有自己的情況。
本篇為個人認(rèn)為"如何更好地寫單元測試", 即更加偏向?qū)嵺`向 中夾雜一些理論的分享。
下列示例的單元測試框架為xUnit
, Mock庫為Moq
優(yōu)點(diǎn)有很多, 這里提兩點(diǎn)我個人認(rèn)為的很明顯的好處
通常在進(jìn)行新功能/模塊的開發(fā)或者是重構(gòu)的時候,測試會進(jìn)行回歸測試原有的已存在的功能,以驗證以前實(shí)現(xiàn)的功能是否仍能按預(yù)期運(yùn)行。
使用單元測試,可在每次生成后,甚至在更改一行代碼后重新運(yùn)行整套測試, 從而可以很大程度減少回歸缺陷。
當(dāng)代碼緊密耦合或者一個方法過長的時候,編寫單元測試會變得很困難。當(dāng)不去做單元測試的時候,可能代碼的耦合不會給人感覺那么明顯。為代碼編寫測試會自然地解耦代碼,變相提高代碼質(zhì)量和可維護(hù)性。
3A分別是"arrange、act、assert", 分別代表一個合格的單元測試方法的三個階段
事先的準(zhǔn)備
測試方法的實(shí)際調(diào)用
針對返回值的斷言
一個單元測試方法可讀性是編寫測試時最重要的方面之一。 在測試中分離這些操作會明確地突出顯示調(diào)用代碼所需的依賴項、調(diào)用代碼的方式以及嘗試斷言的內(nèi)容.
所以在進(jìn)行單元測試的編寫的時候, 請使用注釋標(biāo)記出3A的各個階段的, 如下示例
[Fact] public async Task VisitDataCompressExport_ShouldReturnEmptyResult_WhenFileTokenDoesNotExist() { // arrange var mockFiletokenStore = new Mock(); mockFiletokenStore .Setup(it => it.Get(It.IsAny ())) .Returns(string.Empty); var controller = new StatController( mockFiletokenStore.Object, null); // act var actual = await controller.VisitDataCompressExport("faketoken"); // assert Assert.IsType (actual); }
盡管私有方法可以通過反射進(jìn)行直接測試,但是在大多數(shù)情況下,不需要直接測試私有的private方法, 而是通過測試公共public方法來驗證私有的private方法。
可以這樣認(rèn)為:private方法永遠(yuǎn)不會孤立存在。更應(yīng)該關(guān)心的是調(diào)用private方法的public方法的最終結(jié)果。
如果一個類/方法,有很多的外部依賴,造成單元測試的編寫困難。那么應(yīng)該考慮當(dāng)前的設(shè)計和依賴項是否合理。是否有部分可以存在解耦的可能性。選擇性重構(gòu)原有的方法,而不是硬著頭皮寫下去.
3.4 避免多個斷言
如果一個測試方法存在多個斷言,可能會出現(xiàn)某一個或幾個斷言失敗導(dǎo)致整個方法失敗。這樣不能從根本上知道是了解測試失敗的原因。
所以一般有兩種解決方案
拆分成多個測試方法
使用參數(shù)化測試, 如下示例
[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(actual); }
當(dāng)然如果是對對象進(jìn)行斷言, 可能會對對象的多個屬性都有斷言。此為例外。
一般有兩種。比如針對UserController
下方法的單元測試應(yīng)該統(tǒng)一放在UserControllerTest
或者UserController_Test
下
單元測試方法名
單元測試的方法名應(yīng)該具有可讀性,讓整個測試方法在不需要注釋說明的情況下可以被讀懂。格式應(yīng)該類似遵守如下
<被測試方法全名>_<期望的結(jié)果>_<給予的條件> // 例子 [Fact] public void Add_InputNullOrAlphabetic_ThrowsArgumentException() { ... }
編寫.Net Core的單元測試?yán)@不過要選擇一個單元測試的框架, 三大單元測試框架中
MsTest是微軟官方出品的一個測試框架
NUnit沒用過
xUnit是.Net Foundation下的一個開源項目,并且被dotnet github上很多倉庫(包括runtime)使用的單元測試框架
三大測試框架發(fā)展至今已是大差不差, 很多時候選擇只是靠個人的喜好。
個人偏好xUnit
簡潔的斷言
// xUnit Assert.True() Assert.Equal() // MsTest Assert.IsTrue() Assert.AreEqual()
客觀地功能性地分析三大框架地差異可以參考如下
https://anarsolutions.com/automated-unit-testing-tools-comparison
官方倉庫
https://github.com/moq/moq4
Moq是一個非常流行的模擬庫, 只要有一個接口它就可以動態(tài)生成一個對象, 底層使用的是Castle的動態(tài)代理功能.
基本用法
在實(shí)際使用中可能會有如下場景
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 { ... } } }
在進(jìn)行單元測試的時候, 可以使用Moq
對_userService.GetUser
進(jìn)行模擬返回值
[Fact] public void GetUser_ShouldReturnNotFound_WhenCannotFoundUser() { // arrange // 新建一個IUserService的mock對象 var mockUserService = new Mock(); // 使用moq對IUserService的GetUs方法進(jìn)行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()); }
官方倉庫
https://github.com/AutoFixture/AutoFixture
AutoFixture是一個假數(shù)據(jù)填充庫,旨在最小化3A中的arrange
階段,使開發(fā)人員更容易創(chuàng)建包含測試數(shù)據(jù)的對象,從而可以更專注與測試用例的設(shè)計本身。
基本用法
直接使用如下的方式創(chuàng)建強(qiáng)類型的假數(shù)據(jù)
[Fact] public void IntroductoryTest() { // arrange Fixture fixture = new Fixture(); int expectedNumber = fixture.Create(); MyClass sut = fixture.Create (); // act int result = sut.Echo(expectedNumber); // assert Assert.Equal(expectedNumber, result); }
上述示例也可以和測試框架本身結(jié)合,比如xUnit
[Theory, AutoData] public void IntroductoryTest( int expectedNumber, MyClass sut) { // act int result = sut.Echo(expectedNumber); // assert Assert.Equal(expectedNumber, result); }
Visual Studio提供了完備的單元測試的支持,包括運(yùn)行. 編寫. 調(diào)試單元測試。以及查看單元測試覆蓋率等。
如下功能需要Visual Studio 2019 Enterprise版本,社區(qū)版不帶這個功能。
如何查看覆蓋率
在測試窗口下,右鍵相應(yīng)的測試組 點(diǎn)
點(diǎn)擊如下的"分析代碼覆蓋率"
主要
使用EF Core過程中,如何mock DbSet是一個繞不過的坎。
方法一
參考如下鏈接的回答進(jìn)行自行封裝
https://stackoverflow.com/questions/31349351/how-to-add-an-item-to-a-mock-dbset-using-moq
方法二(推薦)
使用現(xiàn)成的庫(也是基于上面的方式封裝好的)
倉庫地址:
https://github.com/romantitov/MockQueryable
使用范例
// 1. 測試時創(chuàng)建一個模擬的Listvar users = new List () { new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")}, ... }; // 2. 通過擴(kuò)展方法轉(zhuǎn)換成DbSet var mockUsers = users.AsQueryable().BuildMock(); // 3. 賦值給給mock的DbContext中的Users屬性 var mockDbContext = new Mock (); mockDbContext .Setup(it => it.Users) .Return(mockUsers);
使用RestEase/Refit的場景
如果使用的是RestEase
或者Refit
等第三方庫,具體接口的定義本質(zhì)上就是一個interface,所以直接使用moq進(jìn)行方法mock即可。
并且建議使用這種方式。
IHttpClientFactory
如果使用的是.Net Core自帶的IHttpClientFactory
方式來請求外部接口的話,可以參考如下的方式對IHttpClientFactory
進(jìn)行mock
https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
由于ILogger的LogError等方法都是屬于擴(kuò)展方法,所以不需要特別的進(jìn)行方法級別的mock。
針對平時的一些使用場景封裝了一個幫助類, 可以使用如下的幫助類進(jìn)行Mock和Verify
public static class LoggerHelper { public static Mock> LoggerMock () where T : class { return new Mock >(); } public static void VerifyLog (this Mock > loggerMock, LogLevel level, string containMessage, Times times) { loggerMock.Verify( x => x.Log( level, It.IsAny (), It.Is ((o, t) => o.ToString().Contains(containMessage)), It.IsAny (), (Func )It.IsAny
使用方法
[Fact] public void Echo_ShouldLogInformation() { // arrange var mockLogger = LoggerHelpe.LoggerMock(); var controller = new UserController(mockLogger.Object); // act controller.Echo(); // assert mockLogger.VerifyLog(LogLevel.Information, "hello", Times.Once()); }
TDD是測試驅(qū)動開發(fā)(Test-Driven Development)的英文簡稱. 一般是先提前設(shè)計好單元測試的各種場景再進(jìn)行真實(shí)業(yè)務(wù)代碼的編寫,編織安全網(wǎng)以便將Bug扼殺在在搖籃狀態(tài)。
此種開發(fā)模式以測試先行,對開發(fā)團(tuán)隊的要求較高, 落地可能會存在很多實(shí)際困難。詳細(xì)說明可以參考如下
https://www.guru99.com/test-driven-development.html
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Net Core后端單元測試的實(shí)現(xiàn)方法”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司,,關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!