Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Test-Driven Development

Test-Driven Development

A high-level overview of test-driven development and behavior-driven development.


Jimmy Cuadra

March 13, 2014


  1. Test-Driven Development Test-drive your system. No, really.

  2. What is TDD? • A thing that many people give

    lip service to but don’t actually do. • Test-driven development (TDD) is a methodology for writing software where tests are written before implementation code. • In the traditional style of development, you write code, then write tests to verify that it works as expected. • With TDD, you write tests for code that does not exist, then write just enough code to make the tests pass.
  3. – Kent Beck, creator of Extreme Programming, TDD, and Agile

    “Never write a singe line of code unless you have a failing automated test.”
  4. Benefits of testing • Tests verify correctness. • Tests alert

    you of regressions.
  5. Additional benefits of TDD • Helps minimize the amount of

    code necessary to satisfy requirements. • Helps ensure good test coverage. • Helps find bugs as they’re introduced. • Provides feedback on code complexity. • Prevents you from writing code that is difficult to test. • Helps you write code that is well factored, correct, and resistant to change. • Overall: drives the design of your software.
  6. The flip side: dangers of not test-driving code • Code

    is likely to be more verbose. • Code is much more likely to be untested, which is extremely dangerous. • Bugs are more likely. • Objects and methods are much more likely to have high cyclomatic complexity. • It’s usually much more difficult to backfill tests. • Code is harder to change with confidence. • Overall: creates a system that is more difficult to change and more likely to encounter bugs and regressions.
  7. Example: forcing generalization through TDD # Write a function. def

    greet(name): return "Hello, %s!" % name ! # Write a test to verify correctness. This test will pass. def test_greet(): assert greet("StarCraft") == "Hello, StarCraft!" ! # But what if the function was written like this? # The test would still pass, even though the behavior is # not the same! def greet(name): return "Hello, StarCraft!"
  8. And using TDD… # With TDD, you start with a

    test. def test_greet(): assert greet("StarCraft") == "Hello, StarCraft!" ! # Run the test and it fails, because the greet method # doesn't exist. Let's define it. def greet(): pass ! # Run the test and it fails, because greet expects # one argument. Let's add an argument. def greet(name): pass ! # Run the test and it fails, because greet does not # return the expected string. Let's make it return # exactly what is expected. This is the simplest # possible code to make the test pass. (Sometimes # called a “slime.”) def greet(name): return "Hello, StarCraft!"
  9. Force the production code to generalize with an additional test

    case # The original test. def test_greet(): assert greet("StarCraft") == "Hello, StarCraft!" ! # A test checking for a different name. def test_greet_different_name(): assert greet("Charles") == "Hello, Charles!" ! # The original test continues to pass, but the new test # fails, because StarCraft is greeted, not Charles. This # forces us to generalize our production code. def greet(name): return "Hello, %s!" % name ! # Run the tests and they pass. If someone changes the # production code back to the static string, they'll know # they broke something right away because the second # test will fail!
  10. • A more experienced programmer might have tested the general

    case without TDD. • However: TDD is thorough and doesn’t make assumptions. • TDD makes it more likely to notice issues you would not have otherwise. • TDD helps you code in small, iterative steps, which improve your own understanding of the system. It’s easier to see the forest for the trees, and vice versa.
  11. Behavior-driven development • Behavior-driven development (BDD) is a refinement built

    on top of the ideas of TDD and XP/Agile. • BDD focuses on the behavior of the units under test, rather than the details of their implementation. • BDD helps align software with the requirements supplied by stakeholders by focusing on business value and using a common language. • BDD emphasizes testing the system as the consumer will use it.
  12. “Outside-in” • To encourage that software is correct according to

    its business requirements, BDD uses an approach to testing known as “outside-in.” • Testing begins at the highest level. When a high level test fails, you dive in deeper and begin testing the individual component that caused the failure.
  13. Aside: types of tests • System tests: Very high level

    tests intended to verify the behavior of the entire system as a whole. • Acceptance tests: High level tests that make assertions about specific business requirements. e.g. “As a user, I should be able to view my profile.” • Integration tests: Tests that verify the boundaries between units. Where unit tests should use test doubles to stub their dependencies, integration tests verify that those assumptions are correct. • Unit tests: Very granular tests that verify the behavior of the smallest pieces of the system, isolated from their dependencies. In object oriented software, units are usually objects.
  14. Outside-in, continued • In the same way we used tests

    to force a generalized implementation of a function, outside-in allows tests to force the creation of objects solely based on need. • In effect, testing drives the design of the system. • Example: A user signing up: The acceptance test forces you to create a user sign up page, which forces a view, which needs to create a user record, which forces a model.
  15. Red, green, refactor • “Red, green, refactor” is a common

    TDD mantra used to describe the basic workflow. • Red: Write a test, run it, watch it fail, and note why it failed. • Green: Add just enough code to correct the specific failure. Run the test again and address any new failures. Continue doing this until the test passes. • Refactor: Now that the test is passing, do any internal clean up necessary, e.g. extracting duplication into additional methods. Keep running the tests after each change to ensure your changes haven’t caused a regression.
  16. The BDD cycle Outside-in + red, green, refactor

  17. Write the code you wish you had • This simple

    statement can have a very large impact on the way you write code. • When you write a test for code that doesn’t yet exist, you have the opportunity to design the ideal interface for your system. • Write tests for code imagining the ideal API. TDD will ensure that your ideal is the actual result.
  18. – Me “Test-drive your shit.”