Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

30 August 2020

TDD a UI with Fast Tests

AndroidsThis is another part of my series of TDD-ing user interfaces. Earlier this year I looked at test driving classic, fat, state based UIs like Swing or WinForms and then web component libraries like Vaadin. In both situations it was possible to TDD the UI using the Model View Presenter (MVP) pattern and a decent test bed for testing the implementation of the view. Later I explored test driving an immediate mode GUI, which was even easier than doing so for retained mode: There was no need to search for components, capture events or trigger events. This time I want to experiment with test driving a user interface by only using UI tests. In theory I could do that with Swing or Vaadin, but the UI tests of these technologies are too slow. For my TDD cycle I want fast tests!

Android UI Tests
My friend Bastien David told me that Android UI tests are pretty fast and I talked him into running this experiment with me. I had never done any Android development and had only little knowledge of the Kotlin language - it is good to have friends who know. Bastien's Kotlin/​Android starting point used Espresso and Robolectric for testing the Android UIs. The sample test
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class HelloActivityTest {

  @get:Rule
  var activityScenarioRule = activityScenarioRule<HelloActivity>()

  @Test
  fun hello_activity_has_some_hello_text() {
    onView(withId(R.id.text_hello)).check(matches(withText("Hello!")))
  }

}
used Espresso's matchers to find the text "Hello" on the view of the HelloActivity.

First Few Tests
We worked on the same exercise used in my previous articles, the Login Form exercise. The first tests for UI elements of the LoginActivity, i.e. user name field and login button were
@RunWith(AndroidJUnit4::class)
class LoginActivityTest {

  @get:Rule
  var activityScenarioRule = activityScenarioRule<LoginActivity>()

  @Test
  fun `has username field with max length of 20`() {
    onView(withId(R.id.username))
      .check(matches(checkMaxLength(20))) // custom matcher
  }

  @Test
  fun `has label for username`() {
    onView(withId(R.id.username_label))
      .check(matches(withText("Phone, email or username")))
  }

  @Test
  fun `has login button`() {
    onView(withId(R.id.login_button))
      .check(matches(withText("Log in")))
      .check(matches(not(isEnabled())))
  }
}
These tests did not create any code, we declared the UI elements in the layout app/​src/​main/​res/​layout/​activity_login.xml to make each test pass. On Bastien's machine the tests were fast enough and it was easy to drive UI elements and their attributes. We had to create some custom Hamcrest matchers though, e.g. checkMaxLength. We decided not to go deeper and assert colours, styles or positions, but we could have.

Test Driving Logic
The next test
  @Test
  fun `when username is introduced then login button is enabled`() {
    onView(withId(R.id.username))
      .perform(typeText("a real username"))

    onView(withId(R.id.login_button))
      .check(matches(isEnabled()))
  }
brought a bit of logic into the LoginActivity,
class LoginActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_login)

    username.addTextChangedListener {
      login_button.isEnabled = true // new logic
    }
  }
}
We only spent two hours on the exercise and did not get far. Still I could see where we would end. We would inspect every behaviour by its effect on the UI. While we could use the MVP pattern, we did not as the tests were fast enough.

Similar Technologies, e.g. React
Some modern UI technologies come with a fair amount of testing support. For example the same approach should be possible with React. There is a JavaScript/​React starting point in the Login Form Kata. Some people have tried that. I do not know if they went far enough for a definite conclusion.

David Tanzer dedicated some of his React TDD videos on testing the UI: In Part 1: Testing the UI Itself he explains the necessary setup to test React and in Part 2: Value and Cost of Tests he talks about possible test cases. He does not check text elements and style of the UI as he considers these things to have little probability of breaking later. Doing TDD, he looks for tests which influence the design of the code. I highly recommend you watch all of his videos.

Conclusion: Is this still TDD?
Test driving a UI with fast tests like Android or React is certainly possible, maybe even easy. These tests are not unit tests. Is it still TDD? We definitely write the tests first so it is Acceptance test driven (A-TDD) or at least "UI specification test driven". Unlike TDD these tests do not influence the design of the code because the components of the UI are usually specified by the requirements, e.g. in wireframes. As all tests exercise the code through the UI, there is no pressure on the code, its interfaces and collaborators. Still we can evolve the code because we have full test coverage and regression safety.

