Merge pull request #12 from hmalik144/testing_for_sumtest

Testing for sumtest
This commit is contained in:
2023-02-27 18:38:57 +00:00
committed by GitHub
13 changed files with 247 additions and 48 deletions

View File

@@ -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.

View File

@@ -43,6 +43,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.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'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

View File

@@ -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

View 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 |

View File

@@ -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 }"),
}

View File

@@ -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)))
}

View File

@@ -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)
}
}

View File

@@ -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")
}
}

View File

@@ -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)
}
}

View File

@@ -5,7 +5,7 @@ import io.cucumber.junit.CucumberOptions
@CucumberOptions(
features = ["features"],
tags = ["@sample_test"],
tags = ["@calculator_test"],
glue = ["com.example.sumtest.steps"]
)
class CucumberRunner

View File

@@ -8,14 +8,19 @@ import android.view.View.VISIBLE
import android.view.inputmethod.InputMethodManager
import android.view.inputmethod.InputMethodManager.HIDE_NOT_ALWAYS
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.test.espresso.IdlingResource
import com.example.sumtest.databinding.ActivityMainBinding
import com.example.sumtest.utils.BasicIdlingResource
import kotlinx.coroutines.launch
class SumActivity : AppCompatActivity() {
// The Idling Resource which will be null in production.
private var mIdlingResource: BasicIdlingResource? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -53,6 +58,10 @@ class SumActivity : AppCompatActivity() {
binding.secondNumber.removeTextChangedListener(textChangedListener)
binding.secondNumber.text = null
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)
.hideSoftInputFromWindow(currentFocus?.windowToken, HIDE_NOT_ALWAYS)
mIdlingResource?.setIdleState(false)
}
binding.firstNumber.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
}
}

View File

@@ -3,6 +3,7 @@ package com.example.sumtest
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.ViewModel
import com.example.sumtest.utils.BasicIdlingResource
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

View File

@@ -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()
}
}
}