testing technion – institute of technology 236700 author: gal lalouche © 1 author: gal lalouche -...

39
Testing Technion – Institute of Technology 236700 Author: Gal Lalouche © 1 Author: Gal Lalouche - Technion 2015 ©

Upload: marvin-bailey

Post on 24-Dec-2015

214 views

Category:

Documents


0 download

TRANSCRIPT

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

TestingTechnion – Institute of Technology236700

Author: Gal Lalouche ©

1

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Why (unit) test?

• Programming is incremental by nature• We want to verify we haven’t broken anything

• Tests not only examine the code’s functionality but also its• Ease of use• API• Boilerplate & setup• Error path behavior• Coherency and Coupling issues

• Tests are the usage example of our code and are an excellent source for documentation

2

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testable Code → Good Code

• Tests are usually the first time outside code will use your production code• Is the production code hard to use?• Is object creation long and complex?• Are methods cryptic with lots of parameters?• How does your production code handle usage errors?• Have you been surprised by a method’s behavior?

• Is the production code hard to test?• Do you write 10-15 LOC before you reach the assert line?• Is it difficult to assert the desired behavior?• Do you find it hard isolating the tested feature?• Are you not even sure what you should be asserting? 3

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Test types

• Unit Tests – Test a single element of the system• Integration Tests – Verify that a few units can work together

and are wired up correctly• End-to-End Tests – Test the complete flow of interactions

throughout the system• No “mocks”/”stubs”/”fakes”

• Acceptance / Business logic Tests – Answers the question:“Did we build what we wanted?”• Usually centered around a user story

Our focus today

4

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Test types

5

http://blog.jonasbandi.net/2010/09/acceptance-vs-integration-tests.html

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing Framework – Wish list

• Better Asserts: Validating results Vs. expectations• assert(sqrt(4) == 2) Vs. assertEqual(2,sqrt(4))• assert(x != null) Vs. assertNotNull(x)

• Clear indication of Success / Failure• Meaningful message upon failure

• Ease of use – Should be easy to add new Tests• Aggregate tests together into Test Cases / Tests Suites• Continue with other tests even if a test fails / throws an

exception• No duplication of initialization code of tested objects• Side effects should not influence other tests 6

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit

• A unit testing framework for Java• http://www.junit.org

• A Test is a Method • A Test Case is a class which includes tests methods• Aggregates tests for a single tested unit

• A Test Suite is a collection of Test Cases / Test Suites (or both)• Used to create a hierarchy of tests

7

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit 4.0• A step up from JUnit 3.0 • New Assertions methods in org.junit.Assert.*• No need to extend from junit.framework.TestCase• No need to prefix test methods with ‘test’

• Test methods are annotated with @Test

• @BeforeClass – runs once before first test• Setting up the stubs objects / DB Connections

• @AfterClass – runs once after the last test• Closing DB connections

• @Before method – runs before every test• Preparing data• Could be used for advanced construction

• @After method – runs after every test• Resetting stubs / cleaning data

8

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit test case life-cycle

9

Test Case Class load Static C’tor @BeforeClass*

C’tor

@Before*

@Test

@After*

C’tor

@Before*

@Test

@After*

C’tor

@Before*

@Test

@After*

C’tor

@Before*

@Test

@After*

@AfterClass*

@A* - Running all methods annotated

with A

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit life-cycle - Example• Local resources should be initialized and cleaned before/after

every test

10

public class DbTests {

File output; @Before public void createOutputFile() {

output = new File(...);

output.createNewFile();

} @Test public void testSomething() { ... } @Test public void testSomethingElse() { ... } @After public void deleteOutputFile() {

output.delete();

}

}

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit life-cycle - Example• Expensive and global resources can be initialized and

destroyed before/after all tests

11