17 October 2010

Android Browser Bookmarks

In my previous post I already mentioned my Android phone. Soon after I got it, I wanted to import my bookmarks from my desktop PC to its standard browser. So being green I copied my favourites to the sdcard and tried to open them. Well it didn't work.

Google AndroidWorking Around
If I lived in "Google-land", I could use Google Bookmarks. But I don't and had to look for alternatives. After some searching I found a possible way to import bookmarks:
  1. Upload your single html file of exported bookmarks to a document on Google Docs.
  2. In your browser navigate to said page.
  3. Manually click on each link and save as bookmark.
Noooo, you must be kidding me. This is hideous. Then I found a nice instruction how to
import the bookmarks directly into the browser's database. That would be cool. But I'm not bold enough to root my new phone and "play with that, probably break it a bit - and then cry about it later." (Ward from androidcommunity.com)

My Solution
On several places I read hints that some Android apps were able to import bookmarks, but I couldn't find any. Instead I found Matthieu Guenebaud's Bookmarks Manager. It's able to backup and restore the browser bookmarks and uses a plain zip file to store them.
Viewing .ZIP: Bookmarks_2010-09-10_14-11-24.zip

Length  Method Size  Ratio    Date    Time   Name
------  ------ ----- -----    ----    ----   ----
  2273 DeflatN   710 68.8% 09.10.2010  2:11p bookmarks.xml
   526 DeflatN   531  0.0% 09.10.2010  2:11p 23.png
   764 DeflatN   769  0.0% 09.10.2010  2:11p 24.png
   326 DeflatN   331  0.0% 09.10.2010  2:11p 51.png
   684 DeflatN   689  0.0% 09.10.2010  2:11p 57.png
   239 DeflatN   238  0.5% 09.10.2010  2:11p 69.png
   541 DeflatN   546  0.0% 09.10.2010  2:11p 90.png
  1266 DeflatN  1271  0.0% 09.10.2010  2:11p 198.png
   490 DeflatN   495  0.0% 09.10.2010  2:11p 164.png
   304 DeflatN   309  0.0% 09.10.2010  2:11p 124.png
   408 DeflatN   413  0.0% 09.10.2010  2:11p 229.png
------         ----- -----                   ----
  7821          6302 19.5%                     11
The file bookmarks.xml has a simple XML structure of <bookmark>s inside a <bookmarks> element. Yes, that's something I can use.
  1. Backup the bookmarks, even if empty, to get an initial file.
  2. Unpack the archive.
  3. Insert bookmarks into the existing XML structure.
  4. Repack the archive.
  5. Restore from the modified zip file.
  6. Enjoy your new wealth of bookmarks.
In case you don't feel like editing the bookmarks.xml by hand, here is some Scala code.:
val bookmarkXml = scala.xml.XML.loadFile(targetFolder + "/bookmarks.xml")
val lastOrder = Integer.parseInt((bookmarkXml \\ "order").last.text)
val oldNodes = bookmarkXml \\ "bookmark"
val newNodes = to_xml_list(bookmarks, lastOrder + 1000)
val root = <bookmarks>{ oldNodes }{ newNodes }</bookmarks>
scala.xml.XML.save(targetFolder + "/bookmarks.xml", root, "UTF8", true, null)
Thanks to Scala's excellent XML support, reading and modifying the bookmarks file is easy. The method to_xml_list() iterates all favourites and creates XML fragments for each one using the following method.
def to_xml(bm: Favorite, order: Int) = {
  <bookmark>
    <title>{ bm.name }</title>
    <url>{ bm.url }</url>
    <order>{ order }</order>
    <created>{ bm.fileDate.getTime }</created>
  </bookmark>
}
Favorite is a class representing an Internet Explorer favourite that I wrote long ago. (Yeah baby, code reuse!) Value order is the number Bookmark Explorer uses for sorting bookmarks. See the complete source of GuenmatBookmarks.scala.