unit tes2ng ac2vity - se.rit.eduswen-610/activities/unit testing activity.pdf · the structure of a...

20
SWEN-261 Introduc2on to So3ware Engineering Department of So3ware Engineering Rochester Ins2tute of Technology Unit Tes2ng Ac2vity

Upload: others

Post on 30-Sep-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

SWEN-261Introduc2ontoSo3wareEngineeringDepartmentofSo3wareEngineeringRochesterIns2tuteofTechnology

UnitTes2ngAc2vity

Page 2: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Your activity for the Unit Testing lesson is to build tests for existing Project components.

§ These slides direct you through the process of creating unit tests for your project. ü Activity actions are highlighted in green with a

checkmark bullet. § But first, these slides provide technical details:

1.  How to organize test classes using Maven 2.  How to run tests using Maven 3.  How to structure a typical test class 4.  How to use JUnit assertion functions 5.  How to use package-private constants in test code 6.  How to use Mockito mock objects using Dependency

Injection

2

Page 3: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Maven provides a convenient structure for organizing unit test code.

§ Put your test classes in a separate source path. •  The goal is to keep the test source code separate from

the production source code. •  Using Maven that is usually src/test/java. ü Create this directory if it doesn't already exist. ü (Optional) Link your IDE to this source path.

§ Most IDEs provide wizards for creating unit tests. •  Make sure the IDE stores the test classes in the proper

source path. § The unit test code examples in these slides are from

the Guessing Game. •  See the Unit Testing topic page for a link to the ZIP file.

3

Page 4: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Maven will run tests during builds and there is also the test target. [~/workspace/guessing-game] ➔ mvn clean test [INFO] Scanning for projects... // SKIPPING SOME Maven OUTPUT ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.example.appl.GameCenterTest Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.802 sec Running com.example.model.GuessGameTest Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec Running com.example.ui.GetGameRouteTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.267 sec Running com.example.ui.GetHomeRouteTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 sec Running com.example.ui.PostGuessRouteTest Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec Results : Tests run: 24, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------

4

Page 5: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

The structure of a unit test class.

§ Name the test class after the component under test (CuT) in the same package. •  So if CuT is com.example.model.MyEntity •  Then test class is: com.example.model.MyEntityTest

•  Doing so gives the test code package-private access to CuT class.

§ Annotate each test method with @Test. § Use an @Before annotated method to setup

common test scenario; an example comes later. § Use an @After annotated method to clean up.

5

Page 6: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Recall the checklist of types of unit tests.

§ Business logic •  Tests for each path through a method •  Happy path as well as failure paths

§ Constructors and accessors § Defensive programming checks

•  Validation of method arguments w NullPointerException w IllegalArgumentException

•  Validation of component state w IllegalStateException

§ The equals and hashCode methods, as needed § Exception handling

6

Page 7: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Here's an example unit test suite for the GuessGame class. package com.example.model; import static org.junit.Assert.*; import org.junit.Test; public class GuessGameTest { @Test public void ctor_noArg() { ... } @Test public void ctor_withArg() { ... } @Test(expected = IllegalArgumentException.class) public void ctor_tooBig() { ... } @Test(expected = IllegalArgumentException.class) public void ctor_tooSmall() { ... } @Test public void isValidGuess() { ... } @Test public void testGamePlay_win_first_try() { ... } @Test public void testGamePlay_win_second_try() { ... } @Test public void testGamePlay_win_last_try() { ... } @Test public void testGamePlay_loose() { ... } }

7

Test constructors.

Test defensive programming checks.

Test business logic.

Import JUnit assertion functions.

Page 8: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Here's an example test method for the GuessGame class. import static org.junit.Assert.*; import org.junit.Test; public class GuessGameTest { @Test public void testGamePlay_win_first_try() { // Setup test scenario: game start (also Invoke test) final GuessGame CuT = new GuessGame(NUMBER); // Analyze results assertTrue(CuT.isGameBeginning()); assertTrue(CuT.hasMoreGuesses()); assertEquals(3, CuT.guessesLeft()); // Invoke test: first guess, correct assertTrue(CuT.makeGuess(NUMBER)); // Analyze results assertFalse(CuT.isGameBeginning()); assertTrue(CuT.hasMoreGuesses()); assertEquals(2, CuT.guessesLeft()); }

