TDD和Unit Tests, Acceptance Tests的比較


TDD Tests are not Unit Tests
http://stephenwalther.com/blog/archive/2009/04/11/tdd-tests-are-not-unit-tests.aspx

April 11, 2009
Posted by Stephen Walther
Published in Stephen Walther on ASP .NET MVC


很多人對於TDD測試, unit test和acceptance tests, 常常搞不清楚, 把他們視為是相同的東西, 作者在這篇文章中, 花了不少精力來分析比較它們. 希望能對各位讀者有幫助.


TDD Tests are not Unit Tests

首先我們先來看什麼是Unit Test. Unit Test的目的, 是在isolation的狀況下, 單獨確認一段程式是否沒問題. 這裡有一段程式, 說明如何用unit test, 來驗證Math Class中Add method的正確性

Listing 1 – Math.cs
public class Math  
{  
    public int Add(int val1, int val2)  
    {  
        return val1 * val2;  
    }  

Listing 2 – MathTests.cs
[TestClass]
public class MathTests
{

    [TestMethod]
    public void TestAdd()
    {
        // Arrange
        var math = new Math();
       
        // Act
        var result = math.Add(2, 3);

        // Assert
        Assert.AreEqual(5, result);
    }
}

可是你也知道, 在現實生活中應該不會有像這樣單純的事情發生. 通常來說, 一個class會和其他class有些關聯. 以下面例子來說, Validation class便用到data access class和logging class. 在這種狀況下, 如果你要對Validation class進行unit test, 你便要寫些fake or stub class來模擬那些被用到的data access class和logging class.

Listing 3 – Validation.cs
public class Validation
{
    private ForumsRepository _repository = new ForumsRepository();
    private Logger _logger = new Logger();


