Test-Driven Development (TDD) - BGUasharf/SPL/TDD.pdf · SPL/2010 What is TDD? 3 changing code...

45
SPL/2010 SPL/2010 Test-Driven Development (TDD) 1

Transcript of Test-Driven Development (TDD) - BGUasharf/SPL/TDD.pdf · SPL/2010 What is TDD? 3 changing code...

SPL/2010SPL/2010

Test-Driven Development (TDD)

1

SPL/2010SPL/2010 2

SPL/2010SPL/2010

What is TDD?

3

changing code without modifying external functional behavior

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

Pseudocode example

set_hour (a_hour: INTEGER)

-- Set `hour' to `a_hour'

require

valid_argument: 0 <= a_hour and a_hour <= 23

do

hour := a_hour

ensure

hour_set: hour = a_hour

end

10

precondition

postcondition

SPL/2010SPL/2010

TDD = TFD+refactor

11

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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 design by 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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

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

SPL/2010SPL/2010

Example: Stack data object

18

SPL/2010SPL/2010

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

SPL/2010SPL/2010

Interface

Turn Description into interface (formalize )

20

Javadoc: inline tag {@link URL}

SPL/2010SPL/2010

Generics

● it is a "code smell", to let a container receive any object (too general)…

● Object = haven't thought enough about specific objects

21

SPL/2010SPL/2010

fill interface – what means to use a Stack?

● DBC: methods, preconditions, postconditions, invariants

22

SPL/2010SPL/2010

test class

● test-class skeleton for the interface

23

SPL/2010SPL/2010

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 Javadocexpected behavior

24

SPL/2010SPL/2010

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

SPL/2010SPL/2010

first tests

26

SPL/2010SPL/2010 27

SPL/2010SPL/2010

more complex tests

28

SPL/2010SPL/2010 29

SPL/2010SPL/2010

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

30

SPL/2010SPL/2010

Refactoring tests

● test (positive) for push()

● pop() to test push() – circular

● pop() changes the state of the stack (removes top item)

31

SPL/2010SPL/2010

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.

32

SPL/2010SPL/2010

Refactoring tests:6 principles for writing testable interfaces

1. Separate commands and queries:

● Queries return 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()

33

SPL/2010SPL/2010

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; }

34

SPL/2010SPL/2010 35

SPL/2010SPL/2010

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

36

SPL/2010SPL/2010

● count() – add new query that indicates how many elements are stored in Stack

use to write post-condition

of 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

37

SPL/2010SPL/2010

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

38

SPL/2010SPL/2010

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

39

SPL/2010SPL/2010

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

40

SPL/2010SPL/2010 41

SPL/2010SPL/2010

● 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

42

SPL/2010SPL/2010

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

43

SPL/2010SPL/2010

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

44

SPL/2010SPL/2010

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

45