1 / 45

Test-Driven Development (TDD)

Test-Driven Development (TDD). What is TDD?. changing code without modifying external functional behavior. What is programming?. writing code: addressing requirements/solve a problem verify : code answers requirements / program performs according to specifications

avi
Download Presentation

Test-Driven Development (TDD)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Test-Driven Development (TDD)

  2. What is TDD? changing code without modifying external functional behavior

  3. What is programming? • writing code: addressing requirements/solve a problem • verify: code answers requirements / program performs according to specifications • requirement/specification/verification: • complex problem is broken in small problems • can be solved by writing short pieces of code • specify (define) and verify small pieces of code

  4. What is Development? • as we develop more, we understand better… • developer asks better questions, and gets better answers • requirements of program change over time • existing code must be changed... • difficult to predict how other parts of the code are affected

  5. What is Testing? • correctness – code performs by specification • 3 types of tests: • Unit tests: a particular module is working properly: • Implemented by the programmer. • Simple test cases for all functions and methods. A test case is a program… • Integration tests: a combination of modules work well together and exchange messages according to protocols • Acceptance tests: a whole system under test to check that the expected functionality meets the requirements • functional and performance criteria.

  6. Unit Test Terminology • Test case: tests a single scenario of usage. • one aspect of the protocol of the object: its invariant, a single pre-condition / post-condition. • Test suite: collection of test cases that fully verify the public protocol published by an object • Object under test (OUT): the object being tested by a test suite • test suite should focus on testing a single object - assuming all other objects perform correctly

  7. Unit Test Terminology • Test coverage: the part of the code of the object that is executed by running a test suite • every code line of OUT should be executed when running test suite • Test fixture: other objects that will interact with the OUT • A test case must be self-contained: create, initialize and set all the required test fixtures before the test scenario can be executed. Test fixtures must be cleaned up after the test case is run. • Mockup: a basic, test-specific, implementation for classes on which OUT depends. • OUT is independent of other objects - avoid using existing objects • mockup and the "real" object will usually satisfy the same interface • Usually: mockup for data-retrieval, not for logical behavior

  8. Unit Test Terminology • Positive test case: verifies that a public operation of the OUT performs as expected. • Negative test case: verifies that a public operation of the OUT fails as expected • a call to a method when a pre-condition does not hold properly throws an exception • Repeatable and deterministic tests: runs the same test twice in a row - same result. • test case cannot depend on external data or on the timing of RTE scheduler

  9. Pseudocode example set_hour (a_hour: INTEGER) -- Set `hour' to `a_hour' require valid_argument: 0 <= a_houranda_hour <= 23 do hour := a_hour ensure hour_set: hour = a_hour end precondition postcondition

  10. TDD = TFD+refactor

  11. What is TDD? • Test first design - repeatedly first writing a test case and then implementing the code necessary to pass the test. • It is the responsibility of the programmer to define the unit tests as part of the code delivery • requirements granularity level of the module • module depends on other modules

  12. What is TDD? • requirements on the code by writing a test • test case IS the expression of the requirement • implement code to stand by the requirements • code pass tests

  13. Test First Design cycle • Define objects: responsible for specific functionality and interaction • Write tests for each OUT: • Define the interface of the OUT • Define  contract for each method of the OUT interface • Specify pre-conditions, post-conditions for each method and invariant for the OUT. • Write test cases for each invariant, pre and post-condition of each method in the interface. • Write the code so it will pass the test • Run tests • Refactor! improve code designby removing code that "looks bad“ ("code smells“) • Repeat process: code changes, rules change, test change • When needed - Break the contract, Redefine tests, Refactor code

  14. Design By Contract (DBC) Concept that helps programmers express the requirements on an object, correctness criteria: • preconditions : things that must be true before we invoke a method • postconditions : things that must be true after a method is invoked • invariants: things that must be true both before and after a method is invoked

  15. TDD benefits • validation of correctness: errors caused by code/design modifications are caught quickly by programmer, immediately after the code is changed. • courage to make code modifications and refactoring, a change will "break" the program. • integration tests - test cases can help design intelligent integration tests. • design improvement- writing tests before the OUT is implemented ensures OUT is indeed usable, that it is easy to prepare the context in which the OUT can be invoked. • E.g.: OUT depends on too many other objects or global objects, - writing tests becomes very difficult - test cases reveal this - strong incentive to improve the design of the OUT • documentation - test classes provide examples of code usage

  16. JUnit Junit: a simple framework to write tests (in Eclipse) • public default ctor • setUp() - prepare pre-conditions (@Before) • tearDown() - "clean up" the test object after a test has run (@After), "undoes" what the @Before method did. • test method for each test case (@Test) • JUnit run: • instance of the test class is constructed. • run setUp() • run test case • display test results (pass/fail) • run tearDown()

  17. Example: Stack data object

  18. TDD • Requirement: write a simple Stack data object, what is a "Stack"? • informal description: • Stack is a container of Objects. • Objects are ordered in Last-In-First-Out order. • Add/Remove an Object to Stack

  19. Interface Turn Description into interface (formalize ) Javadoc: inline tag {@link URL}

  20. Generics • it is a "code smell", to let a container receive any object (too general)… • Object = haven't thought enough about specific objects

  21. fill interface – what means to use a Stack? • DBC: methods, preconditions, postconditions, invariants

  22. test class • test-class skeleton for the interface

  23. define tests • think of complicated scenarios/ usage: parameters, exceptions, return values • think of complicated behavior/sequences: • push-pop-pop, pop-Exception • isEmpty returns true or false, • push objects of different types <T> • push a null - What should we do? • Change Stack API, - add Exception to push() • do not change interface - specify in Javadoc expected behavior

  24. Implement tests • TFD: think of tests BEFORE implementing • define Stack<Integer> • assume "this.stack" is already instantiated • add @Before method to create the stack

  25. first tests

  26. more complex tests

  27. implement Stack interface • write minimum! code to pass tests • if no test fails, we don't need to write code • passing code is valid Stack implementation. • additional classes require their test cases • Tip: override (and test) toString method, for all classes

  28. Refactoring tests • test (positive) for push() • pop() to test push() – circular • pop() changes the state of the stack (removes top item)

  29. Refactoring tests • Weak pop() test: - push an item and pop without exception • Better test: • returns last element pushed on the stack; • stack has one element less • … a copy of testPush() - one test for two functions • …complex post-conditions - rethink! - analyze contract of pop() • pop() does 2 things: • removes an item from the stack • returns the value of this item. • design (if possible) methods to do one thing - reusable, testable.

  30. Refactoring tests:6 principles for writing testable interfaces 1. Separate commands and queries: • Queriesreturn a value and do not change the visible state of the object • methods with side-effect on the object - push() • Commands change the internal state of the object and do not return values • functions that only get information - isEmpty()

  31. Refactor pop() • pop() does not stand by this rule: it is neither a command nor a query • replace pop() with primitive methods: • top() returns the value of the top object • remove() removes top object from the stack • keep pop(): • T pop() { T top = top(); remove(); return top; }

  32. 2. Separate Basic Queries from Derived Queries • Is this test strong enough? What could go wrong? • Change hats: assume person writing the code tries to pass the test with minimal effort • push=replace • testStackLIFO – 3 item stack

  33. count() – add new query that indicates how many elements are stored in Stack • use to write post-conditionof push(), remove() • @pre(count()) - refer to value of count() before command is executed • isEmpty(): count()==0 • count() - primitive query • isEmpty() - derived query • post-condition computed based on primitive query

  34. 2. Separate basic and derived queries • Derived queries can be specified in terms of basic queries. 3. Define derived queries in terms of basic queries • Define the post-conditions of derived queries in terms of basic queries only

  35. 4. For each basic command, write post-conditions that specify values of basic queries • a command modifies the state of OUT • test modification, with basic queries: • review post-conditions of command by available basic queries of object • if no basic queries is affected – extend/revise interface • isEmpty() is a derived query (count()) – can be removed

  36. Refactor remove() • contract encoded in the pre, post-conditions • remove() removes one element from stack • cannot call when the stack is empty. • WHICH element is removed (top)? • more specific contract • basic observe query

  37. Did this make our post-condition for remove() stronger? • count() has been decreased by one • Do we need to check that the last element is the one that is removed? • itemAt for all values of i=1 to new count() are not affected • Did itemAt() break encapsulation? • stack limit access to only the top element

  38. 5. For each basic command and query, express pre-conditions in terms of basic queries • reminder: basic queries are sufficient to capture post-conditions of basic commands

  39. 6. Specify class invariants that impose “always true” constraints on basic queries • class invariants: properties that remain true in all legal states: @inv count() >= 0 • verify contract of commands that affect count() cannot break this invariant. • remove() : verify that the pre-condition prevents count() from changing from 0 to -1

  40. Summary • specify the interface of objects before implement • interface specification includes contract for methods, expressed in terms of @pre, @post and @inv conditions. • write tests for interface to verify contract is enforced before implementation • design objects interface to be testable: • Separate commands and queries. • Separate basic queries and derived queries. • Define derived queries in terms of basic queries. • For each basic command, write post-conditions that specify values of basic queries • For each basic command and query, express the pre-conditions in terms of basic queries. • Specify class invariants that impose “always true” constraints on basic queries

More Related