8

Page 9: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Here's an example of how to test an expected exception. § Use the @Test annotation expected attribute: public class GuessGameTest { private static final int TOO_BIG = 11; @Test(expected = IllegalArgumentException.class) public void ctor_tooBig() { new GuessGame(TOO_BIG); }

§ Roughly the same as: public class GuessGameTest { private static final int TOO_BIG = 11; @Test public void ctor_tooBig() { try { new GuessGame(TOO_BIG); fail("Ctor should fail."); } catch (IllegalArgumentException e) { // success, do nothing } }

9

Page 10: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

JUnit has many built-in assertions you can use.

§ Test truth-hood • assertTrue([message,] condition) • assertFalse([message,] condition)

§ Test values or objects for equality • assertEquals([message,] expected, actual) • assertNotEquals([message,] expected, actual)

§ Test objects for identity (obj1 == obj2) • assertSame([message,] expected, actual) • assertNotSame([message,] expected, actual)

§ Test null-hood • assertNull([message,] object) • assertNotNull([message,] object)

§ Automatic failure: fail(message) 10

Page 11: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Recall the coding tip recommended.

§ Idioms for testable code: •  Make message strings (and other values) constants •  Make these members package-private (or public)

§ Example: public class GameCenter { final static String NO_GAMES_MESSAGE = "No games have been played so far."; final static String ONE_GAME_MESSAGE = "One game has been played so far."; final static String GAMES_PLAYED_FORMAT = "There have been %d games played."; public synchronized String getGameStatsMessage() { if (totalGames > 1) { return String.format(GAMES_PLAYED_FORMAT, totalGames); } else if (totalGames == 1) { return ONE_GAME_MESSAGE; } else { return NO_GAMES_MESSAGE; } }

11

Page 12: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Here's how the test code would use these members. /** * Test the Game stats message over multiple games played. */ @Test public void play_several_games() { // Setup the test scenario: no games play yet final GameCenter CuT = new GameCenter(); // * Analyze the results assertEquals(GameCenter.NO_GAMES_MESSAGE, CuT.getGameStatsMessage()); // * Create first game CuT.end(makeSession()); // * Analyze the results of first game assertEquals(GameCenter.ONE_GAME_MESSAGE, CuT.getGameStatsMessage()); // * Create second game CuT.end(makeSession()); // * Analyze the results of second game assertEquals(String.format(GameCenter.GAMES_PLAYED_FORMAT, 2), CuT.getGameStatsMessage()); // * Create third game CuT.end(makeSession()); // * Analyze the results of third game assertEquals(String.format(GameCenter.GAMES_PLAYED_FORMAT, 3), CuT.getGameStatsMessage()); } 12

Page 13: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Now let's consider writing tests for more complex components; one's with dependencies.

13

§ UI Controllers in Spark need to access Spark's HTTP request, session and response objects.

§ For example, the PostGuessRoute controller also needs access to the GameCenter Application component.

§ So this component's seam looks like this: The GameCenter and GuessGame are thoroughly tested so are friendly.

Use mock objects for Spark dependencies.

Page 14: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

To review: this is what dependency injection looks like in the component under test. public class PostGuessRoute implements TemplateViewRoute { private final GameCenter gameCenter; /** * The constructor for the {@code POST /guess} route handler. * * @param gameCenter * The {@link GameCenter} for the application. * * @throws NullPointerException * when the {@code gameCenter} parameter is null */ PostGuessRoute(final GameCenter gameCenter) { // validation Objects.requireNonNull(gameCenter, "gameCenter must not be null"); // this.gameCenter = gameCenter; }

14

This JDK method performs a null check and throws an NPE if violated.

This class is instantiated by the WebServer component, which configures Spark.

Page 15: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

The GameCenter class has been thoroughly tested so it is a friendly dependency. import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class PostGuessRouteTest { private static final int NUMBER = 7; private static final String WRONG_GUESS = "3"; private static final String NOT_A_NUMBER = "asdf"; private static final String INVALID_GUESS = "47"; /** * The component-under-test (CuT). * * <p> * This is a state-less component so we only need one. */ private PostGuessRoute CuT = new PostGuessRoute(new GameCenter());

15

The friendly GameCenter is injected into the CuT object using constructor-injection.

Import Mockito functions.

Page 16: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

We'll use mocks for the Spark components and create these new for each test run. public class PostGuessRouteTest { // attributes holding mock objects private Request request;

private Session session;

private Response response;

/**

* Setup new mock objects for each test.

*/

@Before

public void setup() {

request = mock(Request.class);

session = mock(Session.class);

when(request.session()).thenReturn(session);

final GuessGame game = new GuessGame(NUMBER);

when(session.attribute(eq(GameCenter.GAME_ID))).thenReturn(game);

response = mock(Response.class);

}

16

Use the Mockito mock function to create a mock object, eg, for a Spark Request object.

Use the Mockito when and thenReturn APIs to simulate a method call on a mock object.

Page 17: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Configure the mock object to direct the scenario of the test. public class PostGuessRouteTest { @Test public void bad_guess_1() {

// Setup the test scenario: The user's guess is not valid number. when(request.queryParams(eq(GuessPostRoute.GUESS_PARAM))) .thenReturn(NOT_A_NUMBER); // Invoke the test final ModelAndView result = CuT.handle(request, response); // Analyze the results: // * result is non-null assertNotNull(result); // * model is a non-null Map final Object model = result.getModel(); assertNotNull(model); assertTrue(model instanceof Map); // * model contains all necessary View-Model data @SuppressWarnings("unchecked") final Map<String, Object> vm = (Map<String, Object>) model; assertEquals(GameGetRoute.TITLE, vm.get(HomeGetRoute.TITLE_ATTR)); assertEquals(Boolean.FALSE, vm.get(HomeGetRoute.NEW_SESSION_ATTR)); assertEquals(GuessPostRoute.ERROR_TYPE, vm.get(GuessPostRoute.MESSAGE_TYPE_ATTR)); assertEquals(GuessPostRoute.makeBadArgMessage(NOT_A_NUMBER), vm.get(GuessPostRoute.MESSAGE_ATTR)); // * test view name assertEquals(GameGetRoute.VIEW_NAME, result.getViewName()); }

17

For this specific test case, we also need to specify the Request 'guess' query parameter.

Page 18: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Mockito has a rich API for setting scenarios and for inspecting test activity.

§ Arranging scenarios: • when(mock.method(args)).thenReturn(value) • when(mock.method(args)).thenThrow(new XyzExc()) • when(mock.method(args)).thenAnswer(lambda)

§ Inspecting activity within the CuT method: • verify(mock).method(args) • verify(mock, times(1)).method(args) •  Other verification modes: w times(n), atLeast(n), atMost(n) & never()

§ Specifying arguments: •  An actual value or object: eq(value) or eq(object) •  Any value (anyInt(), etc); any() for any Object •  Many more types of Matchers

18

Page 19: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Your exercise is to build unit tests for two classes in your Project.

ü Each team member picks two classes in your project and builds unit tests for them. •  Each team member will pick different classes to test. •  If you need to refactor the code to make the component

more testable then do so. ü Create the test class in the appropriate package in

the test source path. ü Create a reasonable set of test cases.

•  At least three test cases for each CuT. •  Focus on the business logic methods (eg, the handle

method for UI Controllers).

19

Page 20: Unit Tes2ng Ac2vity - se.rit.eduswen-610/activities/Unit Testing Activity.pdf · The structure of a unit test class. § Name the test class after the component under test (CuT) in

Your exercise is to build unit tests for two classes in your Project.

ü Upload the two unit test source files to the Unit Testing Dropbox in MyCourses.

ü Add this to your Definition of Done checklist in Story template card in Trello: •  Create unit tests.

20