程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 捨棄Nunit擁抱Xunit,nunit擁抱xunit

捨棄Nunit擁抱Xunit,nunit擁抱xunit

編輯:C#入門知識

捨棄Nunit擁抱Xunit,nunit擁抱xunit


前言

   今天與同事在討論.Net下測試框架的時候,說到NUnit等大多數測試框架的SetUp以及TearDown方法並不是顯得那麼完美,所以在公司內部的項目中采用了Xunit框架。那麼究竟是什麼樣的原因,讓我們放棄了大多數框架都在用的Nunit或MSTest框架呢?

1. Xunit簡介

  首先奉上馬丁大叔2006年對XUnit介紹的文章,http://www.martinfowler.com/bliki/Xunit.html。

  Xunit其實是JUnit的衍生版,最開始是應用在Smalltalk中,其目的是支持持續集成,關於單元測試等相關內容可以參考我之前TDD系列文章,這裡不做過多的介紹,只是介紹Why we choose Xunit。

  GitHub地址:https://github.com/xunit/xunit

  官方文檔:http://xunit.github.io/

 

2. Xunit簡單Demo

如此簡單:

 

提示:需要通過NuGet下載xunit.net和xunit.visualstudio這兩個安裝包,然後啟動“Test Explorer”運行測試,詳情請參考這裡。

 

3. Xunit對比Nunit的優點

這部分內容參考了官方文章以及一些自己對測試框架的場景的理解,如有錯誤之處,還請指出。

3.1 每個測試單一實例的討論,SetUp以及TestFixtureSetUp

  請參考馬丁大師對單一實例的論述:http://martinfowler.com/bliki/JunitNewInstance.html,文章指出:對於測試緩存或每次測試之前重新實例化對象,這種做法是值得商榷的。雖然其有利於對象的調用,而且基本不用考慮對象回收的問題(僅當在TearDown中回收了資源),但這樣仍然不符合絕對意義上的“對象隔離”原則。而且有些變量是只需全局實例化一次(在Nunit框架中要使用TestFeature創建),雖然這樣也能滿足需求,但是程序中還是有很多這種框架的特性需要熟悉,相比沒有這些框架(指沒有SetUp和TestFixtureSetUp)的語法來講跟不方便一些,當然這些僅僅是一些思考。

  同時,James Newkrik也在文章中提到,與其在SetUp中初始化更多的參數,破壞單一職責的原則,另外加上每回測試都要回顧SetUp和TearDown方法所執行的內容,倒不如將其放在Test內部,去掉SetUp和TearDown來增強測試的的表達性以及隔離性。

 

3.2 Xunit沒有ExpectException

  不采用Attribute的方式來捕捉異常有兩方面的好處:

  1. 在代碼中直接斷言(Assert)能捕捉到更多種類的異常。

  2. 遵守Arrange-Act-Assert (or "3A") 模式:即測試命名上“范圍-作用-斷言”規范。

public class TestClass1 
    { 
        [ Fact ] 
         public void testException() 
        { 
             Assert .Throws< InvalidOperationException >(() => operation()); 
        } 
         void operation() 
        { 
             throw new InvalidOperationException (); 
        } 
    }

 

3.3 Xunit更像面向切面的語言

  Xunit中使用Fact、Theory、XxxData、Fact(Timeout=n)等標簽來組織測試,從功能上講更像切面編程。 請參考下一節。

 

3.4 Xunit去除了更多的Attribute

  保留很少一部分標簽有利於簡化測試框架,加快熟悉測試框架的時間,使框架更為簡潔、實用。

