Neon Warge Neon Warge - 2 months ago 7
Android Question

How to programatically test UI action/activities without repeating test code?

I am very new to TDD and doing TFD in particular. I have not written any codes yet and I wanted to write a test first before develop everything in conjunction with TDD. I want your insight. It seems I am copy pasting my test code. I have made my 'pseudo' user story for me to practice. I am going to paraphrase as this is an actual personal project so please bear with me.


"User can search for a tag"


I have a UI that allows to add and to search for a tag. I made it a little bit conservative by using minimal design. I have a button which toggles between add/search string. I have a
CardView
to represent this, that
CardView
is part of the list (its like Facebook). Now I wanted to test that when the user press the button, the content on that card will change to search mode. I pretty much have an idea on how to do this but copy pasting my test code per each test is kind of bothering me.

Here is my test:

public class TagListActivityTest
{
@Test
public void shouldHaveAddTagCard()
{
// User tapped to expand the card
onView(withId(R.id.edittext_description_minimized))
.perform(click());

// User sees the expanded card
onView(withId(R.id.linearlayout_add_note_maximize))
.check(matches(isDisplayed()));

// User sees the expanded card's quick action buttons
onView(withId(R.id.relativelayout_quick_action_button))
.check(matches(isDisplayed()));

// User clicks the add tag button
onView(withId(R.id.imagebutton_tag))
.perform(click());

// User sees the tag list
onView(withId(R.id.coordinatorlayout_tag_list))
.check(matches(isDisplayed()));

// User sees the add tag card
onView(withId(R.id.cardview_add_tag))
.check(matches(isDisplayed()));
}

@Test
public void shouldToggleToSearch()
{
// I am going to do the exact same thing as shouldHaveAddTagCard
// starting from my parent activity until here...
onView(withId(R.id.edittext_description_minimized))
.perform(click());

onView(withId(R.id.linearlayout_add_note_maximize))
.check(matches(isDisplayed()));

onView(withId(R.id.relativelayout_quick_action_button))
.check(matches(isDisplayed()));

onView(withId(R.id.imagebutton_tag))
.perform(click());

onView(withId(R.id.coordinatorlayout_tag_list))
.check(matches(isDisplayed()));
}
}


The
TagListActivity
is originating from a parent activity. There is some bunch of things you have to do before you can go through the
TagListActivity
and I already have written test for it. So when I test
TagListActivity
I have to go first in application's homescreen and navigate from there as you can see from my test procedure
shouldHaveAddTagCard
. This is my problem, I have to write that procedure over and over again. So when I wanted to test
shouldToggleSearch
I have to go from the parent activity and write those tests again until I reached
TagListActivity
. I think I am doing something wrong.

So my question is:


  1. How can I organize this when there is a known user action procedure.
    I have written test per procedure to make sure it does what I wanted
    to be.

  2. no. 1 makes me feel there is something wrong in what I am doing. I am testing per action (ie user adds tag, user search tag, user deletes tag). So the pre-procedure
    I did before
    user can add tags
    is the same as
    user can search tag
    and I have
    to copy paste those pre-procedure before I can actually test.



Also, it seems that I cannot call a test method from a test method as discussed here. I am thinking of reusing test code but it is not advisable.

Am doing things correctly? Any thoughts?

Answer

To be honest your tests look very good if this is your first time doing TDD.

Reducing duplication

You can use the @Before annotation to execute some code before each test. In your case, it might look something like this:

// this method will be executed before each test
@Before
public void clickOnEditTextDescription() {
    onView(withId(R.id.edittext_description_minimized))
            .perform(click());
    // put as much set up code in here as you need
}

Bear in mind that, in general, you should not make any assertions in the @Before method. It is for set up code only.

But is it always a good thing?

@Before methods are great, however, remember that copying and pasting test code is not always a bad thing. It's a different balance to production code. In production code, you want no duplication because any given piece of business logic should only exist in one place. In test code however, each test needs to be completely independent from all the other tests. If got rid of all the duplication in your test code, then it would be very difficult to change the shared code without breaking all your tests. Furthermore, your tests would be harder to read because you would have to keep referring to the shared code.

I recommend that you do some research on DAMP (descriptive and meaningful phrases) vs DRY (don't repeat yourself). DAMP is more relevant for unit tests, and allows you to repeat yourself sometimes. DRY is more relevant for production code. The following answer is great at explaining this:

http://stackoverflow.com/a/11837973/6816469