diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/.gitignore b/runner/AndroidTestOrchestratorWithTestCoverageSample/.gitignore new file mode 100644 index 000000000..03eee2ade --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +.idea +*.iml +.DS_Store +build diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/README.md b/runner/AndroidTestOrchestratorWithTestCoverageSample/README.md new file mode 100644 index 000000000..42e9df801 --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/README.md @@ -0,0 +1,25 @@ +# AndroidTestOrchestrator with test coverage sample + +The Android Test Orchestrator allows you to run each of your app's tests in isolation, enabling greater reliability. +See https://developer.android.com/training/testing/junit-runner#using-android-test-orchestrator for more background. + +This sample is a subset of the AndroidJUnitRunner sample, but it +illustrates how to enable Jacoco test coverage report with the Android Test Orchestrator in the app/build.gradle file. + +This project uses the Gradle build system. You don't need an IDE to build and execute it but Android Studio is recommended. + +1. Download the project code, preferably using `git clone`. +1. Open the Android SDK Manager (*Tools* Menu | *Android*). +1. In Android Studio, select *File* | *Open...* and point to the top-level `./build.gradle` file. +1. Check out the relevant code: + * The application under test is located in `src/main/java` + * Tests are in `src/androidTest/java` +1. Connect a device or start an emulator: + * Turn animations off. + (On your device, under Settings->Developer options disable the following 3 settings: "Window animation scale", "Transition animation scale" and "Animator duration scale") +1. Run the newly created configuration. + +The application will be started on the device/emulator and a series of actions will be performed automatically. + +If you are using Android Studio, the *Run* window will show the test results. +The test coverage report will be generated in `app/build/reports/coverage/androidTest/debug/index.html`. diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/build.gradle b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/build.gradle new file mode 100644 index 000000000..2260e792f --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 31 + buildToolsVersion rootProject.buildToolsVersion + defaultConfig { + applicationId "com.example.android.testing.androidtestorchestratorsample" + minSdkVersion 14 + targetSdkVersion 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments clearPackageData: 'true' + testInstrumentationRunnerArguments useTestStorageService: 'true' + } + buildTypes { + debug { + testCoverageEnabled true + } + } + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } +} + +dependencies { + // App's dependencies, including test + implementation 'androidx.annotation:annotation:' + rootProject.androidxAnnotationVersion + implementation 'com.google.guava:guava:' + rootProject.guavaVersion + + // Testing-only dependencies + androidTestImplementation 'androidx.test:core:' + rootProject.coreVersion + androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion + androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion + androidTestImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion + androidTestUtil 'androidx.test:orchestrator:' + rootProject.runnerVersion + androidTestUtil 'androidx.test.services:test-services:' + rootProject.testServicesVersion +} diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorAddParameterizedTest.java b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorAddParameterizedTest.java new file mode 100644 index 000000000..63c9e7146 --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorAddParameterizedTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.testing.androidtestorchestratorsample; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import androidx.test.filters.SmallTest; + +import java.lang.Iterable; +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.runners.Parameterized.Parameters; + + +/** + * JUnit4 tests for the calculator's add logic. + * + *

This test uses a Junit4s Parameterized tests features which uses annotations to pass + * parameters into a unit test. The way this works is that you have to use the {@link Parameterized} + * runner to run your tests. + *

