Run espresso test multiple times, the right way - android

I am working with Espresso in android at the moment, and I have this test, which is clicking a button, populating my listView and then logging the execution time of the action.
#RunWith(AndroidJUnit4::class)
class Test {
#get:Rule
val mActivityActivityTestRule = ActivityTestRule(MainActivity::class.java)
#Test
#Throws(java.lang.Exception::class)
fun test_fetchData_populates_listView() {
//Click the get_rec_articles button.
onView(withId(R.id.get_rec_articles_btn)).perform(click())
val start = System.currentTimeMillis()
//check if the listView is created.
onView(withId(R.id.rec_articles_listview)).check(matches(isDisplayed()))
//check if items can be clicked.
onData(CoreMatchers.anything()).inAdapterView(withId(R.id.rec_articles_listview)).atPosition(0).perform(click())
val end = System.currentTimeMillis()
val loadTime = (end - start)
Log.d("###### LOADTIME FOR FETCHDATA FUNCTION:", "" + loadTime + " MILLISECS ######")
}
}
I would like to find a good way to call the test 50 times and then get the average load time. I could just run it manually 50 times and then calculate it, but I have several tests which I would like the average load times from.
I have read the question on the site about the same topic and tried the suggested solutions, but it didn't help.

I had a very similar issue and as a result I've created a library to run Android UI tests multiple times. Might be useful in your case: https://github.com/stepstone-tech/AndroidTestXRunner
As far as the load time goes, after running tests there's a JUnit report created with times of each test being placed in a generated JUNit report XML. You could get those values and calculate an average based on that.

Related

Unit test can not be achieved in android studio using kotlin

In android studio using kotlin, I created a class with the code below:
class Dice(val numSides:Int){
fun roll():Int{
return (1..numSides).random()
}
}
in ExampleUnitTest.kt file I have created the following code to test:
class ExampleUnitTest {
#Test
fun generates_number() {
val dice = Dice(4)
val rollResult = dice.roll()
assertTrue("The value of rollResult was not between 1 and 6", rollResult in 1..6)
}
}
The test should be passed only if Dice(6) as the result should be any number between 1 to 6 but as shown above when with Dice(4) or any number, the test passed. that why? thanks
Your test checks if the rolled number is between 1 and 6. With Dice(4) the result will be between 1 and 4, which means it's always going to be between 1 and 6 as well. Or to put it another way, it's impossible for you to get a value from Dice(4).roll() that isn't somewhere between 1 and 6 - it's never going to be 5 or 6, but that doesn't contradict anything!
What you probably want to test is whether Dice(4).roll()'s possible range of outputs is from 1 to 6. But there's no way to absolutely prove that from the outside, not the way the class is written anyway. All you can do is provide a value for numSides and then call roll() and check the output. That's all the class offers to you in terms of interactivity.
A better question is why you want to test this? Your test seems to be written to fail when your code is working - you should be testing that the behaviour is what you'd expect, so all your tests pass. So really, what you'd want to test is that Dice(4).roll() only produces values within the expected range, i.e. between 1 and 4.
Since it's random the only way to really do this is run the same test lots of times and make sure a bad number never comes up (or you eventually see a number you want), so you can say with a high degree of confidence that it's probably correct. Something like
#Test
fun generates_number() {
val dice = Dice(4)
val allGood = generateSequence { dice.roll() }.take(9999).all { it in 1..4 }
assertTrue("At least one dice roll was not between 1 and 4", allGood)
}
(or you could use a for loop with break to exit early if one of the values isn't valid, instead of the sequence + all)
I just picked 9999 at random but you could use statistics to pick a more suitable number. But the problem here is the random behaviour - because it's not predictable, you can't do a simple state in -> result out test with an expected result.

Android coroutine job join confusion

so I've been looking at the coroutines for past few days and found quite interesting piece of code
val input = MutableStateFlow(5)
lifecycleScope.launch {
input.collectLatest {
val job = launch {
doSomething1()
delay(1000L)
doSomething2()
}
job.join()
}
}
I've been debugging it and im not sure why do we need that job.join in there, how I understand the whole flow right now:
Lets say we push fast 3 values to input, collectLatest will terminate previous unfinished box, but since we are launching new coroutine its job is not terminated, so after pushing fast 3 values we will always reach doSomething2 for each value, if we would skip launch inside collectLatest we would reach only one doSomething2 - but now coming to the job.join - it should suspend coroutine until the job is done, but since we are not doing anything else in this coroutine is it really needed? Does it give any value? From my tests it doesn't really matter if we have that job.join or not, what I am missing in here?

Espresso test fails when multiple match statements but passes with smaller match statements

I have an Instrument test that has four match statements in it. There is one of the four statements that fails when ran with the other three, but passes if it runs by itself.
here is the test that fails
#Test
fun displayTypeFilteredPokemon(){
// When - PokemonList fragment launch and filtered by specific type
launchActivity()
onView(withId(R.id.genAllButton)).perform(click())
// Perform click action to do the filter on specific type
onView(withId(R.id.menu_filter)).perform(click())
onView(withText(R.string.label_type_electric)).perform(click())
// Then - Verify the list is filtered by the selected type
onView(withId(R.id.pokemon_list)).check(RecyclerViewItemCountAssertion(3))
onView(withText("Magnemite")).check(matches(isDisplayed()))
onView(withText("Jolteon")).check(matches(isDisplayed()))
onView(withText("Emolga")).check(matches(isDisplayed()))
}
here is the code for launch activity:
private fun launchActivity(): ActivityScenario<PokemonListActivity>? {
val scenario = launch(PokemonListActivity::class.java)
scenario.onActivity {
val intent = Intent()
intent.putExtra("genId",0)
it.intent = intent
}
return scenario
}
And here is the custom matcher code:
class RecyclerViewItemCountAssertion(private val matcher: Int) : ViewAssertion {
override fun check(view: View?, noViewFoundException: NoMatchingViewException?) {
if(noViewFoundException != null){
throw noViewFoundException
}
val recyclerView = view as RecyclerView
val adapter = recyclerView.adapter!!
assertThat(adapter.itemCount, IsEqual(matcher))
}
}
when using this set of matches it passes:
onView(withId(R.id.pokemon_list)).check(RecyclerViewItemCountAssertion(3))
onView(withText("Jolteon")).check(matches(isDisplayed()))
onView(withText("Emolga")).check(matches(isDisplayed()))
or when this set of matches it passes as well:
onView(withText("Magnemite")).check(matches(isDisplayed()))
here is the view being tested:
I am slightly confused because the view clearly has the matching text there. Could it be the resource isn't idling, therefor the test just closes? For example the reason the test with only one matcher passes is due to being fast enough to match before it finishes?
I have thought about introducing EspressoIdlingResource but I have read that introduces difficulties in a code base and I would like to keep it simple for learning purposes.
Another indicator that I think the race condition is an issue is that when I debug the test it passes. When I just run the test it fails.
Edit 1:
when running all the test by itself, I have no errors. When running all the tests in the class I get the following error:
androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.stegner.androiddex:id/menu_filter
Your approach to writing espresso tests seems wrong.
While writing a UI test, what we want to confirm is the following.
"If user interacts clicks button X, swipes the page, writes 500 to this edittext, and clicks button Y, he will be able to see text Z".
As you can see, we are testing the app as if we are the end user and by just interacting with the elements we see in the screen.
private fun launchActivity(): ActivityScenario<PokemonListActivity>? {
val scenario = launch(PokemonListActivity::class.java)
scenario.onActivity {
val intent = Intent()
intent.putExtra("genId",0)
it.intent = intent
}
return scenario
}
When you use launchActivity and pass a data with an intent, you disregard what we want to test,end users can't pass data with intents, they just click to a button which triggers our code to pass the data with intents. So a good test, should be from end to end (from launch screen to the screen you want) with mimicking user's movements(click X, click Y, write 500 to Z etc)
Now your problem is most likely resulted from using launchActivity in every single one of your tests inside a method, the second test can't launch the Activity (at least it can't launch the activity fast enough) so you get an error in your first assertion.
androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.stegner.androiddex:id/menu_filter
You should remove launchActivity() from your test methods and define an activity test rule using the first screen your app displays.
#Rule
public ActivityTestRule<MyFirstActivity> activityRule =
new ActivityTestRule<>(MyFirstActivity.class);
When you annotate this class with the annotation Rule, before every test starts it will make sure this activity is loaded and then you can go to the page you want with mimicking user interactions. If you still have problems after applying this solution, please write it as a comment because then your problem will be related to synchronization which requires a different answer.

