June 14, 2014

使用Fluent Assertions提昇測試程式可讀性

目前團隊所使用的unit testing framework為NUnit。在NUnit裡如果要做assertion的話可以這樣寫
[Test]
public void Can_Add_Integer()
{
    int total = Calculator.Add(1, 2);
    Assert.AreEqual(3, total);
}

上面的Assertion閱讀起來不難懂,可以理解是在測試total與3是否相等。然而對開發人員在撰寫程式碼時卻有個小問題,Assert.AreEqual(3, total)Assert.AreEqual(total, 3)意義相同嗎?看一下NUnit提供的說明可以知道第一個參數要代入的是預期該得到的數值,而第二個參數是實際運算完後的數值。


當assertion永遠成功時,Assert.AreEqual(3, total)Assert.AreEqual(total, 3)看起來沒什麼差別。但在assertion失敗時,意義上就不一樣了,因為NUnit提供的失敗訊息會有很大差別。如

Assert.AreEqual(3, total)


Assert.AreEqual(total, 3)


以上是NUnit提供的Classic Assert Model。事實上NUnit還有提供另一種assertion方式稱為Constraint Model,如
[Test]
public void Can_Add_Integer_Using_Constraints()
{
    int total = Calculator.Add(1, 2);
    Assert.That(total, Is.EqualTo(3));
}

用以上方式撰寫assertion程式碼在閱讀上易讀許多。

雖然Constraint Model已提供可讀性不錯的撰寫方式,但每次要使用時還是得先Assert.That,而且有時NUnit提供的除錯訊息不是那麼清楚。基於以上因素,便出現Fluent Assertions這套函式庫來強化NUnit(或其它framework)在assertion上的不足。

將上述的assertion改以Fluent Assertions重新撰寫可以得到更簡潔的assertion
[Test]
public void Can_Add_Integer_Using_FluentAssertions()
{
    int total = Calculator.Add(1, 2);
    total.Should().Be(3);
}

寫成一行閱讀起來也很清楚,就如同說話一般
[Test]
public void Can_Add_Integer_Using_FluentAssertions()
{
    Calculator.Add(1, 2).Should().Be(3);
}
有時我們需要驗證功能是否有正確拋出例外,也可以Fluent Assertions來撰寫如
[Test]
public void Cannot_Subtract_And_Throw_NotSupportedException()
{
    Action action = () => Calculator.Subtract(5, 3);
    action.ShouldThrow<NotSupportedException>().WithMessage("Not yet implemented");
}

Fluent Assertions提供的assertion很豐富也簡潔易懂,目前我所在團隊已將原本NUnit的assertion全部換成使用Fluent Assertions,效果還不錯,在撰寫assertion程式碼時速度快了許多也沒遇到太大的問題。

No comments: