只有累積,沒有奇蹟

顯示具有 Nunit 標籤的文章。 顯示所有文章
顯示具有 Nunit 標籤的文章。 顯示所有文章

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年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

2019年1月20日 星期日

[UnitTest] NUnit 參數測試 - 使用 TestCase、TestCaseSource

前言
上一篇 NUnit 入門教學 簡單介紹如何使用 NUnit 撰寫單元測試,但在實務上在寫單元測試時不會那麼單純,舉例來說如果要針對某個 method 進行種不同情境的測試,僅差在不同的參數來多次驗證結果
NUnit 官方文件中有提供很多好用的方法像是 TestCaseTestCaseSource 等方式可以解決此問題,以下會簡單分享小小研究後的說明與差異性,依據適合的情境使用相對應的方法

解決方案
要驗證的 sample Code 如下,Example 類別有個 Reverse 方法內容為將輸入的字串反轉
public class Example
{
    public string Reverse(string input)
    {
        char[] charArray = input.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }
}
為了驗證該方法是否正確,因此準備多種情境的參數來進行單元測試,預計測試資料如下
  • input : abc | output : cba
  • input : marcus output : sucram
  • input : apple output : elppa
撰寫多個 Method 
如果沒有使用參數化進行測試時,預估會根據每個要驗證的內容撰寫一個 method 來對應,為了在 Test Explorer 在眾多 Test cases 好識別,建議定義命名規則 ReverseTest_Input_輸入參數_Return_回傳參數,sample code 如下
using NUnit.Framework;

namespace ConsoleApp2.Tests
{
    [TestFixture()]
    public class ExampleTests
    {
        [Test()]
        public void ReverseTest_Input_abc_Return_cba()
        {
            //Arrange
            var ex = new Example();
            var input = "abc";
            
            //Act
            var actual = ex.Reverse(input);

            //Assert
            Assert.AreEqual("cba", actual);
        }
        [Test()]
        public void ReverseTest_Input_marcus_Return_sucram()
        {
            //Arrange
            var ex = new Example();
            var input = "marcus";

            //Act
            var actual = ex.Reverse(input);

            //Assert
            Assert.AreEqual("sucram", actual);
        }
        [Test()]
        public void ReverseTest_Input_apple_Return_elppa()
        {
            //Arrange
            var ex = new Example();
            var input = "apple";

            //Act
            var actual = ex.Reverse(input);

            //Assert
            Assert.AreEqual("elppa", actual);
        }
    }
} 
缺點會如果測試案例很多的化會有很多重複的 Code,避免重複可能會自己寫一個類別用集合跟迴圈來解決重複 Code的問題

使用 TestCase 
在 NUnit 中提供 TestCase Attribute 來透過不同數據來測試,首先改寫 ReverseTest 方法將輸入值預期結果當作參數,接著在要測試的方法上掛 TestCase attribute 並定義要測試的參數資料,看起來 Code 比較乾淨沒有重複的代碼sample code 如下
using NUnit.Framework;

namespace ConsoleApp2.Tests
{
    [TestFixture()]
    public class ExampleCaseTests
    {
        [Test()]
        [TestCase("abc", "cba")]
        [TestCase("marcus", "sucram")]
        [TestCase("apple", "elppa")]
        public void ReverseTest(string input, string output)
        {
            var ex = new Example();
            var actual = ex.Reverse(input);
            Assert.AreEqual(input, actual);
        }
    }
} 

使用 TestCaseSource 
NUnit 另外也提供 TestCaseSource 測試不同數據,在 ReverseTest 方法使用 TestCaseSource 定義要測試的集合名稱 TestDataCases,另外在 TestDataCases 定義要測試的數據內容集合sample code 如下

using NUnit.Framework;
using System.Collections;

namespace ConsoleApp2.Tests
{
    [TestFixture()]
    public class ExampleCaseDataTests
    {
        [Test, TestCaseSource("TestDataCases")]
        public void ReverseTest(string input, string output)
        {
            var ex = new Example();
            var actual = ex.Reverse(input);
            Assert.AreEqual(output, actual);
        }

        public static IEnumerable TestDataCases
        {
            get
            {
                yield return new TestCaseData("abc","cba");
                yield return new TestCaseData("marcus","sucram");
                yield return new TestCaseData("apple","elppa");
            }
        }
    }
} 
TestCaseData 提供 returns 方法,如果要測試的方法是有回傳值時可以在 return 方法加上參數來驗證回傳值是否正確

Test Explore 可讀性
三種方式都可以解決問題(第一種有點來亂的?),但可讀性也是蠻重要的一個環節,可以比對三者在 Test Explore 呈現的結果,以這 sample code 簡單的測試案例來說 TestCase 與 TestCaseSource 可讀性來看是較好的,測試代碼上亦是如此,但如果在較為複雜的測試案例上或許有不同結果,各位可以在實作時自己斟酌使用適合的方式

參考
NUnit
Test Generator NUnit extension
TestCase Attribute



