神刀安全网

The Transformation Priority Premise

The Transformation Priority Premise

The Transformation Priority Premise is a guideline for approaching Test-driven development in a more analytical way. It is a set of rules that should aid in writing TDD tests.

If you haven’t heard of TPP before but you are familiar with TDD then the basic premise (pun not intended) is the same. You should always make the smallest possible change. However, the order in which you apply the changes matter a lot to the quality and amount of tests that are produced. This is how Uncle Bob explains it:

Refactorings have counterparts called Transformations. Refactorings are simple operations that change the structure of code without changing its behavior. Transformations are simple operations that change the behavior of code. Transformations can be used as the sole means for passing the currently failing test in the red/green/refactor cycle. Transformations have a priority, or a preferred ordering, which if maintained, by the ordering of the tests, will prevent impasses, or long outages in the red/green/refactor cycle.

"Uncle Bob" Martin , "The Transformation Priority Premise" , Uncle Bob’s Blog

The Rules

After trying to write out transformation examples for all of the rules I find a few of them are just very difficult to clearly demonstrate using this program idea (described below). Perhaps this page needs to change ( transform , if you will) to use examples under different circumstances. Please let me know if you’d like to help.

The code snippets are in Python. The original general idea was to imagine you were working on a program to translate a two-digit country code into it’s country name so that there wouldn’t need to be a lot of code context to understand each of the transformations.

It’s important to understand that the examples are not in order and the tests themselves would not all work together. Each step is displayed like this:

# The existing code

# The red (failing) test.
# The new code using that transformation. # It will also make the test go green (pass).

1. {} → nil

Tranform no code into something that returns a nil value.

no code

def test_bad_country_code_returns_none():     assert country_from_code(None) is None
def country_from_code(code):     return None

2. nil → constant

def country_from_code(code):     return None

def test_country_is_a_string():     assert type(country_from_code('AU')) is str
def country_from_code(code):     return ""

3. constant → constant+

A simple constant to a more complex constant.

def country_from_code(code):     return ""

def test_au_is_australia():     assert country_from_code('AU') == 'Australia'
def country_from_code(code):     return "Australia"

4. constant → scalar

Replacing a constant with a variable or an argument.

def country_from_code(code):     return ""

def test_au_is_australia():     assert country_from_code('AU') == 'Australia'
def country_from_code(code):     country = "Australia"      return country

5. statement → statements

Adding more unconditional statements.

This is a poor example because there is no further processing of the country name needed under any specific case so we could not really justify new lines that don’t have any practical use.

def country_from_code(code):     country = "australia"      return country

def test_au_is_australia():     assert country_from_code('AU') == 'Australia'
def country_from_code(code):     country = "australia"     country = country[0].upper() + country[1:]      return country

6. unconditional → if

Splitting the execution path. It is also acceptable to use a ternary instead.

def country_from_code(code):     return "Australia"

def test_es_is_spain():     assert country_from_code('ES') == 'Spain'
def country_from_code(code):     if (code == 'ES'):         return "Spain"      return "Australia"

7. scalar → array

def country_from_code(code):     if (code == 'ES'):         return "Spain"      return "Australia"

def test_dk_is_denmark():     assert country_from_code('DK') == 'Denmark'
def country_from_code(code):     countries = ('AU', 'Australia', 'DK', 'Denmark', 'ES', 'Spain')      return countries[countries.index(code) + 1]

8. array → container

This usually means converting a simple array into a more complex collection like a dictionary or array of objects.

def country_from_code(code):     countries = ('AU', 'Australia', 'DK', 'Denmark', 'ES', 'Spain')      return countries[countries.index(code) + 1]

def test_mx_is_mexico():     assert country_from_code('MX') == 'Mexico'
def country_from_code(code):     countries = {         'AU': 'Australia', 'DK': 'Denmark', 'ES': 'Spain', 'MX': 'Mexico'     }      return countries[code]

9. statement → tail-recursion

In this case I am converting the statement that calculates the index into a recursion that tries the elements one at a time until the correct one is found. In reality this would be a very bad solution but I’m keeping with the theme of these examples.

Tail recursion means that the last instruction (usually the return value) is a recursive call.

def country_from_code(code):     countries = ('AU', 'Australia', 'DK', 'Denmark')      return countries[countries.index(code) + 1]

def test_nz_is_new_zealand():     assert country_from_code('NZ') == 'New Zealand'
def country_from_code(code, index=0):     countries = ('AU', 'Australia', 'DK', 'Denmark', 'NZ', 'New Zealand')      if countries[index] == code:         return countries[index + 1]      return country_from_code(code, index + 2)

10. if → while

Convert a binary branching statement ( if , unless , ternary, etc) into a loop ( while , do , for , etc).

def country_from_code(code):     if (code == 'ES'):         return "Spain"      return "Australia"

def test_th_is_thailand():     assert country_from_code('TH') == 'Thailand'
def country_from_code(code):     countries = (('AU', 'Australia'), ('ES', 'Spain'), ('TH', 'Thailand'))      for country in countries:         if country[0] == code:             return country[1]      return None

11. statement → non-tail-recursion

Non-tail recursion means that the last instruction (usually the return value) is not a recursive call.

def country_from_code(code):     countries = ('AU', 'Australia', 'DK', 'Denmark')      return countries[countries.index(code) + 1]

def test_de_is_germany():     assert country_from_code('DE') == 'Germany'
def country_from_code(code, index=0):     countries = ('AU', 'Australia', 'DK', 'Denmark', 'DE', 'Germany')      if countries[index] != code:         country = country_from_code(code, index + 2)     else:         country = countries[index + 1]      return country

12. expression → function

Replacing an expression with a function or algorithm.

def country_from_code(code):     countries = {         'AU': 'Australia', 'DK': 'Denmark', 'ES': 'Spain'     }      return countries[code]

def test_it_is_italy():     assert country_from_code('IT') == 'Italy'
def get_country_codes():     return {         'AU': 'Australia', 'DK': 'Denmark', 'ES': 'Spain', 'IT': 'Italy'     }  def country_from_code(code):     countries = get_country_codes()      return countries[code]

13. variable → assignment

Replacing the value of a variable.

def country_from_code(code):     if code == 'ES':         return 'Spain'      return 'Australia'

def test_dk_is_denmark():     assert country_from_code('DK') == 'Denmark'
def country_from_code(code):     country = 'Australia'     if code == 'ES':         country = 'Spain'     if code == 'DK':         country = 'Denmark'      return country

14. case

Adding a case (or else ) to an existing switch or if .

def country_from_code(code):     if (code == 'ES'):         return "Spain"      return "Australia"

def test_dk_is_denmark():     assert country_from_code('DK') == 'Denmark'
def country_from_code(code):     if (code == 'ES'):         return "Spain"      if (code == 'DK'):         return "Denmark"      return "Australia"

Further Reading

I would encourage you to read the original article from Uncle Bob .

I mentioned above that this article really needs a lot more thought to come up with better examples for some of the transformations, please contact me if you have any ideas.

If you found this article interesting please consider Subscribing at the top of the page, or leave your comments below. Happy coding.

Elliot Chance –

I’m a data nerd and TDD enthusiast living in Sydney, Australia. I love exploring new technologies and working on modern ways to solve age old problems.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » The Transformation Priority Premise

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址