Kotlin: Skipping Coroutines

I playing around with Kotlin and Coroutines in my demo android application.
Here's what I have:
fun testCoroutine3() = runBlocking {
var num = 0
val jobs = List(10_000) { // create a lot of coroutines and list their jobs.
launch(CommonPool) {
delay(1000L)
println(num++)
}
}
for(job in jobs) {
job.join() //wait for all jobs to finish
}
println("FINAL RESULT $num")
}
Basically I'm creating a list of 10,000 Coroutines that wait for 1 second and print a number then increment it.
Then when all jobs are done I print the final result.
(This demo is taken from the GitHub Documentation)
Now most of my test run fine, all the coroutines run almost simultaneously, and my final result is 10000
However in some rare occasions, I am getting the final result as 9,999
This become more obvious when I increase the number to 50,000 for example:
Is it possible that Kotlin is skipping some coroutines when there's a lot of them? on the 50,000, looks like it skipped 2
Or is something else happening here?
num++ consists of two operations: tmp = num + 1 and num = tmp. When dealing with multithreading like your example there are cases where some operations might overwrite the results of another thread, leading to cases like your example.
If you want to know more, research "race conditions" where the end result depends on a "race" between two seperate processes.

RXJava, How to execute two observeables asynchronously, run a function once both have completed, and get the time difference between both threads?

Im looking for an elegant way to run two observable's at the same time, wait for both to finish (They both fetch data from the web) and only once both have completed perform a function.
I also need to get a time difference between the two threads? Is there an elegant way to do this in RXJava, specifically for android. Or will i have to set a global timer variable in the oncomplete methods of both observable's and only compare them after?
My thinking is to merge the observeables into one, but then im not sure how to get the time difference?
You can zip the timestamped sequence of the two sources:
Observable<A> sourceA = ...
Observable<B> sourceB = ...
Observable.zip(sourceA.timestamp(), sourceB.timestamp(), (a, b) -> {
long timediff = a.time() - b.time();
A itemA = a.value();
B itemB = b.value();
return itemA + ", " + itemB;
})
.subscribe(...);

Categories

Resources