📌 置頂: 請把任何比你弱勢的用路人當作你的至親對待。跟前車保持安全車距 (2秒以上)。

測所翻譯 – Testing on the Toilet: What Makes a Good Test?

In

,

Tags:



by

原文網址:https://testing.googleblog.com/2014/03/testing-on-toilet-what-makes-good-test.html
by Erik Kuefler

對於程式的正確性驗證來說,單元測試 (Unit tests) 是一個非常重要的工具。不過,撰寫好的測試遠比只驗證程式正確性還要重要 ─ 好的單元測試要有某些特徵來讓其易於閱讀與維護。

好測試的其中一個特徵是清晰 (Clarity)。

清晰的意思是指一個測試程式,應該被當作是一個易於人類閱讀的文件,描述其正在測試的 API 功能。

測試本身不應該直接介入實作的細節。一個類別的測試名稱,應該要能夠說明這個類別的用途;而他的測試應該被當作是如何使用這個類別的範例。

另外兩個重要的特徵是完整性 (completeness) 與精簡性 (conciseness)

一個測試在包含所有讓你了解其用途的資訊時為完整,以及沒有包含任何會讓你分心的資訊時為最精簡。

下面這個測試範例在並沒有剛剛提到的兩個重要特徵:

許多會分散注意力的資訊被放入建構子中,且整個測試中最重要的部份被隱藏在一個 helper method 內。
我們可以透過變更 helper function 的方式使其目的更為清晰,讓整個測試更完整,而透過另一個 helper function 來隱藏不相關的建構子資訊使這個測試更精減:

最後一個好測試的重要特徵是有彈性。當一個測試完成,有彈性的測試不必再做任何更動 ─ 除非被測試的類別之目的(purpose) 或行為 (behavior) 有所變更。增加新的行為時應該只需要增加新的測試,而不是更動舊有的。前面提到的測試是沒有彈性的,因為當你新增一個不相關的建構子參數時,你需要去更新他 (大概還有一堆其他的測試也要更新!) 。把這些細節移到另一個 helper method 可以解決這個問題。

 


6 comments:

  1. I agree with your recommending clarity of code.

    However example suggests a problem elsewhere (the constructor of the Calculator class). Your proposal will necessitate helpers like newCalculatorWithCosine() newCalculatorWithoutCosine() ad infitum for every permutation of the constructor parameters.

    In fact should those not be helpers (factories) in the Calculator itself? But we digress from your main point….

    Reply

    我同意你對於程式清晰性的建議。

    不過範例裏面建議的建議引發了一個問題 ( Calculator 類別的建構子). 你提出的作法會讓我們需要做出一堆必要的 heplers 像是 newCalculatorWithCosine()、newCalculatorWithoutCosine() 、等等,我們可以繼續枚舉每個參數來建立新的建構子。

    略…

  2. I’ve seen one method that looked ok, which was to have a CalculatorBuilder class, that then has fluent style methods that you can chain like .WithCosine() and then a final .Build() method that constructs your instance. Not actually got around to using it in practise yet unfortunately… need to write more tests 🙂

    Reply

    我看過一個還可以的方法,就是我們有個 CalculatorBuilder 類別,然後有個 chain 風格的作法,像是 CalculatorBuilder.WithConsine() 接著其他的參數最後 .Build() 來建立一個 instance。
    不過實際上沒有用這個方法…需要寫更多的測試 🙂

  3. It’s a good point that you should definitely be paying attention to the clarity of your constructors – if there are a lot of places in your code base where you have to invoke a clunky six-argument constructor, something is probably wrong! The builder pattern that Sam mentions (http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html) is a very good way to make instances easier to construct, and will clean up your tests too.

    However, there are a lot of situations where a many-argument constructor is only really called once during your application’s setup. This is especially common if you’re doing dependency injection, manually or via a framework like Guice. In these situations there often isn’t much point in creating builders for your production code since you don’t ever reuse them, but it can definitely still be useful to define builders for tests. Builders used in this way are essentially a generalization of helper methods that allow you to specify an arbitrary number of named parameters.

    Reply

  4. There is often a trade off between readability and maintainability. Modularizing your test cases reduces it’s readability while improves maintainability. But I agree that the test cases can be served as documentation of the system too.

    Reply

  5. I am going to suggest something for the hidden constructor variants here, and call it table driven testing. It looks like this test-code uncovered 2 test-cases, one for ENABLE_COSIN_FEATURE and another for (I assume) DISABLE_COSIN_FEATURE, where either the result or the inputs can be partitioned as the tester sees fit into a table of csv data:
    ENABLE_COSIN_FEATURE, 2,3,5
    DISABLE_COSIN_FEATURE,2,3,5
    I have a co-worker who does this kind of breakdown in their test-code regularly, it removes hard-coded stuff from the test-code, and results in hundreds of tiny custom csv files, but it rocks – only because its easy to test new corner cases without making a code-change. The downside is longer time to write the test, and ability to get carried away with the possibilities.

    Reply

  6. We put lot more effort in breaking dependencies but on the other hand we introduce constructor dependencies and the above example constructor dependencies is horrible . Here I see more of coding style than TDDing itself. If one does not follow the good design patterns and practises and write would end up in writing test what has been shown by Erik.

    Reply


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.