COMP 313 / 413: Intermediate Object-Oriented Development
Dr. Robert Yacobellis Senior Lecturer Department of Computer Science
Week 10 Topics
} More About Project 4 – only if not complete in Week 9 ◦ Group Exercise: create a Project 4 extended state diagram
◦ Note: save a copy for your team’s Project 4 submission!!
} Testing in clickcounter and stopwatch
} Possibly time to work on Project 4 in your Groups
Week 10 Topics
} More About Project 4
◦ Some Project 4 details
◦ Group Exercise: create a Project 4 extended state diagram
} Testing in clickcounter and stopwatch
} Possibly time to work on Project 4 in your Groups
Android Testing Fundamentals
} Android recommends TDD and iterative development
◦ When developing a feature iteratively, you start by either writing a new test or by adding cases and assertions to an existing unit test. The test fails at first because the feature isn't implemented yet:
◦ An IntelliJ IDEA project typically contains two test directories: androidTest that contains tests that run on a device or emulator, and test that contains (unit) tests that run on your local machine
– Some of these may use mock objects or “test doubles” – Unit tests may use Robolectric to run on a normal JVM
Android Testing Framework
} Key Android testing framework features ◦ As you know, Android test suites are based on JUnit
– A JUnit test is a method that tests part of the application
◦ Android JUnit extensions provide component-specific test case classes,
that include
– Helper methods for creating mock objects
– Methods that help control a component’s life cycle
– See
◦ Test suites are contained in test packages like applications
– An app called com.mydomain.myapp has a test package name
com.mydomain.myapp.test
– Its tests are run by a test runner, eg, the androidx AndroidJUnitRunner ◦ Testing tools are available in IDEs and in command-line form
Android Testing Framework
Run on a local JVM Run on an emulator or Android hardware
Instrumented versus Local Tests
Android Test Case Classes
This is now deprecated
IntelliJ IDEA Test Structure
} In IntelliJ IDEA Projects the “source sets” for source and device test code live in src/main and src/androidTest, respectively
◦ Java source code and resources live in java and res sub- directories of these Project directories
– clickcounter & stopwatch Project tests don’t have resources
◦ This configuration is managed in the Project’s build.gradle file
–
} You can run these Android tests on a device or emulator via the Android Debug Bridge (ADB) by right-clicking app in ClickCounter or Stopwatch in the Project view and selecting Run 'All Tests' or by changing to the Android view and running androidTest
◦ Note: shapes-android-java did not have Android test code 9
Robolectric Project Test Structure
} Robolectric allows Android Activities to be unit tested in a Java Virtual Machine as opposed to on a device or emulator
} Robolectric dependencies in IntelliJ IDEA are also specified in the build.gradle file, and test source files live in a deep sub-directory of src/test/java
◦ The sub-directory structure matches the package structure in src/main/java (in Projects stopwatch, clickcounter, shapes)
} All directories under src/test/java are visited when you run gradlew testDebug in Terminal or run test in the Android view
◦ Doing this runs the <ActivityName>Robolectric.java file, which extends the primary Activity test defined in src/androidTest
◦ Doing this also finds and runs all of the Test.java files in src/test/java; these files may extend unit test files in main/java
Android Activity Testing
} ActivityInstrumentationTestCase2<T> used to be used in Stopwatch, but has been deprecated in more recent versions of Android since Android 2.2 (API 8)
} Instead, AndroidX now supplies a Test Support Library with JUnit support up to JUnit 4.13 plus other features
◦ If you are creating an instrumented JUnit 4 test class to run on a device or emulator, your test class must be prefixed with the @RunWith(AndroidJUnit4.class) annotation and create an “AndroidTestRule” to connect tests with the class under test
◦ See the next slides for how stopwatch and clickcounter do this ◦ Note that the corresponding ActivityTestRule is also now
deprecated in Android – replaced by ActivityScenarioRule 11
Android Activity Testing
} Here’s stopwatch’s code to set up Android testing (this class lives in a subdirectory of src/androidTest)
@RunWith(AndroidJUnit4.class)
@SmallTest // see this website for more details
public class StopwatchActivityTest extends AbstractStopwatchActivityTest {
@Rule
public ActivityTestRule<StopwatchAdapter> activityRule =
new ActivityTestRule<>(StopwatchAdapter.class); // connect test to app Activity
@Override
protected StopwatchAdapter getActivity() {
return activityRule.getActivity(); // returns the Activity under test }
} //
stopwatch testing class diagram
Android Activity Testing
} Here’s clickcounter’s code to set up Android testing (this class lives in a subdirectory of src/androidTest)
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ClickCounterActivityTest extends AbstractClickCounterActivityTest {
@Rule
public ActivityTestRule<ClickCounterActivity> activityRule =
new ActivityTestRule<>(ClickCounterActivity.class); // connect test to app Activity
@Override
protected ClickCounterActivity getActivity() {
return activityRule.getActivity(); // returns the Activity under test }
} //
clickcounter testing class diagram
Android Testing Instrumentation
} You can invoke callback methods in test code that allow you to execute an Activity’s life cycle step by step
} Testing that an Activity saves and restores its state:
// Start the main activity of the application under test
mActivity = getActivity(); // getActivity() is a key method that’s part of the instrumentation API // in this case it causes onCreate() to be called the first time as the Activity is started
// Get a handle to the Activity object's main UI widget, a Spinner
mSpinner = (Spinner)mActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
// Set the Spinner to a known position mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
// Stop the activity - The onDestroy() method should save the state of the Spinner mActivity.finish(); // finish() causes onDestroy() to be invoked
// Re-start the Activity - the onResume() method should restore the state of the Spinner mActivity = getActivity(); // getActivity() will cause onResume() to be invoked
// Get the Spinner's current position
int currentPosition = mActivity.getSpinnerPosition();
// Assert that the current position is the same as the starting position assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
stopwatch UI Tests – androidTest
} StopwatchActivityTest.java in the src/androidTest directory delegates to AbstractStopwatchActivityTest.java in src/main; its test methods do not run in the UI thread, but instead use it:
◦ testActivityCheckTestCaseSetUpProperly – Activity launched OK
◦ testActivityScenarioRun performs these checks:
the initial displayed value is 0, and Start/Stop can be clicked after sleeping 5.5 seconds the displayed value is 5 àdisplayed value checks are run in the UI thread
◦ testActivityScenarioRunLapReset performs these checks: display is 0, press Start/Stop, wait 5+ seconds, expect time 5 press Reset/Lap, wait 4 seconds, expect time display still 5 press Start/Stop, expect time display still 5 (turns off Lap) press Reset/Lap, expect time display 9 (makes time visible) press Reset/Lap , expect time display 0
stopwatch-android-java
Reminder: stopwatch State Machine
stopwatch Unit Tests – gradlew testDebug 1
} gradlew testDebug runs StopwatchActivityRobolectic.java in src/test, which extends and runs all the tests in AbstractStopwatchActivityTest.java (above)
} Test.java in .../model/clock runs tests in DefaultClockModelTest: ◦ Both tests override the onTickListener to be an atomic int
◦ testStopped – after sleeping 5.5 seconds, that int is still 0
◦ testRunning – after sleeping 5.5 seconds the int is 5
} Test.java in .../model/time runs tests in DefaultTimeModelTest:
◦ testPreconditions – the model’s runtime == 0, laptime <= 0
◦ testIncrementRuntimeOne – times are correct after one tick
◦ testIncrementRuntimeMany – times are correct after one hour
◦ testLapTime – after 5 ticks the runtime is +5, laptime is +0; after 5 ticks after “clicking” Lap runtime is +10, laptime is +5
} Both Default...Tests delegate to corresponding Abstract...Tests 19
stopwatch Unit Tests – gradlew testDebug 2
} Test.java in .../model/state runs tests in DefaultStopwatchStateMachineTest (again, implemented in AbstractStopwatchStatemachineTest):
◦ There’s a dependency on an inner mock class that implements TimeModel, ClockModel, and StopwatchUIUpdateListener and replaces their methods with simple integer manipulations, etc.
◦ testPreconditions – tests that the initial state is Stopped
◦ testScenarioRunLapReset – verifies the following scenario: time is 0, press start, wait 5+ seconds, expect time 5, press lap, wait 4 seconds, expect time 5, press start, expect time 5, press lap, expect time 9, press lap, expect time 0
clickcounter UI Tests – androidTest
} ClickCounterActivityTest.java in the src/androidTest directory delegates to AbstractClickCounterActivityTest.java in src/main; its test methods run in the UI thread on a device or emulator:
◦ testActivityTestCaseSetUpProperly – Activity is launched OK
◦ testActivityScenarioIncReset performs these checks:
clicking Reset displays 0, enables Inc, and disables Dec clicking Inc displays 1; Inc, Dec, and Reset are all enabled clicking Reset again displays 0, Inc is enabled, Dec disabled
◦ testActivityScenarioIncUntilFull performs these checks: clicking Reset displays 0, enables Inc, and disables Dec clicking Inc repeatedly increments the display until full when display is full, Inc is disabled, Dec and Reset enabled
◦ testActivityScenarioRotation checks that the displayed value remains the same if the device is rotated
Implicit clickcounter State Machine
clickcounter Unit Tests – gradlew testDebug
} gradlew testDebug runs ClickCounterActivityRobolectic.java in src/test;
it extends and runs all the tests in AbstractClickCounterActivityTest.java (above)
} Test.java in test/.../misc/boundedcounter/model is also run by gradlew testDebug and runs all tests in SimpleBoundedCounterTest:
◦ testInitiallyAtMin – the counter is initially at its minimum value ◦ testIncrement – one is added to the displayed counter value
◦ testDecrement – one is subtracted from the max counter value ◦ testFullAtMax – the counter is at its maximum value when full
◦ testEmptyAtMin – the counter is at its minimum when emptied ◦ testPreconditions* – the initial counter has distinct max & min ◦ testGet* – the counter value is consistent across gets
◦ testIsFull* – the counter isn’t full if decremented at full
◦ testIsEmpty* – the counter isn’t empty if incremented at empty * inherited from AbstractCounterTest
Project 4 – Functional Requirements – Slide 1 of 2
} The timer has the following controls:
◦ One two-digit display of the form 88.
◦ One multi-function button. (Clicking the button causes a click event.)
} The timer behaves as follows (part 1 of 2):
◦ The timer always displays the remaining time in seconds.
◦ Initially, the timer is stopped and the (remaining) time is zero.
◦ If the button is pressed when the timer is stopped, the time is incremented by one up to a preset maximum of 99. (The button acts as an increment button.)
◦ If the time is greater than zero and three seconds elapse from the most recent time the button was pressed, then the timer beeps once and starts running. (When the remaining time is greater than 0 the clock model (see stopwatch) is used to send tick events to the state machine.)
Project 4 – Functional Requirements – Slide 2 of 2
} The timer behaves as follows (part 2 of 2):
◦ While running, the timer subtracts one from the time for every
second that elapses. (Caused by a clock model tick event.)
◦ If the timer is running and the button is pressed, the timer stops and the
time is reset to zero. (The button acts as a cancel button.)
◦ If the timer is running and the time reaches zero by itself (without the button being pressed), then the timer stops and the alarm starts beeping continually and indefinitely.
◦ If the alarm is sounding and the button is pressed, the alarm stops sounding; the timer is now stopped and the (remaining) time is zero. (The button acts as a stop button.)
◦ The timer handles rotation by continuing in its current state.
} Your group activity is to create a set of unit tests for Project 4; you do not need to create the tests, just a list of proposed tests
(do include rotation!)
Project 4 – Events
There are only two kinds of events in Project 4:
} Button Click events (always processed in all states)
} Clock Tick events (ignored in stopped and alarm states unless a Clock Tick is used to repeatedly play a “beep” for the alarm)
Project 4 Tests to Consider (not complete)
} From clickcounter (button clicking and displayed timer value):
◦ testActivityTestCaseSetUpProperly: Activity launched OK
◦ testActivityScenarioIncUntilFull: clicking Inc repeatedly
increments the display until full (the timer will display 99)
◦ testInitiallyAtMin: the counter is initially at its minimum (0)
◦ testIncrement: one is added to the (initial) counter value
◦ Optional: testGet: the counter value is consistent across gets
} From stopwatch (counting down or “running” the timer):
◦ testIncrementRuntimeOne: times are correct after one tick
◦ testLapTime: after 5 ticks the runtime is -5 (the displayed timer
value should be 5 less than its initial value which must be >= 5) } Items in italics need to be changed for the simple timer!
◦ Eg, counteràdisplayed time, incrementàclick, ... 27
Project 4 – Group Activity
} Your group activity is to create a set of unit tests for Project 4;
you do not need to create the tests, just a list of proposed tests
} If you haven’t finished your Project 4 (timer) extended state diagram, do that first so the unit tests will make more sense
} Submit both of these on Sakai by July 29 using the assignment ◦ They can be submitted by different people in your larger Group
} Do include a test that rotation saves the timer state in Project 4
} If there’s time left after you finish these, work on Project 4 in
your larger groups
Week 10 Topics
} More About Project 4
◦ Some Project 4 details
◦ Group Exercise: create a Project 4 extended state diagram
} Testing in clickcounter and stopwatch
} Possibly time to work on Project 4 in your Groups