    public void CreateForumPost(ForumPost post)
    {
        // Validate forum post
        if (String.IsNullOrEmpty(post.Subject))
            throw new Exception("Subject is required!");

        // Store forum post in database
        _repository.CreateForumPost(post);

        // Log it
        _logger.Log("Add new forum message");
    }

}

根據SRP 原則, 在設計良好的系統中, 每個class只負責一個任務. 所以Validation class只包含vlidation logic, data access class只包含 data access logic等等. 當你在做unit test, 你便是要確認是否他們已經滿足自己單一所負責的任務. 因此你在測試Validation class時, 便不用去測試data access class的功能, 你只管Validation class是否照當初所要行為那樣運作.

因此unit test的目的, 是讓工程師有信心, 程式會照當初所預期的那樣運作. 這對regression testing來說是有很大的價值, 因為你一旦有改程式, unit test便可以幫你檢查是否有違背當初想要的功能.

那TDD和unit test 不一樣在哪裡呢? 和unit test不一樣的地方是, TDD是用來觸發系統的設計工作. 也就是在程式被撰寫之前, TDD要你先想清楚系統要如何使用, 如何運作.

TDD和傳統waterfall 開發不一樣, 並不是要求你一次就先做完design, 而是逐漸設計來建立整個系統(每次先設計一點, 再寫一點code看是否滿足, 藉著再下一次設計). 也就是Kent Back 所說的incremental design 和 Martin Fowler 所說的 evolutionary design

當你在實踐TDD時, 你的開發步驟便是像這樣的動作:
1. Create a failing test (Red)
2. Write just enough code to pass the test (Green)
3. Refactor your code to improve its design (Refactor)

也就是先建立failing test, 用它來描述你希望你的程式碼要如何運作, 但是這時候並不用決定你程式要如何設計. 當你真正開始撰寫程式, 要去pass這個測試, 這時候才需要決定要如何設計.

此外另外和unit test不同的一點, TDD 的測試範圍不只限於一個method 或是class. 因為TDD著重的是behavior是否是你預期要的, 而不是一個method的responsibility, 所以TDD可能會橫跨好幾個classes. Fowler 在他的文章中, 也提到了相同的觀點:
Unit testing in XP is often unlike classical unit testing, because in XP you're usually not testing each unit in isolation.
Fowler
http://www.artima.com/intv/testdriven4.html


TDD Tests are not Acceptance Tests

Accpetance Test是在檢查系統的運作行為, 是否如客戶所預期的. 所以通常需要和客戶合作, 來決定有些需求或是行為, 是客戶想要的. 所以這裡你便可知道Acceptance Test的對象是end-user, 而TDD的對象是developer

此外acceptance test也不像unit test一樣, 大多是使用xUnit系列的testing framework, 而是使用acceptance testing framework: Selenium, Fit, FitNesse or Watir/WatiN.
 
另外一個不同的地方, 在做accpetance test時, 每個東西都是準備好的, 都是真的, 不會使用stub或是fake來代替.

在執行速度上, TDD要求在短時間內要執行完畢. 因為一旦你的程式有修改時, 便需要執行TDD來確保原先功能仍然正確. Kent Beck曾經提出所有TDD測試, 需要在 10 min內執行完畢的概念. 但是在accpetance test上, 這並不是重點. 並且當你使用真的DB 或是web server時, 也不太可能在 10 min跑完.


What is a TDD Test?

最好了解TDD做測試的目的, 就是先了解 Test-Driven Development的目的在做什麼. 根據Martin Fowler的說法:
TDD's origins were a desire to get strong automatic regression testing that supported evolutionary design.
http://martinfowler.com/articles/mocksArentStubs.html#DrivingTdd

首先, TDD 做測試的目的, 就像和unit test一樣, 是要支援regression test. 當你修改系統時,你要使用TDD Test很快去確認是否原先的功能仍然正確. 有了regression test, 你才可以安心去做incermenetal design和refactoring, 因為它提供了一個好的安全保護網.

其次, TDD 做測試的目的, 也和accpetance test一樣, 用來驅動設計的進行. TDD的測試可視為迷你版的acceptance test, 讓你思考這項工作完成的criteria是什麼.

以下有些參考文獻, 可以讓你了解Test-Driven Development是什麼, 以及如何做:
1. Included in Robert and Micah Martin’s excellent book Agile Principles, Patterns, and Practices in C# and partially reproduced here
http://www.objectmentor.com/resources/articles/xpepisode.htm.

2. Included in Kent Beck’s excellent book Test-Driven Development by Example.

3. Included in James Newkirk’s blog.
http://blogs.msdn.com/jamesnewkirk/archive/2004/11/08/254253.aspx

一般在實踐Test Driven Development時, 開始時要先出一堆user stories, 並且以non-tech的方式, 描述系統會如何運行, 其行為為何. 這時候是需要和顧客一起合作, 一起來找出真正的需求為何.

In Kent Beck’s walkthrough, he keeps a list of tasks that looks very much like a task list. Implementing one task inspires him to add additional tasks to the list. When he creates a test that corresponds to the task, and implements the code necessary to pass the test, he crosses the task off his list.

這裡, 我們來看個例子, 有關於forum的web applcation, 它相關的user stories如下:
· A user should be able to create a new forum post. A forum post includes an author, subject, and body.
· A user should be able to view all of the forum posts on the home page. The latest posts should appear higher than earlier posts.
· A user should be able to reply to an existing post.
· A user should be able to select a particular post and see the post and all of the replies.

在你發展的過程中, 你會因越來越清楚, 或是顧客給你額外的feedback, 而逐漸增加user stories. 但是一開始, 沒有完整也無所謂, 至少有一個開始的地方.

接著你挑選一個user story去實作. Your TDD tests should flow out of the user story. 這裡我們挑的user story是
· A user should be able to create a new forum post. A forum post includes an author, subject, and body.

然後你必須要作TDD測試, 所以你會寫出像下面 Listing 4中的程式:

Listing 4 – ForumControllerTests.cs
[TestMethod]  
public void ForumPostValid()  
{  
    // Arrange  
    var controller = new ForumController();  
    var postToCreate = new ForumPost();  
 
    // Act  
    controller.Create(postToCreate);  

所以這個測試是要驗證Create這個method. 接著你要開始撰寫程式, 好讓程式能夠pass測試. 所以你要有ForumController and ForumPost class.


然後你想到, 必須要檢查subject是否有填寫, 因此你接著寫下一個test method, 就像Listing 5的內容:

Listing 5 – ForumControllerTests.cs
[TestMethod]
public void ForumPostSubjectIsRequired()
{
    // Arrange
    var controller = new ForumController();
    var postToCreate = new ForumPost { Subject = string.Empty };

    // Act
    var result = (ViewResult)controller.Create(postToCreate);

    // Assert
    var subjectError = result.ViewData.ModelState["Subject"].Errors[0];
    Assert.AreEqual("Subject is required!", subjectError.ErrorMessage);
}

到這裡, 你應該可以知道, TDD 的測試是根據user stories來驅動的. TDD的測試就是mini版的acceptance test. 你使用TDD測試來驅使開發工作的進行


結論

TDD測試, 和unit test, accpetance test很像, 但是並不完全相同. 以下是比較表
像Unit test: can be used for regression testing
不像Unit test: does not necessarily test one unit of code in isolation
像Acceptance Test: used to drive the creation of an application
不像Acceptance Test: not an end-to-end test. A TDD test does not interact with a live database or web server.

arrow
arrow
    全站熱搜

    kojenchieh 發表在 痞客邦 留言(4) 人氣()