只有累積,沒有奇蹟

顯示具有 Unit Test 標籤的文章。 顯示所有文章
顯示具有 Unit Test 標籤的文章。 顯示所有文章

2021年11月17日 星期三

[NETCore] 如何在 ASP.NET Core Middleware 加上單元測試 Unititest

前言
Middleware 在 ASP.NET Core 開發時是個很常見的功能,概念很像 ASP.NET Application Life cycle 管線的 Handler 機制 (若對於 Life Cycle 想了解更多可以看之前寫的文章 Application Life Cycle),提供開發者可以在 Request 進入到 Application 前加上客製化的邏輯,實務上用起來挺方便的也蠻好用的,在加上 middleware 相關邏輯後也會加上單元測試,確保所撰寫的邏輯有驗證過且是沒有問題,也避免在 middleware 異常時 Devops 同仁半夜叫你起床處理(萬萬不可阿,這篇文章要說明的是如何在 middleware 加上 unittest,對有經驗的開發者相信是一塊小蛋糕,內容若有問題歡迎提出來一起討論。


建立檢查 Token Middleware
舉一個常用的例子,實務上在與第三方在做串接時需要 token 作為對方請求的驗證,作為過濾不必要的請求以節省伺服器的資源。全部 API 接口都需要做 token 檢查因此將此邏輯檢查可以放在 Middleware,首先建立一個新的 .NET Core 3.1 WebAPI 專案,接著建立 TokenVerificationMiddleware.cs 類別,在此類別中加入下列代碼
public class TokenVerificationMiddleware
{
	private readonly RequestDelegate _next;

	public TokenVerificationMiddleware(RequestDelegate next)
	{
		_next = next;
	}

	public async Task Invoke(HttpContext context)
	{
		if (context.Request.Headers.ContainsKey("token") && context.Request.Headers["token"] == "marcusblog")
		{
			await _next.Invoke(context);
		}
		else
		{
			context.Response.StatusCode = 403;  // UnAuthorized
			await context.Response.WriteAsync("Invalid request token");
			return;
		}
	}
}
在此類別的代碼中定義了當請求來時檢查 Request 的 Header 資訊是否有包含 token 關鍵字,token 有的話是否等於 marcusblog,如果都符合就讓此請求往下一步進行進入到下一個流程像是 Action 邏輯;相反的如果不符合 token 檢查邏輯,就會回傳 403 等 Status Code。
當建立好檢查 Token 的 Middleware 之後,接著開啟專案中的 StartUp.cs 類別在 Configure 方法中加上以下代碼
app.UseMiddleware();
上述是使用內建的 UseMiddleware 方法並定義指定的 Middleware 類別,另外還可以使用擴充方法(Extensions Method)達到使用 Middleware,也是個人比較推薦的方式,將要使用的 Middleware 統一集中在特定的class中代碼在閱讀上較為容易理解,使用方式是新增 ApplicationBuilderExtension 類別,裡面新增 UseTokenVerificationMiddleware 方法並定義回傳 IApplicationBuilder 型別,內容與在 startup.cs 的一樣
public static class ApplicationBuilderExtension
{
	public static IApplicationBuilder UseTokenVerificationMiddleware(this IApplicationBuilder builder)
	{
		return builder.UseMiddleware();
	}
}
接著再回到 Configure 方法將 UseTokenVerificationMiddleware 取代原本的方法
//app.UseMiddleware();
app.UseTokenVerificationMiddleware();
開啟偵錯進行測試,可以發現網頁打開會因為 Request 中沒有在 Header 中帶 token 參數出現 Invalid request token,畫面如下


加上單元測試
一般來說請求進到 Application 的 Request 請求的內容會存在於 HttpContext 中,接著再把 HttpContext 的請求傳遞到所指定的 Middleware 邏輯中,也就是在建構子中的參數 HttpContext,那麼要進行單元測試的話第一步是要先模擬請求的 HttpContext 物件,這邊可以使用 DefaultHttpContext 達到這目的,DefaultHttpContext 繼承了 HttpContext 抽象類別,讓我們在單元測試中更為方便,舉例來說如果要模擬 hpptContext 物件並在 Header 加上 token 內容用下列方式就可以達到
var context = new DefaultHttpContext();
context.Request.Headers.Add("token", "this is a book");
DefaultHttpContext 還可以指定測試的路徑(Path)、內容(Body)、ContentType 或是可以指定 Cookie Form 及 QueryString 等常見的設定值,使用上相當的簡易與方便,放在測試案例中代碼如下
public class TokenVerificationMiddlewareTest
{
    private TokenVerificationMiddleware _target;
    [SetUp]
    public void Initial()
    {
        _target = new TokenVerificationMiddleware(null);
    }

    [Test]
    public void Correct_Header_Should_Return_Success()
    {
        var context = new DefaultHttpContext();
        context.Request.Headers.Add("token", "marcusblog");

        var result = _target.Invoke(context);
        context.Response.StatusCode.Should().Be(200);
    }

    [Test]
    public void Without_Header_Should_Return_UnAuthorized()
    {
        var context = new DefaultHttpContext();

        var result = _target.Invoke(context);
        context.Response.StatusCode.Should().Be(403);
    }

    [Test]
    public void Empty_Header_Should_Return_UnAuthorized()
    {
        var context = new DefaultHttpContext();

        var result = _target.Invoke(context);
        context.Response.StatusCode.Should().Be(403);
    }

    [Test]
    public void Wrong_Header_Should_Return_UnAuthorized()
    {
        var context = new DefaultHttpContext();
        context.Request.Headers.Add("token", "wrongtoken");

        var result = _target.Invoke(context);
        context.Response.StatusCode.Should().Be(403);
    }
}
以上是使用 nUnit 搭配 FluentAssertions 套件,測試幾種常見的情境像是 token 為空、header 為空、帶錯誤的 token及帶正確的 token 值 測試後的結果 Pass 成功 !
大功告成,打完收工

感想
在 ASP.NET Core 有了 httpDefaultContext 之後,在針對 Middleware 撰寫單元測試變得方便許多,對有經驗的開發者來說也是小蛋糕一塊,如果有不清楚的地方歡迎一起討論,hope it helps !

參考
為 .NET Core Middleware 加上 Unit Test
DefaultHttpContext Class
ASP.NET Core 執行原理解剖[4]:進入HttpContext的世界
ASP.NET Core 中介軟體
ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware




2021年7月7日 星期三

[Tool] END-TO-END Test 測試工具 - Playwright

前言
過去提到 Web 自動化測試可能第一個念頭會透過 Selenium 來實現 ,今天要介紹的是一套新的 Open Source 自動化測試工具 Playwright,他是基於 Javascript 的跨瀏覽器 end to end 測試工具,目前屬於微軟眾多的 Open Source 專案之一,今天這篇文章就來推坑這款好用的工具,若有問題歡迎提出來一起討論。

介紹
Playwright 是一種 opensource 的自動化測試工具,使用 Node.js 所開發,不僅支援當今常用到的三種瀏覽器 Chrome、Firefox 以及 WebKit,還可透過簡單的設定測試在不同設備上的瀏覽器以及自定義座標與地理位置(Geo),此外,Playwright API 支援的語言有 Javscript、TypeScript、Python 及 C#,文章提到 Playwright 使用 headless browser 以及 Devops Protocol 來執行,因此速度比 Selenium 快且穩定性較高,在使用前可以先了解一下設計的初衷,在 Playwright Github 文件的 README.md 有提到下列說明
We are the same team that originally built Puppeteer at Google, but has since then moved on. Puppeteer proved that there is a lot of interest in the new generation of ever-green, capable and reliable automation drivers. With Playwright, we'd like to take it one step further and offer the same functionality for **all** the popular rendering engines. We'd like to see Playwright vendor-neutral and shared governed.
備註 : Puppeteer 是 Playwright 的前身,其 API 與核心概念都很相似。

安裝 Playwright-cli
由於小弟手邊沒有 Mac 筆電,因此使用的是以 Windows OS 為主,如果要在 Windows 下跑 Playwright 的話需要先安裝 Node.js 及 npm,可以到 Node.js 官方網站下載 Windows 安裝應用程式,如下圖所示

安裝完畢之後,可以透過命令提示字元執行以下指令查詢是否有安裝成功
node -vC:\Users\username>node -v
v12.18.4

C:\Users\username>npm -v
6.14.6
看到兩個指令均有回覆相關資訊,及代表安裝環境成功 !
接著使用 npm 或是 yarn 在 Node.js 安裝 PlayWright (PlayWright需要 Node.js 10 或更高的版本)
$ npx playwright-cli --help

# To save as a dependency
$ npm install -D playwright-cli

使用方式
Code Page
輸入下列指令即可透過 Playwright-cli 開啟新的頁面,舉例來說我想使用 Chrome 瀏覽器來開啟小廢廢的部落格
npx playwright-cli open marcus116.blogspot.com
可以依據需求來調整要測試的瀏覽器,如果想換 webkit 瀏覽器則調整參數 open 改為 wk 即可 備註 : 實際在 windows 執行有點頓,有機會在 Mac 測測看再回報

指定 Device
如果希望測試時指定 Device 的型號,則可以透過 cli 的 --device 參數來指定想要測試的 device 型號 playwright.devices,舉例來說指定設備用 iPhone 開啟 wiki 網頁
npx playwright-cli --device="iPhone 8" open wikipedia.org
呈現畫面如下
模擬地理位置
可以指定地理位置(Geolocation),舉例來說我希望開啟瀏覽器時定位在台北車站附近
npx playwright-cli --geolocation="25.0448717,121.5209451" open maps.google.com
呈現畫面如下
另外還支援截圖與產生 PDF 檔案的功能,詳細可以參考 playwright-cli Github 文件說明。

在 C# 使用
起手式第一步是到 nuget 下載 PlaywrighySharp 套件,目前最新的版本為 0.142
使用 Visual Studio 2019 建立 NUnit 測試專案,我們使用 Chorome 開啟 google.com 並在輸入框輸入 hello world,並加上一些條件像是開啟畫面是 1024 * 1024,headless 設定為 false,按下在下搜尋按鈕,依據此邏輯接著在 UnitTest1 輸入下列代碼
public class Tests
{
    [SetUp]
    public async Task Setup()
    {
    }

    public async Task Browser()
    {
        var playwright = await Playwright.CreateAsync();
        var browser = await playwright.Chromium.LaunchAsync(headless: false);

        return browser;
    }

    public async Task Page()
    {
        var browser = await Browser();
        var page = await browser.NewPageAsync();

        return page;
    }

    [Test]
    public async Task Search_Using_Chrome()
    {
        var page = await Page();
        await page.GoToAsync("https://www.google.com");

        await page.SetViewportSizeAsync(1024, 1024);

        // textbox : hello world
        await page.TypeAsync("input[name='q']", "hello world");

        // Click Button
        await page.ClickAsync("input[name='btnK']");
        
        // Wait 3 second
        await page.WaitForTimeoutAsync(3000);
    }
}
然後在測試總管點選測試,即可看到測試的結果如下 結果正常,大功告成打完收工 !!!

感想
以上針對新的測試工具 Playwright 套件做一些基本的介紹,另外此套工作可以也在 Docker 環境跑 Playwright 腳本,若有興趣可以參考 Running Playwright in Docker,雖然接觸此工具的時間不長但對於前景的發展感覺是很不錯的,日後有機會可以做更深獲是進一步的研究跟分享,以上如果有不清楚的地方歡迎一起討論,hope it helps !

參考
Playwright
WHY DO WE NEED END-TO-END TESTS?
Playwright (vs. Puppeteer): Cross-Browser Testing done right
What is the Microsoft Playwright Automation Tool Guide

2019年7月25日 星期四

[UnitTest] 如何測試目標方法中含有 static method 代碼 ?

情境
由於部門過去的 專案幾乎都沒有加上單元測試進行保護,主管在新的一年規劃中開發代碼更有品質,希望開發的專案加上新功能或是修改時要加上單元測試,有些 Legacy Code 寫法是屬於一條龍式的 「義大利麵式碼」特別有親切感(大誤,遇到這種就需要先重構 (refactor) 後物件化比較好寫單元測試,今天主題是單元測試中要被測試的 method 中常常會有相依於某個 Static method在寫 Code 時用 static 物件寫起來很方便,但遇到這種情況在單元測試就需要多下點功夫,以下簡單筆記如何測試 static class 的方法

解決方案
舉個例子來說有個需求要去呼叫第三方取得相關資訊,定義一個 ThirdParty 類別其中透過 SendRequest 方法來送出 Request 與第三方 API 溝通,使用 static 的 LoggerHelper 記錄第三方回傳的內容 response 物件其中是 Legacy Code 紀錄 log 方式是 Log message 寫到網路硬碟Sample Code 如下
using System;
using NLog;

namespace ConsoleApp1
{
    public class Program
    {
        static void Main(string[] args)
        {
            var thirdParty = new ThirdPartyClass();
            thirdParty.SendRequest();

            Console.ReadKey();
        }
    }

    public class ThirdPartyClass
    {
        public void SendRequest()
        {
            //... call 3rd server
            var response = _apiService.PostData();

            // log response 
            LoggerHelper.Info($"Call 3rd response is : {response}");
            
            //... more
        }
    }
    public static class LoggerHelper
    {
        private static string filePath = "d:\\logs\\";

        public static void Info(string message)
        {
            // 依賴於網路硬碟 X:\logs\info
        }

        public static void Error(string message)
        {
            // 依賴於網路硬碟 X:\logs\error
        }
    }

    [Test()]
    public void ThirdPartyClassTest()
    {
        ThirdPartyClass thirdParty = new ThirdPartyClass();
        thirdParty.SendRequest();
    }
}
在之前上 TDD 課時候老師有提到 單元測試準則 : 一次只驗證一件事 SendRequest 方法直接耦合static function 無法進行隔離測試,91哥也在 文章 中指出直接相依 static function 的主要問題是
  • 無謂地佔住記憶體過久
  • 直接耦合造成無法獨立進行單元測試Line 15 : 取得全部參數方法 
  • 無法享用物件導向設計的好處(繼承的重用與擴充、介面的可抽換性、多型的擴充性)
  • race condition
需要針對此測試方法進行解耦的設計讓 ThirdPartyClass 與 LoggerHelper 都依賴於某物件互相不直接耦合 以達到解耦合的效果過去有學到很多種方法可以達到這件事情,本次介紹的是透過 interface 來解決這問題,說明如下

Step 1 : 首先先建立一個 ILoggerHelper 介面讓 LoggerHelper 實作  
備註 : Resharper 快捷鍵 : Ctrl + R ,I
LoggerHelper 實作 ILoggerHelper 後 Code 如下
public class LoggerHelper : ILoggerHelper
{
    private string filePath = @"d:\\logs\\";

    public  void Info(string message)
    {
        // 依賴於網路硬碟 X:\logs\info
    }

    public  void Error(string message)
    {
        // 依賴於網路硬碟 X:\logs\error
    }    
}

public interface ILoggerHelper
{
    void Error(string message);
    void Info(string message);
}
Step 2 : 此時原本的 ThirdPartyClass 原本依賴的 static LoggerHelper 方法無法使用會出現 error,如下圖所示
Step 3 : 讓 SendRequest 耦合於 ILoggerHelper 介面ThirdParty Class code 如下
public class ThirdPartyClass
{
    private  ILoggerHelper _logger;

    public ThirdPartyClass(ILoggerHelper logger)
    {
        _logger = logger;
    }
       
    public void SendRequest()
    {
        //... call 3rd server
        //var response = _apiService.PostData();

        // log response 
        _logger.Info($"Call 3rd response is :");
        
        //... more
    }
}
程式說明 : 
  • 使用建構式注入 _logger  [ 快捷鍵 :  ctorf ]
  • 寫 log 方式由原先 LoggerHelper 改用 _logger
Step 4 : 在測試建立一個 fakeLoggerHelper 實作 ILoggerHelper 介面,並產生相對應實作方法 Implement  missing member,接著 ThirdPartyClassLog 建構子注入 fakeLoggerHelperCode 如下
[TestFixture()]
public class ThirdPartyClassTests
{
    [Test()]
    public void ThirdPartyClassTest()
    {
        ILoggerHelper fakeLoggerHelper = new fakeLoggerHelper();
        ThirdPartyClass thirdParty = new ThirdPartyClass(fakeLoggerHelper);
        thirdParty.SendRequest();
    }

    private class fakeLoggerHelper : ILoggerHelper
    {
        public void Error(string message)
        {
            // do something
        }

        public void Info(string message)
        {
            // do something
        }
    }
}
Step 4 : 重跑一次測試,綠燈 Pass 測試成功 !!
Summary
這篇文章是讓測試物件依賴於 interface 來解決測試 static method,其他非 static  method 大多使用 extract & overrite 來處理,想了解更多細節可以看 91大大分享的 [Unit Test Tricks] Extract and Override會讓自己對單元測試了解更多,但建議還是要搭配實務才可以驗證到底自己是否真正了解,否則久了沒用(老了?)有一天還是容易忘記

參考

2019年4月17日 星期三

[UnitTest] 使用 Fluent Assertions 增加單元測試碼可讀性

前言
過去在撰寫單元測試代碼時都是使用 NUnit 內建的 Assert.AreEqual 來驗證是否符合預期,雖然早已聽過 Fluent Assertions 盛名但並未實際使用過,直到最近在與同事討論時同事大推發現真的很不錯,讓戴碼的可能性增加不少,想起之前上 91 Training 時不斷強調測試代碼可讀性的重要性,這一篇就來簡單介紹 Flnent Asserentions 的安裝與使用若有問題或是錯誤的地方歡迎各位高手給予指導

安裝 Fluent Assertions
Fluent Assertion 支援 .NET Framework 與 .NET Core,常用的測試框架像是 NUnit、MSTest、xUnit 等都支援,一開始在 Visual Studio 2019 建立名稱叫做 FluentAssertionsConsoleApp 的 ConsoleApp專案,接著在專案 Program.cs 輸入要測試的程式碼 Add 方法,如下所示
public class TestMethod
{
    public int Add(int numberOne, int numberTwo)
    {
        return numberOne + numberTwo;
    }
}
接著要建立測試專案,在 Visual Studio 2019 建立專案介面有做調整,可以透過搜尋輸入框及下拉選單過找到想要建立的專案範本,這陣子使用操作上覺得挺方便的,如下圖所示,在搜尋框輸入 NUnit 選定 NUmit 專案並按下一步 
建立完畢 NUnit 測試專案後,下一步是在測試專案安裝 Fluent Assertions nuget 套件,步驟如下
Step 1 : 按下 CTRL + Q 輸入 Nuget,開啟 nuget 
Step 2 : 在 Nuget Package Mnager 輸入 FluentAssertions,目前最新版為 5.6.0 按下安裝
安裝完畢後確認測試專案有 Fluent Assertional 即代表安裝成功

測試代碼
接著要開始測試 TestMethod 中的 Add 方法,開始嘗試用 Fluent Asserentions 語法寫測試,代碼如下
using FluentAssertions;
using FluentAssertionsConsoleApp;
using NUnit.Framework;

namespace Tests
{
    public class Tests
    {       
        [Test]
        public void AddTest_Using_FluentAssertions()
        {
            var testMethod = new TestMethod();
            var actual = testMethod.Add(2, 4);
            actual.Should().Be(6);
        }
    }
}
使用 Fluent Assertions 寫法後代碼變得容易理解許多,actual.Should().Be(6) 用口語化解釋為 " 測試的結果應該為 6 ",另外 Be 方法也提供 Because 參數,加上後可以讓語句與閱讀上更加通順,加上 Because 後代碼如下
[Test]
public void AddTest_Using_FluentAssertions_Becasue()
{
    var testMethod = new TestMethod();
    var actual = testMethod.Add(2, 2);
    actual.Should().Be(4, because:"2+2=4");
}
測試案例加上 because 後當測試失敗時,可以透過錯誤訊息內容讓你或是你的團隊成員更容易理解測試的目的,測試失敗的 Testcase 如下
透過 Resharper 可以看到 Should 擴充方法內容如下,Should 與 Be 都是 FluentAssertions 針對 int 型別定義的擴充方法 ( Extension Methods ),透過 F12 後可以看到內容

/// <summary>
/// Returns an <see cref="T:FluentAssertions.Numeric.NumericAssertions`1" /> object that can be used to assert the
/// current <see cref="T:System.Int32" />.
/// </summary>
[Pure]
public static NumericAssertions<int> Should(this int actualValue)
{
  return new NumericAssertions<int>((object) actualValue);
}
擴充方法可以參考 MSDN 說明 : 傳送門,擴充方法可以在很多地方看到像是 LINQ 中的 Where ,針對集合物件做篩選的動作在回傳 IEnumerable<T>,在 Fluent Assertions 針對現有類別加入方法,方便使用 Should、Be 或是其他語法,方便在單元測試中撰寫,以下列出一些常用的方式,

public void TestBasicMethod()
{
    // object
    object obj = null;
    obj.Should().BeNull("because the obj is null");
    obj.Should().NotBeNull();

    // string 
    var something = "something";
    something.Should().BeEmpty();
    something.Should().NotBeEmpty();

    // datetime 
    var time= new DateTime();
    time.Should().HaveYear(2019);
    time.Should().HaveDay(4);
    time.Should().HaveMonth(3);
    time.Should().HaveHour(22);
    time.Should().HaveMinute(15);
    time.Should().HaveSecond(0);

    // dictionary 
    var dic = new Dictionary<int, string>()
    {
        {1, "Marcus"},
        {2, "Flash"},
        {3, "Neil"}
    };
    dic.Should().NotBeEmpty();
    
    // type
    something.Should().BeOfType<string>("because a {0} is set", typeof(string));
    something.Should().BeOfType(typeof(string), "because a {0} is set", typeof(string));

    // equal 
    string otherObject = "whatever";
    something.Should().Be(otherObject, "because they have the same values");
    something.Should().NotBe(otherObject);

    // exception 
    something.Should().BeOfType<Exception>()
        .Which.Message.Should().Be("Other Message");
}

如果您希望測試代碼更容易理解,建議你可以使用 Fluent Assertion Library 來協助撰寫單元測試,更多的測試語法可以參考官方文件說明,這篇就簡單介紹 Fluent Assertion,日後如果有遇過不錯的寫法也會在一併分享給各位,Happy Coding :)

參考
Fluent Assertions 
讓單元測試代碼更好寫、好讀:Fluent Assertions

2019年4月12日 星期五

[UnitTest] Refactoring 好幫手 - Refactoring.Guru

前言
過去還在菜鳥時期很常遇過一種情境,就是當看到一段既有的代碼或是專案裡的 Code,看起來有些怪異的地方但又說不出來怪的點是哪一點,想改又不知道從何下手的情境,如果遇到這問題想要得到解答的話,可以試試看到 refactoring.guru,這網站透過漫畫的方式介紹兩項開發者在 Coding 時的必修課程,重構 ( Refacting ) 與設計模式 ( Design Pattern );以下就針對這兩項目做簡單說明

Refactoring
除了閱讀經典書籍 重構:改善既有代碼的設計 之外,可以透過網站整理重構相關的幾個議題說明
  • Refactoring : 重構的目的是消除技術債,介紹容易維護及乾淨的代碼帶來什麼樣的好處
  • Dirty Code : 什麼是技術債以及造成的影響
  • Refactoring Process : 如何重構? 介紹 Code Smell 及遇到該如何解決程式碼的壞味道
  • Clean Code : 甚麼是乾淨的程式碼 ?
個人覺得實用的部分是針對 Code Smell 的介紹,以及為甚麼這段 Code 需要 (why) 重構還有如何(how) 重構它,讓你瞭解這段代碼背後的問題,遇到了可以用什麼樣的方式來解決它,不像是一些文章用比較生硬的角度切入在理解上比較難懂,在配上漫畫圖片的介紹,加速對於問題的理解,舉例來說如果遇到 Code 有一段內容很長的方法,什麼樣的情境符合 Long Method,網站建議解決方是 extract method 以及下圖說明 


如果想了解更多,可以到網站分類 Refactor : 傳送門

Design Pattern

設計模式在開發時也是必修學分之一,這部份自己也尚在領悟中就不在班門弄斧介紹,有興趣的可以直接點選頁面更快 : 傳送門,網站介紹的 Sample Code : GitHub 傳送門

同場加映
其實在過去當新進同仁進公司時,都會推薦另一個類似的網站叫做 source making,讓有興趣的新進同仁可以自行去研究如果有時間可以跟大家分享,在 source making 分類與今天介紹的十分相似,有 design pattern、Antipatterns、Refactoring 以及 UML 等四大類,其中插圖風格也是頗為相似 (不確定是否為相同作者),如果有興趣的朋友也可以加減看看,這裡就廢話不多說直接附上 傳送門

參考
refactoring.guru

2019年3月28日 星期四

[UnitTest] ASP.NET Core 2.2 測試專案中的版本衝突

問題 
這幾天專案某項功能接近尾聲,要替其核心 ASP.NET Core 專案加上單元測試專案,加入後按下建置發現跳出 Error 錯誤訊息  "CS1705 Assembly 'xxxx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' uses 'Microsoft.Extensions.Logging.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' which has a higher version than referenced assembly 'Microsoft.Extensions.Logging.Abstractions' with identity 'Microsoft.Extensions.Logging.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' "  加一個測試專案就造成參考 dll 版本不同,且測試專案並未加入任何參考類別庫就建置失敗怎麼想都不太對,這篇就針對此案例作簡單紀錄與分享若是有不清楚或是錯誤的地方歡迎討論予糾正

解決方法 
就自己過去經驗這類訊息是因為專案參考版本不同造成,但為了保險還是看一下案發現場錯誤訊息提到的 CS1705 ,提示訊息屬於編譯器錯誤,傳送門 
接著到專案與測試專案的參考進行比對,比對其 dll 的版本與新加入的測試專案 dll 版本號,經過地毯式的比對後發現其錯誤訊息提到的 dll 版號皆為相同並無異常,因此判定此推論不成立
接著,想到錯誤訊息提到 .NET Core 版本不同,因此開啟專案檢查兩個專案版本差異,檢查方式如下
專案按右鍵 > 屬性 > Application > Target Framework,經比對後兩者皆為 ASP.NET Core 2.2 版無誤,猜測再度失敗 !
快要走投無路的時候發現在微軟 github 也有許多人遇到類似的問題,討論傳送門如下
 Version conflicts in test project depending on a Microsoft.AspNetCore.App project #2253
而且在 ASP.NET Core 2.2 此問題似乎尚未解決 (狀態還沒 close),整理一下共通的症狀如下
  • 測試專案
  • 無法編譯的 dll 為 Mircosoft.AspNetCore、Mircosoft.Extension 
如果符合以上疑似症狀,嘗試了各種方法還是沒有辦法建置成功的話,請服用以下步驟
Step 1 : 專案按下右鍵,編輯專案檔 csproj
Step 2 : 在  itemGroup  區段加入下列代碼 (如果在不行可以指定版本)
<PackageReference Include="Microsoft.AspNetCore.App" />
接著在重新將專案建置一次,就可以看到建置成功的畫面 (淚)
擦乾眼淚宣告懸案正式結案 !! 

心得
神奇的是只要建置成功過一次之後,就無法再還原發現場狀況,不指定 Mircosoft.AspNetCore.App 版號也可以正常建置成功 (嚇.jpg),不知道算不算是 .Core 2.2 的bug,如果是希望新版出來時可以修復此問題,不然光看錯誤訊息要修復可能真的不太容易。

參考
https://github.com/dotnet/sdk/issues/2253

2019年3月21日 星期四

[UnitTest] 如何測試目標方法中 Guid 型別的代碼 ?

情境
如果要產生一個亂數時很常會想到 Guid 方法解決,在 C# 使用 Guid 的方式相當簡單僅要透過  Guid.NewGuid  靜態方法產生一組 Guid 使用,在目前公司很常看到使用 Guid 作為識別碼,今天在重構舊代碼時忽然想到如果遇到 Guid 該如何進行加上單元測試,以下就目前想到的解法進行測試與說明,如果各位高手們有更好的方法歡迎高抬貴手一起討論研究。

解決方案
寫個簡單的 Sample 方法讓整個情境可以更容易了解,舉個例子來說有個 Generator 類別提供 NewGuidToken 方法產生 Guid 資料並回傳給呼叫端,Sample Code 如下
public static class Generator
{
    public static Guid NewGuidToken()
    {
        return Guid.NewGuid();
    }
}
第一時間讓我想到之前上 TDD 遇到測試今天是不是聖誕節案例時很像,但特別為了 Guid 寫一個介面讓使用的 class 實做,在透過建構子注入就可以斷開其依賴關係,但這 case 算是很單純個人覺得沒必要做這麼複雜的工,忽然想到在 C# 中每個資料型別物件大多都提供  TryParse  進行轉換,且該方法回傳結果為 true / fales bool型別,與 UnitTest 中的 Assert 搭配使用相當方便,單元測試 Sample Code 如下
[TestFixture()]
public class GeneratorTests
{
    [Test()]
    public void Input_NewGuid_Return_True()
    {
        // act 
        var input = Generator.NewGuidToken();

        // assert
        Assert.IsTrue(Guid.TryParse(input.ToString(), out Guid output));
    }
    [TestCase("Teset123")]
    [TestCase("Marcus")]
    public void Input_FakeGuid_Return_True(string input)
    {
        Assert.IsFalse(Guid.TryParse(input, out Guid output));
    }
} 
當你的情境只需要驗證產生出來的是否符合 Guid 時,就可以直接透過  Assert.IsTrue(your guid string)   來確認 Guid 格式是否正確 (第 11 行 );反之,如果要驗證格不符合時透過不是時用 Guid.TryParse + Assert.IsFalse 即可 ( 第17行 ),相當容易上手且閱讀性很高。

接著在來確認單元測試後的結果,其單元測試結果都執行正常
三份測試都通過且驗證無誤,綠燈 Pass 測試成功 !!

Summary
以上就是針對 Guid 型別寫單元測試的方法,也是小弟的矬見,如果各位大大有更好的方式歡迎一起討論跟溝通 :)