+ */ +@RunWith(Parameterized.class) +@SmallTest +public class CalculatorAddParameterizedTest { + + /** + * @return {@link Iterable} that contains the values that should be passed to the constructor. + * In this example we are going to use three parameters: operand one, operand two and the + * expected result. + */ + @Parameters + public static Iterable data() { + return Arrays.asList(new Object[][]{ + {0, 0, 0}, + {0, -1, -1}, + {2, 2, 4}, + {8, 8, 16}, + {16, 16, 32}, + {32, 0, 32}, + {64, 64, 128}}); + } + + private final double mOperandOne; + private final double mOperandTwo; + private final double mExpectedResult; + + private Calculator mCalculator; + + /** + * Constructor that takes in the values specified in + * {@link CalculatorAddParameterizedTest#data()}. The values need to be saved to fields in order + * to reuse them in your tests. + */ + public CalculatorAddParameterizedTest(double operandOne, double operandTwo, + double expectedResult) { + + mOperandOne = operandOne; + mOperandTwo = operandTwo; + mExpectedResult = expectedResult; + } + + @Before + public void setUp() { + mCalculator = new Calculator(); + } + + @Test + public void testAdd_TwoNumbers() { + double resultAdd = mCalculator.add(mOperandOne, mOperandTwo); + assertThat(resultAdd, is(equalTo(mExpectedResult))); + } +} \ No newline at end of file diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorInstrumentationTest.java b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorInstrumentationTest.java new file mode 100644 index 000000000..80b31bae5 --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/androidTest/java/com/example/android/testing/androidtestorchestratorsample/CalculatorInstrumentationTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.testing.androidtestorchestratorsample; + +import junit.framework.TestSuite; + +import org.junit.Before; +import org.junit.Test; +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.runner.RunWith; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnitRunner; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +/** + * JUnit4 Ui Tests for {@link CalculatorActivity} using the {@link AndroidJUnitRunner} with the + * Android Test Orchestrator. + * This class uses the JUnit4 syntax for tests. + *

+ * With the new AndroidJUnit runner you can run both JUnit3 and JUnit4 tests in a single test + * suite. The {@link AndroidRunnerBuilder} which extends JUnit's + * {@link AllDefaultPossibilitiesBuilder} will create a single {@link + * TestSuite} from all tests and run them. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class CalculatorInstrumentationTest { + + /** + * Use {@link ActivityScenario} to create and launch of the activity. + */ + @Before + public void launchActivity() { + ActivityScenario.launch(CalculatorActivity.class); + } + + @Test + public void noOperandShowsComputationError() { + final String expectedResult = getApplicationContext().getString(R.string.computationError); + onView(withId(R.id.operation_add_btn)).perform(click()); + onView(withId(R.id.operation_result_text_view)).check(matches(withText(expectedResult))); + } + + @Test + public void typeOperandsAndPerformAddOperation() { + performOperation(R.id.operation_add_btn, "16.0", "16.0", "32.0"); + } + + @Test + public void typeOperandsAndPerformSubOperation() { + performOperation(R.id.operation_sub_btn, "32.0", "16.0", "16.0"); + } + + @Test + public void typeOperandsAndPerformDivOperation() { + performOperation(R.id.operation_div_btn, "128.0", "16.0", "8.0"); + } + + @Test + public void divZeroForOperandTwoShowsError() { + final String expectedResult = getApplicationContext().getString(R.string.computationError); + performOperation(R.id.operation_div_btn, "128.0", "0.0", expectedResult); + } + + @Test + public void typeOperandsAndPerformMulOperation() { + performOperation(R.id.operation_mul_btn, "16.0", "16.0", "256.0"); + } + + private void performOperation(int btnOperationResId, String operandOne, + String operandTwo, String expectedResult) { + // Type the two operands in the EditText fields + onView(withId(R.id.operand_one_edit_text)).perform(typeText(operandOne), + closeSoftKeyboard()); + onView(withId(R.id.operand_two_edit_text)).perform(typeText(operandTwo), + closeSoftKeyboard()); + + // Click on a given operation button + onView(withId(btnOperationResId)).perform(click()); + + // Check the expected test is displayed in the Ui + onView(withId(R.id.operation_result_text_view)).check(matches(withText(expectedResult))); + } + +} diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/AndroidManifest.xml b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..f3b473cae --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/Calculator.java b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/Calculator.java new file mode 100644 index 000000000..b7a17d12e --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/Calculator.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.testing.androidtestorchestratorsample; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A simple calculator with a basic set of operations. + */ +public class Calculator { + + public enum Operator {ADD, SUB, DIV, MUL} + + /** + * Addition operation + */ + public double add(double firstOperand, double secondOperand) { + return firstOperand + secondOperand; + } + + /** + * Substract operation + */ + public double sub(double firstOperand, double secondOperand) { + return firstOperand - secondOperand; + } + + /** + * Divide operation + */ + public double div(double firstOperand, double secondOperand) { + checkArgument(secondOperand != 0, "secondOperand must be != 0, you cannot divide by zero"); + return firstOperand / secondOperand; + } + + /** + * Multiply operation + */ + public double mul(double firstOperand, double secondOperand) { + + return firstOperand * secondOperand; + } +} diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/CalculatorActivity.java b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/CalculatorActivity.java new file mode 100644 index 000000000..d745a3210 --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/java/com/example/android/testing/androidtestorchestratorsample/CalculatorActivity.java @@ -0,0 +1,138 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.testing.androidtestorchestratorsample; + +import android.app.Activity; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +/** + * {@link android.app.Activity} which contains a simple calculator. Numbers can be entered in the + * two {@link EditText} fields and result can be obtained by pressing one of the + * operation {@link Button}s at the bottom. + */ +public class CalculatorActivity extends Activity { + + private static final String TAG = "CalculatorActivity"; + + private Calculator mCalculator; + + private EditText mOperandOneEditText; + private EditText mOperandTwoEditText; + + private TextView mResultTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_calculator); + mCalculator = new Calculator(); + mResultTextView = (TextView) findViewById(R.id.operation_result_text_view); + mOperandOneEditText = (EditText) findViewById(R.id.operand_one_edit_text); + mOperandTwoEditText = (EditText) findViewById(R.id.operand_two_edit_text); + } + + /** + * OnClick method that is called when the add {@link Button} is pressed. + */ + public void onAdd(View view) { + compute(Calculator.Operator.ADD); + } + + /** + * OnClick method that is called when the substract {@link Button} is pressed. + */ + public void onSub(View view) { + compute(Calculator.Operator.SUB); + } + + /** + * OnClick method that is called when the divide {@link Button} is pressed. + */ + public void onDiv(View view) { + try { + compute(Calculator.Operator.DIV); + } catch (IllegalArgumentException iae) { + Log.e(TAG, "IllegalArgumentException", iae); + mResultTextView.setText(getString(R.string.computationError)); + } + } + + /** + * OnClick method that is called when the multiply {@link Button} is pressed. + */ + public void onMul(View view) { + compute(Calculator.Operator.MUL); + } + + private void compute(Calculator.Operator operator) { + double operandOne; + double operandTwo; + try { + operandOne = getOperand(mOperandOneEditText); + operandTwo = getOperand(mOperandTwoEditText); + } catch (NumberFormatException nfe) { + Log.e(TAG, "NumberFormatException", nfe); + mResultTextView.setText(getString(R.string.computationError)); + return; + } + + String result; + switch (operator) { + case ADD: + result = String.valueOf(mCalculator.add(operandOne, operandTwo)); + break; + case SUB: + result = String.valueOf(mCalculator.sub(operandOne, operandTwo)); + break; + case DIV: + result = String.valueOf(mCalculator.div(operandOne, operandTwo)); + break; + case MUL: + result = String.valueOf(mCalculator.mul(operandOne, operandTwo)); + break; + default: + result = getString(R.string.computationError); + break; + } + mResultTextView.setText(result); + } + + /** + * @return the operand value which was entered in an {@link EditText} as a double + */ + private static Double getOperand(EditText operandEditText) { + String operandText = getOperandText(operandEditText); + return Double.valueOf(operandText); + } + + /** + * @return the operand text which was entered in an {@link EditText}. + */ + private static String getOperandText(EditText operandEditText) { + String operandText = operandEditText.getText().toString(); + if (TextUtils.isEmpty(operandText)) { + throw new NumberFormatException("operand cannot be empty!"); + } + return operandText; + } +} diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-hdpi/ic_launcher.png b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100755 index 000000000..965ac9624 Binary files /dev/null and b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-mdpi/ic_launcher.png b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100755 index 000000000..df52c5278 Binary files /dev/null and b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xhdpi/ic_launcher.png b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100755 index 000000000..3d479bfe7 Binary files /dev/null and b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100755 index 000000000..c85d90b05 Binary files /dev/null and b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/layout/activity_calculator.xml b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/layout/activity_calculator.xml new file mode 100644 index 000000000..60f5e3565 --- /dev/null +++ b/runner/AndroidTestOrchestratorWithTestCoverageSample/app/src/main/res/layout/activity_calculator.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + +