Developer Testing Life of a Software Engineer How



























- Slides: 27
Developer Testing
Life of a Software Engineer
How a software engineer spends time o o Writing code is a small fraction. Time spent on: n n n figuring out what ought to be going on designing debugging (most of the time)
Debugging o o o Finding bugs (place in code) could take days. Fixing the bug may not take that much of time. When you fix a bug: n n n Chance that you will introduce another bug You may notice immediately Again could take ages to find and fix
An attempt for a remedy o o “Classes should contain their own tests”. Test methods that test the class n o Instead ask the computer to do the test. n n o Outputs to console – you need to inspect. Do all comparisons. Just tell us whether the test passes or not. THIS IS SELF TESTING CODE.
Advantages of self testing code o o o Running tests is as easy as compiling. Over time a suite of tests will test 100 s of classes testing the system comprehensively. If already working functionality is broken we get immediate feedback. We can be more confident of changing working code (even other people’s code) Aids XP practices of n Collective code ownership n Continuous integration n Refactoring Thinking of the interface, promotes thinking using Object Oriented principles.
Does it make sense to write more code o Wouldn’t we waste our time writing tests. That is will projects get delayed because of writing unit tests. n n Logically it does not make sense. However, practice shows => Unit tests make development faster.
When should we write unit tests? o o Before you start programming. When you need to add a feature, begin by writing the test.
JUnit o o Open source testing framework – developed by Erich Gamma and Kent Beck. The framework is very simple, yet it allows you to do all the key things you need for testing.
Practice Exercise
1. Create an empty class and compile 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. /** * Reads money values from a property file */ public class Money. Reader { /** * constructs a Money. Reader instance * @param path the path of the property file */ public Money. Reader(String path) { } /** * returns the currency code * @param key the key in the property file */ public String get. Currency. Code(String key) { return null; } /** * returns the amount * @param key the key in the property file */ public long get. Amount(String key) { return 0; } }
2. Now let’s create the skeleton for the test class. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. import junit. framework. Test. Case; import junit. framework. Test. Suite; import junit. textui. Test. Runner; /** * This Tests the class Money. Reader */ public class Money. Reader. Tester extends Test. Case { /** * constructs an instance */ public Money. Reader. Tester(String name) { super(name); } public static void main(String args[]) { Test. Runner. run(suite()); } public static Test. Suite suite() { return new Test. Suite(Money. Reader. Tester. class); } /** * creates the environment that the test depends on */ public void set. Up() { } /** * This cleans the test environment created by set. Up() and * the changes done to environment by test methods */ public void tear. Down() { } /** * This tests the constructor */ public void test. Constructor() { } }
3. Now let’s code the test method to test the constructor. If the file exists the constructor should work OK else it should throw an exception. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. /** * This tests the constructor */ public void test. Constructor() { try { Money. Reader reader. Fail = new Money. Reader("none. properties"); fail("An IOException was expected"); } catch (IOException e) { //do nothing as this Exception was expected } } Try to compile the test. You may have to change the method signature of the Money. Reader as public Money. Reader(String path) throws IOException
4. Now we need to code the constructor such that the test passes 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. // add the new field Properties props; //code the constructor /** * constructs a Money. Reader instance * @param path the path of the property file */ public Money. Reader(String path) throws IOException { props = new Properties(); props. load(new File. Input. Stream(path)); } Now lets compile and run the test again. The test passes with output. Time: 0. 06 OK (1 tests)
5. Now let’s focus on get. Currency. Code(). We need a property file. Let's use the set. Up() to create it and tear. Down() to remove it. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. /** * creates the environment that the test depends on */ public void set. Up() throws IOException { Properties props = new Properties(); props. set. Property("money", "EUR 5000"); File. Output. Stream fos = new File. Output. Stream("money. props"); props. store(fos, "HEADER"); fos. close(); } /** * This cleans the test environment created by set. Up() and * the changes done to environment by test methods */ public void tear. Down() { File file = new File("money. props"); file. delete(); }
6. Now further improve the test for the constructor to test proper construction 1. try { 2. Money. Reader reader = new Money. Reader("money. props"); } catch (Exception e) { fail("Exception caught"); } 3. 4. 5. Compile and run the test.
7. Add the test method 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. /** * This tests the get. Currency. Code method */ public void test. Get. Currency. Code() { try { Money. Reader reader = new Money. Reader("money. props"); assert. Equals("EUR expected", "EUR", reader. get. Currency. Code("money")); } catch (IOException e) { fail("Exception caught"); } } compile and run the test it should fail
8. Now lets fix the class to pass the test /** * returns the currency code * @param key the key in the property file */ public String get. Currency. Code(String key) { String value = props. get. Property(key, ""); String. Tokenizer tokenizer = new String. Tokenizer(value); if (tokenizer. has. More. Elements()) { return tokenizer. next. Token(); } return null; } Now compile and run the test. . Time: 0. 38 OK (2 tests)
9. We can do the same for get. Amount() 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. /** * returns the amount * @param key the key in the property file */ public long get. Amount(String key) { String value = props. get. Property(key, ""); String. Tokenizer tokenizer = new String. Tokenizer(value); if (tokenizer. has. More. Elements()) { String currency = tokenizer. next. Token(); } if (tokenizer. has. More. Elements()) { String amount. Str = tokenizer. next. Token(); return Long. parse. Long(amount. Str); } return 0; } /** * This method tests get. Amount method */ public void test. Get. Amount() { try { Money. Reader reader = new Money. Reader("money. props"); assert. Equals("5000 expected", 5000 L, reader. get. Amount("money")); } catch (IOException e) { fail("Exception caught"); } }
10. Now let's test the boundary condition. i. e. if no amount is stored, 0 returned Add line to set. Up() props. set. Property("money 1", "EUR"); change test. Get. Amount() as 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. /** * This method tests get. Amount method */ public void test. Get. Amount() { try { Money. Reader reader = new Money. Reader("money. props"); assert. Equals("5000 expected", 5000 L, reader. get. Amount("money")); assert. Equals("0 expected", 0 L, reader. get. Amount("money 1")); } catch (IOException e) { fail("Exception caught"); } }
When a bug is found o o After a while a bug is found. The classes that use Money. Reader expects n n o it to return valid currency codes that these recognize. If not valid throw an exception. Money. Reader just assumes whatever value in the text file to be valid.
11. Add a new line to set. Up() + change the test. props. set. Property("money 2", "MYCURRENCY"); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. /** * This tests the get. Currency. Code method */ public void test. Get. Currency. Code() { try { Money. Reader reader = new Money. Reader("money. props"); assert. Equals("EUR expected", "EUR", reader. get. Currency. Code("money")); try { String currency. Code = reader. get. Currency. Code("money 2"); fail("Exception expected as 'MYCURRECY' is not a valid currency"); } catch (Runtime. Exception e) { // do nothing this is an expected exception } } catch (IOException e) { fail("Exception caught"); } }
12. Now let's fix the class to pass the test Add field: /** Valid currency codes (from ISO 4217) that our system recognizes */ private static final String[] currency. Codes = {"EUR", "USD", "GBP", "JPY", "DEM"}; o 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. change method /** * returns the currency code * @param key the key in the property file */ public String get. Currency. Code(String key) { String value = props. get. Property(key, ""); String. Tokenizer tokenizer = new String. Tokenizer(value); String currency = ""; if (tokenizer. has. More. Elements()) { currency = tokenizer. next. Token(); } // check whether a valid currency code boolean currency. Valid; List currencies = Arrays. as. List(currency. Codes); currency. Valid = currencies. contains(currency); if (currency. Valid) { return currency; } else { throw new Runtime. Exception(); } } Compile and run the test. It should pass. . . Time: 0. 44 OK (3 tests)
FAQ
o o 1. What should I test? Every piece of code that has the risk of failure. This includes boundary conditions as well. Also expected exceptions need to be tested. 2. Should there be a test for each public method I write in a class? Not necessarily. You should test code that has a risk of failure. So no point in testing getters and setters. Also there can be situations where more than one test is required to test a method. Use your common sense. Test everything that has a risk of failing or being broken in the future.
o o 3. When should I write the test? Before adding each functionality you should write the test for it. 4. Can I postpone the wrting of the tests, for example to the end of the week or till all the features are in? NO!!. Besides losing the advantages of doing the test first, you might never get the time to do this as lack of tests would cause you to spend more time debugging-fixingintroducing new bugs-debugging-fixing cycles. (Also late nights)
o o 5. How and when should I run the tests? Every time you compile you need to run the test for that particular class. Also you need to have all the tests for the system added to a system wide test suite which when you run executes all the tests for the system. You need to run this (the whole suite()) everytime before you commit changes to the CVS. This is important because there may be dependencies between classes such that a defect which was newly introduced in a class but escaped its unit test (ideally it should catch it) gets caught in another class’s unit test. 6. What is the ideal testing environment for an XP project? On a clean machine you checkout the sources from CVS (for the first time). Without installing or any system changes you run ant test. This should (install/deploy) the system; set system settings; create dependent database settings; start appservers/webservers etc; run all the tests, print results; clean everything the test did and shut down all servers started by the test. Make it a must to create this environment from day one of the project and maintain it as it’ll be time consuming to do later.