public class DbTests {

static DatabaseConnection database;

@BeforeClass public static void login() {

database = new DatabaseConnection(); database.establishConnection();

} @Test public void testSomething() { ... } @Test public void testSomethingElse() { ... } @AfterClass public static void logout() {

database.logout();

}

}

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit 4.0 - Example• Assuming we want to test a new Stack interface

implementation

• Smart Asserts:• assertNotEqual, assertTrue, assertSame,assertNotNull, assertArrayEqual, etc.

12

Name is testing scenario

ResultExpectation

org.junit.ComparisonFailure: expected:<[LAST]-ITEM> but was:<[FIRST]-ITEM>

Initialization

import org.junit.Assert;

import org.junit.Test;

public class StackTestCase {

@Test public void peekReturnsTheLastPushedItem() {

Stack<String> s = new ArrayListStack<>();

s.push("FIRST-ITEM");

s.push("LAST-ITEM");

Assert.assertEquals("LAST-ITEM", s.peek());

} }

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Ideal Test • A recipe for a good test:

• If (1) and (2) are needed at all (e.g., initialization in constructor/@Before), it will be as short as possible (ideally just calls to new)

• (3) will be a single method invocation• (4) will be a single assert

• Of course this ideal cannot always be achieved• But if you can’t, you should always ask yourselves why?

13

@Testpublic void testShouldNameExplicitlyStatesExpectation() { (1) – Initialize collaborators (optional) (2) – Initialize object under test (optional) (3) – Exercise behavior (4) – Assert outputs }

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Test names – bad• It should be easy pin-pointing the problem when a test fails

without looking at its code

• Consider the following test:

• How about addItemWithOutOfStockProduct?• Describes the method and the parameter• Still not a good name – does not describes the desired behavior

14

@Testpublic void addItemTest() {  Product p = new Product();  p.setAvailable(false);  ShoppingCart cart = new ShoppingCart();  cart.addItem(p);  assertEquals(0, p.numItems());}

Bad name.Specifies the name of the

invoked method but not the desired behavior

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Test names – good• It should be trivial understanding what is the expected

behavior from the name alone• We do not need to mention the name of the invoked method

• Good rule of thumb: the test name should include an explicit expectation• Use verbs such as should, does, returns, throws, etc. 15

@Testpublic void doNotCountOutOfStockItem() {  Product p = new Product();  p.setAvailable(false);  ShoppingCart cart = new ShoppingCart();  cart.addItem(p);  assertEquals(0, p.numItems());}

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Exceptions• A test that throws an exception will fail.• So how do we make sure a code throws an exception when it should?

• We would like to verify 3 things:• The right exception has been thrown• The right line has thrown the exception (sometimes)• The exception contains the right data (rarely)

16

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Exceptions• The hard way – Using the try/catch pattern

• Verifies our 3 wishes• Easy to forget the fail resulting in a test that never fails• Quite long and verbose• Test code is just as important as our “regular” code! We want to minimize

code duplication and boilerplate.

@Testpublic void badEncodeArgument() { Encoder encoder = new Encoder(“UTF-8”); try { encoder.encode(null); fail(“Exception should have been thrown”); } catch (NullPointerException e) { assertEquals(“Text should not be null”, e.getMessage()); } }

17

Based on http://alexruiz.developerblogs.com/?p=1530

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Exceptions• The easy way (JUnit 4.0) – expected value in @Test annotation

• Can’t check the exception message• Can’t verify which line has thrown the exception• Very short

• Can be “good enough” in most cases

@Test(expected = IllegalArgumentException.class)public void badEncodeArgument() { Encoder encoder = new Encoder(“UTF-8”); encoder.encode(null);}

18

Based on http://alexruiz.developerblogs.com/?p=1530

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Exceptions• The smart way (JUnit 4.7) – Using @Rule annotation

and ExpectedException

• Verifies our 3 wishes• Still quite short

• JUnit Rules offers a few additional candies

@Rule public ExpectedException thrown = ExceptedException.none();