2019年1月21日 星期一

[UnitTest] NUnit 測試例外 exception - 使用 Assert.Throws

前言
介紹了如何使用 NUnit 撰寫單元測試、使用 TestCase 處理參數化方法測試不同情境,如果寫單元測試時想要驗證例外狀況時該怎麼處理 ? NUnit 分別在 NUnit 2 & 3 中提供 ExpectedException、Assert.Throws 讓開發者處理測試例外的情境,以下簡單分享如何使用

使用說明
要驗證的 sample Code 如下,Example 類別有個 WorkTime 方法內容為只要工作超過 12 小時就會拋出自訂的 AngryException 錯誤訊息為我要森77了(幼稚)
public class Example
{
    public string Worktime(int hours)
    {
        if (hours > 12)
        {
            throw new AngryException("我森77!!");
        }

        // todo something
        return "正常";
    }

    public class AngryException : Exception
    {
        public AngryException(string message) : base(message)
        {
        }
        public override string Message
        {
            get { return "AngryException"; }
        }
    }

}
NUnit 2 
在 NUnit 2.x 針對驗證例外提供了 ExpectedException在要驗證的方法加上 attribute 指定要驗證的 exception 類別,如果需要驗證錯誤的訊息內容可加上 ExpectedMessage 參數,sample code 如下
[Test()]
[ExpectedException(typeof(AngryException))]
public void WorktimeTest_worktime_over15hour_ExpectedException()
{
    var ex = new Example();
    ex.Worktime(15);
}