2019年1月19日 星期六

[UnitTest] NUnit 入門教學

前言
NUnit 是一個 open source 的 .Net 單元測試框架,根據官方統計下載次數達到 3000 萬次,支援 .Net Framework 與 .Net Core,近幾年開始在寫單元測試以來一直是使用 NUnit 寫單元測試為主,最近計畫把常用的或是上課學到好用的功能寫成文章分享出來,以下簡單介紹如何在.Net Framework 使用 NUnit 寫單元測試

安裝 NUnit
首先我們到 Visual Studio | Marketplace 下載 Test Generator NUnit extension,支援 NUnit 2.6 及 3 以上版本,也支援 NUnit Test 與 IntelliTests ( 僅 VS Enterprise 支援 ),支援在方法上按右鍵快速產生單元測試

建立單元測試
這邊準備好一份簡單的 sample Code,Example 類別底下有個 Reverse 方法內容為將輸入的字串做反轉的動作,我們要對字串反轉的方法撰寫單元測試,步驟說明如下
public class Example
{
    public string Reverse(string input)
    {
        char[] charArray = input.ToCharArray();
        Array.Reverse(charArray);
        return new string(charArray);
    }
}
Step 1 : 在 Reverse 方法上按右鍵,選擇 Create Unit Test
Step 2 : Test Framework 選擇 NUnit3,按下OK  (沒有NUnit選項請參考此網址)
在新增單元測試時可以選擇 / 輸入以下資訊
  • 是需要新建立一個測試專案或是在既有專案中
  • 測試專案、測試類別、方法的命名
  • Namespace 以及產生單元測試時 Assert 預設值
Step 3 : 會依據你上列選擇的項目產生下列要被測試類別內容
using NUnit.Framework;
using ConsoleApp2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2.Tests
{
    [TestFixture()]
    public class ExampleTests
    {
        [Test()]
        public void ReverseTest()
        {
            Assert.Fail();
        }
    }
} 
這裡簡單說明
  • 自動 using NUnit.Framework 做為測試的框架 和 要被測試的類別 ConsoleApp2
  • 測試類別名稱為 被測試的類別+Tests
  • 預設 Assert fail
撰寫單元測試
產生了 UnitTest 的 Template 之後,接下來開始使用 3A 撰寫單元測試內容
  • Arrange : 測試物件的初始化、定義需要使用的參數資訊 
  • Act : 呼叫被測試的方法 
  • Assert : 驗證結果
Step 1 : 根據以上產生下列代碼
namespace ConsoleApp2.Tests
{
    [TestFixture()]
    public class ExampleTests
    {
        [Test()]
        public void ReverseTest()
        {
            //Arrange
            var ex = new Example();
            var input = "abc";
            
            //Act
            var actual = ex.Reverse(input);

            //Assert
            var expected = "cba";
            Assert.AreEqual(expected, actual);
        }
    }
}
Step 2 : 測試方法按下右鍵 > Run Tests (快捷鍵 : Ctrl + R T )
Step 3 : Visual Studio 會根據所選的測試進行驗證,執行結果可以在 Test Explorer 看到紅燈或綠燈 

快捷鍵
以上為 NUnit 簡單的介紹,另外在執行單元測試時可以透過快捷鍵更快的驗證單元測試的結果,以下分享我常用的快捷鍵
  • Run Test : Ctrl + R,T 
  • Debug Test : Ctrl + R, Ctrl T 
  • Run All Test : Ctrl + R, A 
  • Repert Last Run : Ctrl + R, L 
參考
NUnit
Test Generator NUnit extension



2019年1月1日 星期二

[UnitTest] Visual Studio 2017 新增單元測試時沒有 NUnit 選項 ?

前言
新筆電總是讓人充滿驚喜在使用上除了要重新安裝常用的開發工具外,像是 Visual Studio & SQL Server..等必須工具,還有些常用的套件與小工具也要一併安裝,NUnit 是蠻常用的 Test Framwroek 之一,在寫測試時在要測試的 method 下按下右鍵 create Unit Tests,選擇要使用測試的 Test Framework 即可,在測試專案中預設只有 MSTest 選項,如果需要 NUnit 則需要另外安裝,這篇文章簡單紀錄安裝過程

安裝 NUnit extension
簡單整理安裝完畢會發生甚麼事情
- Download URL 
- Visual Studio 2017 使用
- 預設會安裝 NUnit 2 & 3
- 新增 IntelliTest (Enterprise Only)

套件安裝
Step 1 : 進入 Test Generator NUnit extension > 按下 Download 下載套件
Step 2 : 會跳出需要安裝的 IDE 版本接著無腦式的按下一步
Step 3 : 安裝過程會提醒你關閉 Visual Studio安裝完畢後如下圖
Step 3 : 安裝過程會提醒你關閉 Visual Studio安裝完畢後如下圖

重新開啟 Visual Studio
大功告成,新增 Unit Test 選單出現 NUnit 了啦 !! 
參考
Get started with unit testing

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

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com