1 / 54

Test Driven development

Test Driven development. Tor Stålhane. What we will cover. We will cover three aspects of testing Testing for green-field projects. This is TDD as it was originally understood and used. Testing for changing legacy code TDD and acceptance testing . Development and testing .

liz
Download Presentation

Test Driven development

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 Tor Stålhane

  2. What we will cover We will cover three aspects of testing • Testing for green-field projects. This is TDD as it was originally understood and used. • Testing for changing legacy code • TDD and acceptance testing

  3. Development and testing From a TDD point of view, it is unreasonable to separate testing and implementation. When we decide on a implementation strategy, we have also indirectly chosen a test strategy. The reason for this is that we have to build tests for what we later will implement.

  4. Why TDD Important advantages of TDD are that it encourages: • Writing clear requirements • Development in small steps. This will make debugging easier since we will have small code chunks to debug. • Minimalistic code and enforce the YAGNI principle – “You Ain’t Gonna Need It”

  5. Green field projects

  6. What is a green field project? A green field project is a project where we start out with a clean slate. We are thus free to select such things as programming language, architecture, development method and detailed design. In reality, however, we are often bound by such things as company policy, available tools and methods and our knowledge and experience.

  7. Where to start TDD operates with four pairs of strategies which encompass testing and code. We will look at each pair in some detail. • Details vs. the “big picture” • Uncertain territory vs. the familiar • Highest values vs. the low-hanging fruits • Happy paths vs. error situations.

  8. Details vs. the “big picture” • Start with the details:Solve all the small problems before we combine them into a component • Start with the “big picture”:Solve the design problems – e.g. component structure and then include all the details In most cases, we will start with writing tests and then code for our greatest concerns.

  9. Uncertain territory vs. the familiar – 1 The question is about priorities: • Exploring uncertain territory – reduce risk. • The familiar – what is best understood – will in many cases bring larger user benefits faster. We an thus look at this as a cost/benefit problem – reduced risk vs. immediate benefits. We will write test and then code in a way that give us the best cost/benefit ratio

  10. Uncertain territory vs. the familiar – 2 The main principle is to postpone important decisions as long as possible – but not longer. In the mean time we should collect info, so as to have as much knowledge as possible when the decisions have to be made.

  11. Uncertain territory vs. the familiar – 3 p2 Earn N Spend X Wait Lose M 1 - p2 p1 Earn N Act now Lose M 1 – p1

  12. Highest value vs. “low-hanging fruits” This is a variation of the previous topic – familiar vs. unknown – and is thus also a cost/benefit question. Going for the “low-hanging fruits”, we can demonstrate a lot of functionality early in the project without spending too many resources. Since it is easier to write both tests and code for the “low-hanging fruits”, this is the popular alternative

  13. Happy paths vs. error situations. The choice between a happy path and an error situation is mostly an easy choice. Even if the robustness requirements are extreme, we will first need tests and code that do something useful for the user. There are exceptions – situations where we will need the error handling quite early. A typical example is a log-on function where we need error handling like “unknown user” and wrong password right from the start.

  14. Essential TDD concepts We will walk through the following concepts: • Fixtures • Test doubles • Guidelines for a testable design • Unit test patterns • Legacy code

  15. Fixtures A fixture is a set of objects that we have instantiated for our tests to use. This, a fixture is a piece of code that we want to test.

  16. Test Doubles - 1 A test double is an object “stand-in”. Typical features are that it: • Looks like the real thing from the outside • Execute faster • Is easier to develop and maintain Test doubles are used for two types of testing: • State based testing • Interaction based testing

  17. Test Doubles - 2 We can sum up state based and interaction based testing in that we use • Interaction based testing to verify how an object talks to its collaborators. Am I using the objects around me correctly? • State based testing to test how well the object listens. Am I responding correctly to the input and responses that I get from others?

  18. Test Doubles – 3 We have three types of test doubles: • Stubs: the simplest possible implementation of an interface • Fakes: more sophisticated than a “stub” – e.g. an alternative, simpler implementation • Mocks: more sophisticated than a “fake”. It can contain • Assertions • The ability to return hard coded values • A fake implementation of the logic

  19. Why use test doubles – 1 Important reasons: • Can test an object without writing all its environment • Allows a stepwise implementation as we successively replace more and more doubles • When something is wrong we can be almost sure that the problem is in the new object and not in its environment.

  20. Real 2 Real 3 Double 2 Double 1 Real 4 Real 3 Real 2 Double 3 Double 2 Double 1 Real 2 Double 2 Real 1 Real 1 Real 1 Real 1 3 1 4 2 Why use test doubles – 2

  21. Unit-testing patterns – 1 The most important unit test patters are assertion patters. A typical example is shown below. public static void assertEquals(java.lang.String expected, java.lang.String actual) Asserts that two strings are equal. Throws an AssertionFailedError if not.

  22. Unit-testing patterns – 2 Sourceforge uses six basic assertions: • assertFalse. Check that the condition is false • assertTrue. Check that the condition is true • assertEquals. Check that two parameters are equal. • assertNotEquals. Check that two parameters are not equal. • assertNotSame. Check that two objects do not refer to the same object • fail. Fail a test

  23. Unit-testing patterns – 3 In the general case, an assertion should be placed • As close as possible to the new code – for instance right after the last statement. • Right after each important new chunk of code. The intention is to discover any error as soon as possible in order to reduce the debugging effort

  24. Unit-testing patterns – 4 It is important to discover any error as soon as possible. By doing this we have less code to go through in order to find the cause of the error. In order to achieve this, we need assertions whenever we want to check that we are on the right course.

  25. Assertion types An assertion’s type depends on its usage. The most important types are: • Resulting state assertion • Guard assertion • Delta assertion • Custom assertion • Interaction assertion

  26. Resulting state assertion The main idea here is to • Execute some functionality • Check that the resulting state is as expected. This is done using one or more assertions – most often assertEquals assertions.

  27. Guard assertion The guard assertion is used to check that our assumptions of what the status is before executing the new code is correct. E.g.: assertTrue(java.lang.String message, boolean condition) New code to be tested assertNotSame(java.lang.Object expected, java.lang.Object actual)

  28. Delta assertion – 1 A delta assertion is an assertion where we, instead of checking an absolute value, checks the expected delta – change – of the value. This has at least two advantages: • We do not need a “magic number” in the test. • The test will be more robust to changes in the code.

  29. Delta assertion – 2 test_var_1 = current value new code which, among other things, increase the current value by x assertEquals(test_var_1, test_var_1 + x) This assertion will hold whatever value we have for test_var_1.

  30. Custom assertion – 1 Custom assertion covers a wide range of customized assertion patterns – also called “fuzzy assertions”. • Con: they have to be tailored to the assertion that we want to make. • Pro: • They can cover a wide range of important test situations. • Over time we can build up a library of custom assertions thus reducing the need for writing new ones.

  31. Custom assertion – 2 In most cases we can obtain the custom assertion effect by using more complex predicates and expressions in the usual assertions. However, the custom assertion will enable better error handling and allow more informative error messages.

  32. Custom assertion – example custom_assertInrange(in_value, message) assertTrue(message, (in_value < max) && (in_value > min)) As the conditions get more complex, the custom assertion becomes a more attractive alternative

  33. Interaction assertion – 1 The interaction assertion does not check that our code works as expected. Its role is to check that our code cooperate with our collaborator objects as expected. This can be done using any of the assertion patterns that we have discussed earlier, e.g. assertFalse, assertSame, assertEquals or assertTrue.

  34. Interaction assertion – 2 A typical case where we would use an interaction assertion is as follows: • We download a freeware component plus some documentation. • Based on the documentation we believe that if we do “A” then the component will return “B”. • Use an assertion to check if this is true.

  35. Keep or throw away – 1 We need to decide whether we want to keep the assertion in the production code or not. • Pro keeping it:Will quickly diagnose new errors. • Con keeping it:May need updates when the code is changed, e.g. during maintenance

  36. Keep or throw away – 2 The decision may vary depending on the assertion pattern used. E.g. the delta pattern and the custom pattern will be more robust against code changes than for instance the resulting state pattern, especially if it contains one or more magic numbers.

  37. Legacy code

  38. Legacy code – 1 Much of the development done – may be even most of the development done – is changing an existing code base. TDD assumes that we write tests, then write code and then run the tests. This approach is not useful for legacy code and we cannot use TDD directly as described earlier.

  39. Legacy code – 2 The following process should be used: • Identify the change point in the legacy code • Identify an inflection point • Cover the identified inflection point • Break internal dependencies • Break external dependencies • Write tests • Make changes • Refactor covered code

  40. Legacy code – 3 Note that the test in step 3 on the previous slide are not tests to check your changes. These tests are called characterization tests. Their purpose is to help you understand the behavior of the current code. We could thus apply interaction assertion patterns even if there may be no real interactions

  41. Inflection points – 1 An inflection point – also called a test point – is defined as follows: An inflection point is a point downstream from the change point in your code where you can detect any change in the code’s behavior relevant to your changes. Everything else considered equal, we will prefer an inflection point as close as possible to the change point.

  42. Inflection points – 2 We will use a remote inflection point if we e.g. are concerned with side effects from our changes. The side effects may show up only late in the execution and thus a distant inflection point will be a better solution.

  43. Test and change As we change the legacy code we will use both the characterization tests and the new-code tests. • New code tests to check that our changes have the intended effect • Characterization tests to check that we o not break any of the behavior that we want to keep.

  44. Acceptance testing

  45. Write tests forthe story Pick a user story Implement functionality Automate tests The TDD acceptance testing process

  46. TDD acceptance testing We will look in more details on the three first steps of the process: • Pick a user story • Write tests for the story • Automate tests

  47. Pick a user story – 1 We have already partly covered this in an earlier section – “Where to start”. Thus, in many cases, the user stories already are prioritized. Otherwise, the user stories should be prioritized based on: • Business value – what is it worth to the customer. • Technical risk – how difficult is it to implement.

  48. Pick a user story – 2 More formally, we can use business value and technical risk to assess the leverage of each user story: Leverage = (Business value – Tech. risk) / Tech. risk

  49. Pick a user story – 3 When we reach this stage in system development, developers, testers and customers already have had a lot of informal discussions on what the customer needs, when and why. This knowledge will be included in the decision of which user story to pick next.

  50. Write tests for the story – 1 One common way to do this is as follows: • Sit down with the customer and describe the most important scenarios for this user story. • Reach an agreement on what functionality needs to be implemented in order to realize each scenario.

More Related