[Test()]
[ExpectedException(typeof(AngryException), ExpectedMessage = "我森77!!")]
public void WorktimeTest_worktime_over15hour_ExpectedExceptionMsg()
{
    var ex = new Example();
    ex.Worktime(15);
}
NUnit 3 
在 NUnit 3 版本提供新的驗證例外方法 Assert.Throws 來取代 ExpectedException,使用委派來驗證是否有拋出指定的 exception,如果需要驗證非同步的例外也可以使用 Assert.ThrowsAsync使用上相當簡單請參考 sample code 如下
[Test()]
public void WorktimeTest()
{
    var ex = new Example();
    Assert.Throws<AngryException>(() => ex.Worktime(15));
}

[Test()]
public void WorktimeTest_Message()
{
    var ex = new Example();
    Assert.Throws<AngryException>(() => ex.Worktime(15), "我森77!!");
}

ExpectedException 移轉
在 NUnit 3 已無法使用 ExpectedException搜尋原因後在 Why was ExpectedExceptionAttribute removed 中有提到可能的原因為 ExpectedException was removed because it encourages bad practices and it was generally felt that removing it helped developers fall into the pit of success.,Assert.Throws 與 ExpectedException 相較之下,在撰寫單元測試時更明確的指出預期錯誤的代碼與模組如果之前是使用 NUnit 2 ExpectedException 驗證例外狀況可以使用 ExpectedException 及 NUnit.That.Resharper.Plugin 進行移轉的動作,有需要的人可以自行研究看看

參考
NUnit
ExpectedExceptionAttribute
Assert.Throws
nunit issues 799

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com