mirror of
https://github.com/hmalik144/sumtest.git
synced 2025-12-10 03:05:27 +00:00
Merge pull request #12 from hmalik144/testing_for_sumtest
Testing for sumtest
This commit is contained in:
@@ -100,3 +100,6 @@ class SumTestSteps {
|
|||||||
....
|
....
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
As part of being an automation tester the project has been connected to CircleCI for CI/CD operations.
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ dependencies {
|
|||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||||
implementation 'androidx.activity:activity-ktx:1.6.1'
|
implementation 'androidx.activity:activity-ktx:1.6.1'
|
||||||
|
implementation 'androidx.test.espresso:espresso-idling-resource:3.5.1'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
Feature: Sample feature file
|
|
||||||
|
|
||||||
@sample_test
|
|
||||||
Scenario: Successful login
|
|
||||||
Given I start the application
|
|
||||||
|
|
||||||
@sample_test
|
|
||||||
Scenario: Unsuccessful login
|
|
||||||
Given I start the application and throw an exception
|
|
||||||
41
app/src/androidTest/assets/features/sumtest.feature
Normal file
41
app/src/androidTest/assets/features/sumtest.feature
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
Feature: Sum test feature to test the app for addition operations
|
||||||
|
|
||||||
|
@calculator_test
|
||||||
|
Scenario Outline: Run successful functions on the calculator
|
||||||
|
Given I start the application
|
||||||
|
When I run calculator sum for values "<First value>" and "<Second value>"
|
||||||
|
And I assert the operation has run successfully with result "<Sum>"
|
||||||
|
Examples:
|
||||||
|
| First value | Second value | Sum |
|
||||||
|
| 5 | 5 | 10 |
|
||||||
|
| -231 | -5 | -236 |
|
||||||
|
| 999 | 1 | 1000 |
|
||||||
|
| 1000 | 1000 | 2000 |
|
||||||
|
| -1000 | -1000 | -2000 |
|
||||||
|
|
||||||
|
@calculator_test
|
||||||
|
Scenario Outline: Run unsuccessful functions on the calculator
|
||||||
|
Given I start the application
|
||||||
|
When I run calculator sum for values "<First value>" and "<Second value>"
|
||||||
|
And I assert the operation has failed with error message <Error Type>
|
||||||
|
Examples:
|
||||||
|
| First value | Second value | Error Type |
|
||||||
|
| 2200 | 500 | Overflow |
|
||||||
|
| t44 | -5 | InvalidInput |
|
||||||
|
| | 10 | EmptyInput |
|
||||||
|
| 1! | 44 | InvalidInput |
|
||||||
|
| 1.2 | -5 | InvalidInput |
|
||||||
|
| -2200 | -150 | Overflow |
|
||||||
|
| 1,200 | 120 | InvalidInput |
|
||||||
|
|
||||||
|
@calculator_test
|
||||||
|
Scenario Outline: Run functions for integer limitation on the calculator
|
||||||
|
Given I start the application
|
||||||
|
When I run calculator sum for values "<First value>" and "<Second value>"
|
||||||
|
And I assert the operation has failed with error message <Error Type>
|
||||||
|
Examples:
|
||||||
|
| First value | Second value | Error Type |
|
||||||
|
| 2147483647 | 10 | Overflow |
|
||||||
|
| 2147483637 | 10 | Overflow |
|
||||||
|
| -2147483648 | -10 | Overflow |
|
||||||
|
| -2147483638 | 10 | Overflow |
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.example.sumtest.constants
|
||||||
|
|
||||||
|
import com.example.sumtest.Result
|
||||||
|
|
||||||
|
enum class ErrorTypes(override val message: String): Result {
|
||||||
|
EmptyInput("One or more fields are empty"),
|
||||||
|
InvalidInput("Only integers are allowed"),
|
||||||
|
Overflow("Exception overflow error. NSOSStatusErrorDomain Code=-10817 \\\"(null)\\\" UserInfo={_LSFunction=_LSSchemaConfigureForStore, ExpectedSimulatorHash={length = 32, bytes = 0xa9298a34 dc614504 8992eb3c f65c237f ... ff5133c6 37c50886 }"),
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.example.sumtest.robots
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.ViewInteraction
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||||
|
|
||||||
|
|
||||||
|
open class BaseTestRobot {
|
||||||
|
|
||||||
|
fun fillEditText(@IdRes resId: Int, text: String): ViewInteraction =
|
||||||
|
onView(withId(resId)).perform(
|
||||||
|
ViewActions.replaceText(text),
|
||||||
|
ViewActions.closeSoftKeyboard()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun clickButton(@IdRes resId: Int): ViewInteraction =
|
||||||
|
onView((withId(resId))).perform(ViewActions.click())
|
||||||
|
|
||||||
|
fun getViewInteraction(@IdRes resId: Int): ViewInteraction = onView(withId(resId))
|
||||||
|
|
||||||
|
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
||||||
|
.check(matches(withText(text)))
|
||||||
|
|
||||||
|
fun matchText(@IdRes resId: Int, text: String): ViewInteraction =
|
||||||
|
matchText(getViewInteraction(resId), text)
|
||||||
|
|
||||||
|
fun getStringFromResource(@StringRes resId: Int): String =
|
||||||
|
Resources.getSystem().getString(resId)
|
||||||
|
|
||||||
|
fun checkVisibility(@IdRes resId: Int, visibility: Visibility) =
|
||||||
|
getViewInteraction(resId).check(matches(withEffectiveVisibility(visibility)))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.example.sumtest.robots
|
||||||
|
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.Visibility.*
|
||||||
|
import com.example.sumtest.R
|
||||||
|
|
||||||
|
fun sumTest(func: SumTestRobot.() -> Unit) = SumTestRobot().apply { func() }
|
||||||
|
class SumTestRobot: BaseTestRobot() {
|
||||||
|
|
||||||
|
fun enterSumValues(firstNumber: String, secondNumber: String) {
|
||||||
|
fillEditText(R.id.firstNumber, firstNumber)
|
||||||
|
fillEditText(R.id.secondNumber, secondNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitEntries() {
|
||||||
|
clickButton(R.id.cta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitValuesForSum(firstNumber: String, secondNumber: String){
|
||||||
|
enterSumValues(firstNumber, secondNumber)
|
||||||
|
submitEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkSumHasCalculated(result: String) {
|
||||||
|
// Results is displayed and error is not
|
||||||
|
checkVisibility(R.id.result, VISIBLE)
|
||||||
|
checkVisibility(R.id.error, GONE)
|
||||||
|
|
||||||
|
matchText(R.id.result, result)
|
||||||
|
// Edit texts are empty
|
||||||
|
matchText(R.id.firstNumber, "")
|
||||||
|
matchText(R.id.secondNumber, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkCalculationError(errorMessage: String) {
|
||||||
|
checkVisibility(R.id.result, GONE)
|
||||||
|
checkVisibility(R.id.error, VISIBLE)
|
||||||
|
|
||||||
|
matchText(R.id.error, errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.example.sumtest.steps
|
|
||||||
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.test.core.app.ActivityScenario
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import com.example.sumtest.SumActivity
|
|
||||||
import io.cucumber.java.Before
|
|
||||||
import io.cucumber.java.en.Given
|
|
||||||
import org.junit.Assert
|
|
||||||
|
|
||||||
class SampleSteps {
|
|
||||||
|
|
||||||
private lateinit var mActivityScenarioRule: ActivityScenario<*>
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
mActivityScenarioRule = ActivityScenario.launch(SumActivity::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Given("I start the application")
|
|
||||||
fun i_start_the_application() {
|
|
||||||
mActivityScenarioRule.moveToState(Lifecycle.State.STARTED)
|
|
||||||
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
Assert.assertEquals("com.example.sumtest", appContext.packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Given("I start the application and throw an exception")
|
|
||||||
fun i_start_the_application_and_throw_an_exception() {
|
|
||||||
mActivityScenarioRule.moveToState(Lifecycle.State.STARTED)
|
|
||||||
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
Assert.assertEquals("com.example.sumtest", appContext.packageName)
|
|
||||||
|
|
||||||
throw Exception("This is an exception")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.sumtest.steps
|
||||||
|
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.Lifecycle.State.RESUMED
|
||||||
|
import androidx.test.core.app.ActivityScenario
|
||||||
|
import androidx.test.espresso.IdlingRegistry
|
||||||
|
import androidx.test.espresso.IdlingResource
|
||||||
|
import com.example.sumtest.SumActivity
|
||||||
|
import com.example.sumtest.constants.ErrorTypes
|
||||||
|
import com.example.sumtest.robots.sumTest
|
||||||
|
import io.cucumber.java.After
|
||||||
|
import io.cucumber.java.Before
|
||||||
|
import io.cucumber.java.en.And
|
||||||
|
import io.cucumber.java.en.Given
|
||||||
|
import io.cucumber.java.en.When
|
||||||
|
|
||||||
|
class SumTestSteps {
|
||||||
|
private lateinit var mActivityScenarioRule: ActivityScenario<SumActivity>
|
||||||
|
private lateinit var mIdlingResource: IdlingResource
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mActivityScenarioRule = ActivityScenario.launch(SumActivity::class.java)
|
||||||
|
mActivityScenarioRule.onActivity {
|
||||||
|
mIdlingResource = it.getIdlingResource()!!
|
||||||
|
IdlingRegistry.getInstance().register(mIdlingResource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Given("I start the application")
|
||||||
|
fun i_start_the_application() {
|
||||||
|
mActivityScenarioRule.moveToState(RESUMED)
|
||||||
|
}
|
||||||
|
|
||||||
|
@When("^I run calculator sum for values \"([^\"]*)\" and \"([^\"]*)\"$")
|
||||||
|
fun i_run_calculator_sum_for_values(firstValue: String, secondValue: String) {
|
||||||
|
sumTest {
|
||||||
|
submitValuesForSum(firstValue, secondValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@And("^I assert the operation has run successfully with result \"([^\"]*)\"\$")
|
||||||
|
fun i_assert_the_operation_has_run_successfully(result: String) {
|
||||||
|
sumTest {
|
||||||
|
checkSumHasCalculated(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@And("^I assert the operation has failed with error message (Overflow|InvalidInput|EmptyInput)$")
|
||||||
|
fun i_assert_the_operation_has_failed_with_error_message(result: ErrorTypes) {
|
||||||
|
sumTest {
|
||||||
|
checkCalculationError(result.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun unregisterIdlingResource() {
|
||||||
|
IdlingRegistry.getInstance().unregister(mIdlingResource)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import io.cucumber.junit.CucumberOptions
|
|||||||
|
|
||||||
@CucumberOptions(
|
@CucumberOptions(
|
||||||
features = ["features"],
|
features = ["features"],
|
||||||
tags = ["@sample_test"],
|
tags = ["@calculator_test"],
|
||||||
glue = ["com.example.sumtest.steps"]
|
glue = ["com.example.sumtest.steps"]
|
||||||
)
|
)
|
||||||
class CucumberRunner
|
class CucumberRunner
|
||||||
@@ -8,14 +8,19 @@ import android.view.View.VISIBLE
|
|||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.view.inputmethod.InputMethodManager.HIDE_NOT_ALWAYS
|
import android.view.inputmethod.InputMethodManager.HIDE_NOT_ALWAYS
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.test.espresso.IdlingResource
|
||||||
import com.example.sumtest.databinding.ActivityMainBinding
|
import com.example.sumtest.databinding.ActivityMainBinding
|
||||||
|
import com.example.sumtest.utils.BasicIdlingResource
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SumActivity : AppCompatActivity() {
|
class SumActivity : AppCompatActivity() {
|
||||||
|
// The Idling Resource which will be null in production.
|
||||||
|
private var mIdlingResource: BasicIdlingResource? = null
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -53,6 +58,10 @@ class SumActivity : AppCompatActivity() {
|
|||||||
binding.secondNumber.removeTextChangedListener(textChangedListener)
|
binding.secondNumber.removeTextChangedListener(textChangedListener)
|
||||||
binding.secondNumber.text = null
|
binding.secondNumber.text = null
|
||||||
binding.secondNumber.addTextChangedListener(textChangedListener)
|
binding.secondNumber.addTextChangedListener(textChangedListener)
|
||||||
|
mIdlingResource?.setIdleState(true)
|
||||||
|
}
|
||||||
|
if (it.error != null) {
|
||||||
|
mIdlingResource?.setIdleState(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,9 +74,21 @@ class SumActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
(getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
|
(getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
|
||||||
.hideSoftInputFromWindow(currentFocus?.windowToken, HIDE_NOT_ALWAYS)
|
.hideSoftInputFromWindow(currentFocus?.windowToken, HIDE_NOT_ALWAYS)
|
||||||
|
mIdlingResource?.setIdleState(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.firstNumber.addTextChangedListener(textChangedListener)
|
binding.firstNumber.addTextChangedListener(textChangedListener)
|
||||||
binding.secondNumber.addTextChangedListener(textChangedListener)
|
binding.secondNumber.addTextChangedListener(textChangedListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only called from test, creates and returns a new [BasicIdlingResource].
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
fun getIdlingResource(): IdlingResource? {
|
||||||
|
if (mIdlingResource == null) {
|
||||||
|
mIdlingResource = BasicIdlingResource()
|
||||||
|
}
|
||||||
|
return mIdlingResource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.example.sumtest
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.example.sumtest.utils.BasicIdlingResource
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.example.sumtest.utils
|
||||||
|
|
||||||
|
import androidx.test.espresso.IdlingResource
|
||||||
|
import androidx.test.espresso.IdlingResource.ResourceCallback
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
class BasicIdlingResource : IdlingResource {
|
||||||
|
|
||||||
|
private lateinit var mCallback: ResourceCallback
|
||||||
|
|
||||||
|
// Idleness is controlled with this boolean.
|
||||||
|
private val mIsIdleNow: AtomicBoolean = AtomicBoolean(true)
|
||||||
|
|
||||||
|
override fun getName(): String = this.javaClass.name
|
||||||
|
|
||||||
|
override fun isIdleNow(): Boolean = mIsIdleNow.get()
|
||||||
|
|
||||||
|
override fun registerIdleTransitionCallback(callback: ResourceCallback) {
|
||||||
|
mCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new idle state, if isIdleNow is true, it pings the [ResourceCallback].
|
||||||
|
* @param isIdleNow false if there are pending operations, true if idle.
|
||||||
|
*/
|
||||||
|
fun setIdleState(isIdleNow: Boolean) {
|
||||||
|
mIsIdleNow.set(isIdleNow)
|
||||||
|
if (isIdleNow) {
|
||||||
|
mCallback.onTransitionToIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user