@Testpublic void badEncodeArgument() { Encoder encoder = new Encoder(“UTF-8”); thrown.expect(NullPointerException.class); thrown.expectMessage(“Text should not be null”); encoder.encode(null);}

19

Based on http://alexruiz.developerblogs.com/?p=1530

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit - Timeout• We want to verify that our code finishes in the allotted time.

• This is criminally verbose

@Testpublic void testTimeout() {

final boolean[] done = new boolean[] { false };new Timer().schedule(new TimerTask() {

@Override public void run() {foo();done[0] = true;

}}, 0);Thread.sleep(10);if (done[0] == false)

fail(“Failed to run in the allotted time");}

20

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit - Timeout• In JUnit 4.0 – Use the timeout annotation value

• Test will fail after 10 milliseconds.

• Global timeouts using @Rule (JUnit 4.7)

@Test(timeout = 10)public void testTimeout() { // Tested code}

21

@Rule public MethodRule globalTimeout = new Timeout(10);

@Testpublic void test1() { // Tested code}

@Testpublic void test2() { // Tested code}

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Ignoring tests• You can ignore tests using the @Ignore annotation• Test will not run, but will still be visible in the test reports

• Commented out tests will usually be forgotten about• Won’t be deleted and possibly will not be fixed

• Ignored tests are easily visible in the output window

• Make sure you disable tests temporarily• Bad tests should either be deleted or fixed• Ask yourself – why are you ignoring the test in the first place?

22

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit - Running• Textual runner• Used mainly in command line environments (Scripts, Maven, Ant, CI)

• IDE Support• To run several test cases

org.junit.runner.JUnitCore.runClasses(TestCase1.class, ...);

@RunWith(Suite.class)@Suite.SuiteClasses({ TestCase1.class, TestCase2.class, subpackage.AllTests.class, …})public class AllTests {} 23

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

JUnit – Source code Organization

• Option 1: Test class in same folder as subject• A single hierarchy of folders• Locality

• Option 2: Keep tests in the mirrored directory structure of source location• A duplicated hierarchy of folders• Easy to find all tests• Easy to separate source code from its tests• Easier bytecode separation

24

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Leveraging OOP• Tests are classes too!• Can use inheritance to our advantage• Use Abstract Test classes against abstract classes / interfaces

25

A

B C

ATest

BTest CTest

Classes Tests Class

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Leveraging OOP

26

public abstract class Stack<T> { public void push(T element); public int size();}

public class ArrayStack<T> extends Stack<T> { // … implementation code}

public class LinkedListStackStack<T> extends Stack<T> { // … implementation code}

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Leveraging OOPpublic abstract class StackTest { private final Stack<Object> stack;  protected StackTest(Stack<Object> stack) { this.stack = stack; }  @Test public void sizeIsOneAfterOnePush() { stack.push(new Object()); Assert.assertEquals(1, stack.size()); }}

27

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Leveraging OOP

• Extend abstract TC to initialize the concrete test subjects

28

public class LinkedListStackTest extends StackTest {  public LinkedListStackTest() { super(new LinkedListStack<Object>()); } // specific LinkedListStackTest tests}

public class ArrayStackTest extends StackTest {  public ArrayStackTest() { super(new ArrayStack<Object>()); }

// specific ArrayStack tests }

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Order (or lack thereof)• Do not assume order between tests• There’s no order guaranties in TestCases• There is one in TestSuites

• Avoid side effects in tests

29

public class StackTest {  private static Stack<Object> s = new Stack<>();

@Test public void pushFirstAndCheckSize() { s.push(new Object()); assertEquals(1,s.size()); }

@Test public void pushSecondAndCheckSize() { s.push(new Object()); assertEquals(2,s.size()); }}

Bad !

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Resource Management• Do not load data from hard-coded locations on a file system

• Use Class.getResource() or Class.getResourceAsStream()

• Will search the file relatively to the Test Case .class file• Maven will automatically create a src & test resource folders

@Beforepublic void setUp () { InputStream inp = FileInputStream("C:\\TestData\\dataSet1.dat"); …}

@Beforepublic void setUp () { InputStream inp = getClass().getResourceAsStream("dataSet1.dat"); …}

30

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Unit Testing: What to Test• It is not practical to test everything:• State space > Not always practical• Loops in the code State space is infinite / Halting Problem• End to End and Acceptance tests will cover the state space quite well

• Cover as much of the code as possible, but be smart about it• Extensiveness of the test should be proportional to the complexity of the

implementation of the tested method• If m2() calls m1(), no need to duplicate m1() tests for m2()• But, for each variation in the flow (usually an if()) you should have a

corresponding test that exercises it• Always test public methods

• Should you test package-private and protected methods as well?• Private methods usage is important only in the context public methods

• If you find yourself wanting to test a private method, you should rethink your design, (e.g., extract the private method to a helper class)

31

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Unit Testing: Quality• Cover code usage• Check common usage errors / edge cases:

• nulls• empty strings• zeros• empty lists• single-item lists• uninitialized objects• bad configurations

• Exception handling

• Code Coverage• Function, Statement, Branch, Decision coverage• Monitor using external Code Coverage tools (e.g. EclEmma)

32

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Code Coverage - EclEmma

33

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Code Coverage• Code coverage is correlated with good testing, but does not

equate it• You can achieve very high coverage with bad tests (e.g., testing

without asserts)• You can write great tests with low coverage

• 100% coverage may look “pretty”, but should not be a goal in and off itself• You should always suspect tests that were written solely for

achieving high coverage• Do you really need to test for NullPointerException for each

of your parameters?• You can use code coverage to Spartanize your code• e.g., branches that are never taken may be dead code 34

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Do’s & Don’ts• Mutability makes code harder to test• Tests also have to take state into account

• Always place shortest, simplest tests first in a TestCase or in a TestSuite• You should always make sure the simplest cases work before you

carry on to the complicated ones• Prefer lots of short tests instead of a few long ones• Every test should test a single behavior• Speed up discovery-to-fix cycle

• Testing code should be simpler than the subject code• Complicated tests usually implies complicated production code usage• Tests are the code usage examples• Do not put if statements in your tests. Loops are quite rare.

35

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Do’s & Don’ts

• Always make sure you start Green before you start coding• This way, you know if you broke something• Unless working in TDD…

• In which case, start Green before refactoring!

• Regression testing • Make sure new additions don’t break existing code

• Run tests as often as possible• The earlier you detect the bug the sooner you solve it

and the easier it becomes• Therefore, test runs should be short, at most 10ms per

test• Never commit code that doesn’t pass all tests!

36

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Do’s & Don’ts• When you work on a bug, write a test case first• Your work is done when the test case succeeds• If you write the test after the bug is fixed, you cannot know if

the test succeeded in reproducing the bug• Helps with regression testing, i.e., ensuring the bug won’t

reappear later• You can actually debug using unit tests!

1. Write a test that fails2. If you still aren’t sure what the bug is, write a smaller unit

test that fails3. Repeat step 2 as necessary• This could be preferable to running a debugger, as the tests

remain when done

37

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Testing – Do’s & Don’ts

• When you change the subject class, add tests

• Testing is meant to improve the quality of the code• Do not write bad code to pass tests !!

• Although possible in TDD…• Do not write bad tests to pass tests !! (Make sure a test can fail)

• Hard to test? Re-think the design!

38

Auth

or: G

al L

alou

che

- Tec

hnio

n 20

15 ©

Test types – misc. (not taught this semester)• Behavior-driven development• Using Domain Specific Language (DSL) to structure tests around

stories and features• Property tests• Large randomized input to check invariants and properties of

output• Mutability testing• Changing (mutating) the source code or tests slightly, to see if our

tests fail• This ensures tight coupling between tests and source code

• Design by Contract• Asserting pre and post-conditions on class’s methods• Fails in runtime, possible to check even at compile time

39