Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Unit Testing in Java
1. Unit Testing in Java
Testing for Developers
Guy Davis
9/18/2006
01/10/2009
2. Objectives
Understand JUnit4 test framework
Learn unit testing methodology
What, when, and how to test…
–
Design app for testability and maintainability
Write maintainable unit tests
Assertions, Stubbing, Fixtures
–
Contrast with functional/acceptance testing
2 01/10/2009
3. Contents
Introduction to Unit Testing
Unit Testing Methodology
Test Style and Maintainability
JUnit Assertions
Test Fixture Setup
Testing Indirect Inputs and Outputs
Design for Testability
Testing Legacy Systems
Data-driven Testing
3 01/10/2009
4. Why Test?
Why Developer Testing?
Can’t QA “test quality into the product”?
–
Test terminology
Black-box and white-box tests
–
Unit, Component, System
–
Integration, Functional, Acceptance
–
Regression, Performance, Destructive
–
Question: Why automate tests?
4 01/10/2009 Section: Introduction to Unit Testing
5. Compared to Functional Testing
What is functional testing?
Tests the requirements, not the design.
–
Tests at the system level, not the unit (class).
–
Involves many interacting components
Typically requires more setup to test
–
Functional tests are more state-dependent
Automated functional tests avoid the GUI
–
Test the business processes and logic
5 01/10/2009 Section: Introduction to Unit Testing
6. JUnit Test Framework
Concepts in JUnit:
Test Method: single test
–
Test Case: group of test methods
–
Test Suite: group of test cases
–
Test Fixture: supporting objects
–
Recently updated to JUnit4
Uses assertions from JDK 1.5
–
6 Section: Introduction to Unit Testing
01/10/2009
7. Running JUnit, Viewing Results
Running JUnit in Eclipse
Run, Debug
–
Single/All
–
Running JUnit from Ant
Unit, Integration, All
–
Build system test results
Code coverage report
7 01/10/2009 Section: Introduction to Unit Testing
8. Test Execution
Steps in a test
Setup test fixtures: @BeforeClass, @Before
–
Exercise the system under test (SUT)
–
Verify the expected outcomes
–
Cleanup: @AfterClass, @After
–
Two possible outcomes:
Pass or Fail
–
8 Section: Introduction to Unit Testing
01/10/2009
9. Verifying Outcomes
Use assertXYZ(“comment”, expected, actual)
Use most appropriate assert available
–
Add descriptive comment as first argument
–
Test exceptional outcomes
@Test(expected = IllegalArgumentException.class)
–
Use fail(“Should never …”) method
–
Performance assertions
@Test(timeout=500)
–
9 Section: Introduction to Unit Testing
01/10/2009
10. Finding Test Conditions
What are the inputs to SUT?
Method arguments
–
System state
–
External data and files
–
What are the outputs of SUT?
Direct outputs: Returned values, exceptions
–
Side effects: state of system and its objects
–
10 Section: Unit Testing Methodology
01/10/2009
11. Finding Test Cases
Boundary Values
For -256, -1, 0, 1, 255 for 8-bit integers
–
Cardinality Choices
0, 1, 2
–
Null, “”, “Some String”
–
Failure cases
Nulls and other invalid inputs
–
11 Section: Unit Testing Methodology
01/10/2009
12. Test Granularity
How many tests are needed?
Enough to cover all test conditions
–
If code coverage is 100%: Are you done?
–
How many conditions per test?
1:1 is ideal; easier to determine exact failure
–
Not so small it creates unnecessary overhead
–
12 Section: Unit Testing Methodology
01/10/2009
13. Assertions
Goals for assertions:
Tests as documentation
–
Avoid test code duplication
–
Reduce likelihood of test errors; simplicity
–
Assertion patterns:
Expected Objects
–
Guard Assertions
–
Custom Asserts
–
13 Section: JUnit Assertions
01/10/2009
14. Terminology
Clean Slate
Each test method does own setup
–
Independent and cohesive
–
Standard Test Fixture
Assumes standard environment for each
–
Shared Test Fixture
Standard environment shared by all tests
–
14 Section: Test Fixture Setup
01/10/2009
15. Practices
Extract common fixtures into setUp()
Avoid code duplication for test setup
–
Write creation methods i.e. createPerson()
Place common cleanup into tearDown()
Better than many finally blocks in tests
–
Use inner classes (named & anonymous)
Avoids test code in production classes
–
Avoid hard-coded data in tests-> use constants
15 Section: Test Fixture Setup
01/10/2009
16. What is a good test?
Self-checking Unit testing Patterns
Single glance readable
Repeatable –
Single condition test
–
Robust
Declarative style
–
Complete
Clean-slate setup
–
Efficient
Custom assertions
–
Stub out dependencies
Specific –
Consistent naming
–
Reviewed
Back to front test coding
–
Test-driven development
–
16 Section: Test Style and Maintainability
01/10/2009
19. Indirect Inputs
Types of inputs:
Returned values internal to SUT
–
17 Section: Testing Indirect Conditions
01/10/2009
20. Indirect Inputs
Types of inputs:
Returned values internal to SUT
–
Updateable parameters
–
17 Section: Testing Indirect Conditions
01/10/2009
21. Indirect Inputs
Types of inputs:
Returned values internal to SUT
–
Updateable parameters
–
Internal exception handling
–
17 Section: Testing Indirect Conditions
01/10/2009
22. Indirect Inputs
Types of inputs:
Returned values internal to SUT
–
Updateable parameters
–
Internal exception handling
–
How do we test this?
17 Section: Testing Indirect Conditions
01/10/2009
23. Indirect Inputs
Types of inputs:
Returned values internal to SUT
–
Updateable parameters
–
Internal exception handling
–
How do we test this?
Mock Objects (a.k.a. Stubs)
–
17 Section: Testing Indirect Conditions
01/10/2009
24. Stubbing Strategy
Test creates stub
1.
Stub provided to SUT Test
2.
SUT uses Stub
3.
Test verifies stub (passive)
4.
SUT
OR stub asserts (active)
Benefits:
Stub
Isolation
Completeness
Performance
18 Section: Testing Indirect Conditions
01/10/2009
27. Indirect Outputs
Types of output:
Log files written to
–
19 01/10/2009 Section: Testing Indirect Conditions
28. Indirect Outputs
Types of output:
Log files written to
–
Database tables altered
–
19 01/10/2009 Section: Testing Indirect Conditions
29. Indirect Outputs
Types of output:
Log files written to
–
Database tables altered
–
Emails sent
–
19 01/10/2009 Section: Testing Indirect Conditions
30. Indirect Outputs
Types of output:
Log files written to
–
Database tables altered
–
Emails sent
–
How can we verify these outputs?
19 01/10/2009 Section: Testing Indirect Conditions
31. Indirect Outputs
Types of output:
Log files written to
–
Database tables altered
–
Emails sent
–
How can we verify these outputs?
Check the real component
–
19 01/10/2009 Section: Testing Indirect Conditions
32. Indirect Outputs
Types of output:
Log files written to
–
Database tables altered
–
Emails sent
–
How can we verify these outputs?
Check the real component
–
Replace with a mock object
–
19 01/10/2009 Section: Testing Indirect Conditions
34. What defines testable code?
Easier to test a new class or an existing one?
Why? What makes a testable class?
–
Separation of concerns
20 01/10/2009 Section: Design for Testability
35. What defines testable code?
Easier to test a new class or an existing one?
Why? What makes a testable class?
–
Separation of concerns
High cohesion
–
20 01/10/2009 Section: Design for Testability
36. What defines testable code?
Easier to test a new class or an existing one?
Why? What makes a testable class?
–
Separation of concerns
High cohesion
–
Low coupling
–
20 01/10/2009 Section: Design for Testability
37. What defines testable code?
Easier to test a new class or an existing one?
Why? What makes a testable class?
–
Separation of concerns
High cohesion
–
Low coupling
–
When should we write tests?
20 01/10/2009 Section: Design for Testability
38. What defines testable code?
Easier to test a new class or an existing one?
Why? What makes a testable class?
–
Separation of concerns
High cohesion
–
Low coupling
–
When should we write tests?
Test-first development
–
20 01/10/2009 Section: Design for Testability
39. Designing for Testability
Architectural choices
Layered design is more testable (i.e. MVC)
–
Design choices:
What types to test?
–
Concrete classes (good)
Abstract classes (better)
Interfaces (best)
Avoid the singleton design pattern. Why?
–
Development process:
Write the tests first
–
21 01/10/2009 Section: Design for Testability
40. Adding Tests to Old Code
How do we safely fix/enhance untested code?
Identify the area you need to change.
1.
Build a safety net there first
2.
Wrap existing code with unit tests before touching it
–
With safety net in place…
3.
Refactor the code to ease addition of new code
1.
Write unit test for issue
2.
Add code for fix/enhancement
3.
22 01/10/2009 Section: Testing Legacy Systems
41. Data-driven testing
Parameterized testing
Write single assert method that takes parameters
–
Runs the same test over and over with different inputs
–
MockRunner JDBC
Mocks for Connection, Statement, ResultSet etc.
–
DBUnit and HSQLdb
Loads data (XML files) into in-memory DB just for test
–
Others?
23 01/10/2009 Section: Data-Driven Testing
Automate tests:
Cost not proportional to number of test runs like manual is
More repeatable and less error-prone
More fun and more likely to happen
Goal of testing is to prevent defects, not find as many as possible. Root-cause analysis, fix the source of the problem.
Mention how I always mix up the TestCase with a test case which is a certain scenario.
Show the test results links for branches.
Show Cobertura report.
Show LoadModel test case
Question: What happened to Error outcome from JUnit?
Can either make static Assert.assertFoo() calls or use a static import to shorten this down.
Show example of exception assertion in DVAdapterTest class.
Expected objects: don’t compare pointX and pointY variables, just compare Point (object)
Guard assertion: assertNotNull(collection) before asserting about its contents
Custom assert: assertExactlyOneChildNode(node); created during test refactoring
Clean Slate: watch out for code duplication
Standard: brittleness from dependence on external data
Shared: test run wars, interactions between tests, unrepeatable tests, example is our DV_TEST database
A mock object fills the role of the real dependency by acting just like it.
A mock object fills the role of the real dependency by acting just like it.
A mock object fills the role of the real dependency by acting just like it.
A mock object fills the role of the real dependency by acting just like it.
A mock object fills the role of the real dependency by acting just like it.
A mock object fills the role of the real dependency by acting just like it.
Install Stub into SUT by:
Passing as argument
Passing as constructor argument
Set explicitly with setter method
Set in the environment (preferences, system property, JNDI)
Discuss Active versus Passive stubs (Active stubs participate in the verification)
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Real component: Talk about DBUnit and embedded databases like HSQL for Merge tests.
Mock object: Show MockRunner objects in LoadModel test for working JDBC.
Mention that mock objects don’t need to be created by hand. Projects like EasyMock, jMock, and MockMaker can auto-generate.
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
Talk about how much setup some tests require.
Then show the answer: separation of concerns
Then ask when should we test?
See section 11 page 7 last slide for reasons.
Mention that this can be hard as the original code wasn’t written with testability in mind. Likely it is highly coupled and not cohesive.
Normally, you will need to iterate around the “write tests & refactor” loop until you have decent coverage.
Show LoadModelTest for the Mockrunner example.
Show AbstractPreferencesTestCase for the DBUnit.