NUnit 2.2MSTestxUnit.netComments [Test] [TestMethod] [Fact] Marks a test method. [TestFixture] [TestClass] n/a xUnit.net does not require an attribute for a test class; it looks for all test methods in all public (exported) classes in the assembly. [ExpectedException] [ExpectedException] Assert.Throws orRecord.Exception xUnit.net has done away with the ExpectedException attribute in favor of Assert.Throws. SeeNote 1. [SetUp] [TestInitialize] Constructor We believe that use of [SetUp]is generally bad. However, you can implement a parameterless constructor as a direct replacement. See Note 2. [TearDown] [TestCleanup] IDisposable.Dispose We believe that use of[TearDown] is generally bad. However, you can implementIDisposable.Dispose as a direct replacement. See Note 2. [TestFixtureSetUp] [ClassInitialize] IUseFixture<T> To get per-fixture setup, implement IUseFixture<T> on your test class. See Note 3 [TestFixtureTearDown] [ClassCleanup] IUseFixture<T> To get per-fixture teardown, implement IUseFixture<T> on your test class. See Note 3 [Ignore] [Ignore] [Fact(Skip="reason")] Set the Skip parameter on the[Fact] attribute to temporarily skip a test. n/a [Timeout] [Fact(Timeout=n)] Set the Timeout parameter on the [Fact] attribute to cause a test to fail if it takes too long to run. Note that the timeout value for xUnit.net is in milliseconds. [Property] [TestProperty] [Trait] Set arbitrary metadata on a test n/a [DataSource] [Theory], [XxxData] Theory (data-driven test). SeeNote 4

 

3.4 Xunit使用IDisposable和IUseFixture<T>接口來代替顯示聲明SetUp和TestFixtureSetUp

   首先,創建一個支持IDisposable對象:

using System;
using System.Configuration;
using System.Data.SqlClient;

public class DatabaseFixture : IDisposable
{
    SqlConnection connection;
    int fooUserID;

    public DatabaseFixture()
    {
        string connectionString = ConfigurationManager.ConnectionStrings["DatabaseFixture"].ConnectionString;
        connection = new SqlConnection(connectionString);
        connection.Open();

        string sql = @"INSERT INTO Users VALUES ('foo', 'bar'); SELECT SCOPE_IDENTITY();";

        using (SqlCommand cmd = new SqlCommand(sql, connection))
            fooUserID = Convert.ToInt32(cmd.ExecuteScalar());
    }

    public SqlConnection Connection
    {
        get { return connection; }
    }

    public int FooUserID
    {
        get { return fooUserID; }
    }

    public void Dispose()
    {
        string sql = @"DELETE FROM Users WHERE ID = @id;";

        using (SqlCommand cmd = new SqlCommand(sql, connection))
        {
            cmd.Parameters.AddWithValue("@id", fooUserID);
            cmd.ExecuteNonQuery();
        }

        connection.Close();
    }
}

  最後增加測試,並實現IClassFixture<DatabaseFixture>接口:

using System;
using System.Configuration;
using System.Data.SqlClient;
using Xunit;

public class ClassFixtureTests : IClassFixture<DatabaseFixture>
{
    DatabaseFixture database;

    public ClassFixtureTests(DatabaseFixture data)
    {
        database = data;
    }

    [Fact]
    public void ConnectionIsEstablished()
    {
        Assert.NotNull(database.Connection);
    }

    [Fact]
    public void FooUserWasInserted()
    {
        string sql = "SELECT COUNT(*) FROM Users WHERE ID = @id;";

        using (SqlCommand cmd = new SqlCommand(sql, database.Connection))
        {
            cmd.Parameters.AddWithValue("@id", database.FooUserID);

            int rowCount = Convert.ToInt32(cmd.ExecuteScalar());

            Assert.Equal(1, rowCount);
        }
    }
}

   從這裡讀者可能體會到,Xunit更多的利用了C#本身的一些特性,而非使用一些特殊的Attribute或者方法(例如SetUp),在設計哲學上更多的考慮了對象自動實現自我管理的機制,而非人為去管理,從某種意義上來講,解除了部分依賴性,將部分功能交給程序C#本身處理,減少工作量。

 

4. 文章引用

Martin Flower介紹Xunit: http://www.martinfowler.com/bliki/Xunit.html

Xunit Github地址:https://github.com/xunit/xunit

Nunit 官方地址:http://www.nunit.org/

周公介紹Xunit:http://zhoufoxcn.blog.51cto.com/792419/1172320/

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved