mirror of
https://github.com/hmalik144/sumtest.git
synced 2025-12-10 03:05:27 +00:00
Test framework built
Edge cases establish and tested CircleCI integrated for better CI/CD operations Robot Framework implemented for automation testing
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
features = ["features"],
|
||||
tags = ["@sample_test"],
|
||||
tags = ["@calculator_test"],
|
||||
glue = ["com.example.sumtest.steps"]
|
||||
)
|
||||
class CucumberRunner
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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