📜 ⬆️ ⬇️

Coffee with cucumbers (Espresso + Cucumber)



A relatively recent Espresso library for testing UI Android applications has appeared. Its advantages over counterparts were viewed more than once. In short, they lie in the fact that this is Google’s development for its own OS (they used Robotium before themselves), as well as in the laconism of syntax and speed of work. So, we decided to go with the times and use Espresso. But we don't have enough of the advantages that we already have, we want BDD ( http://en.wikipedia.org/wiki/Behavior-driven_development ), we want screenshots and reports in json and html, we want to run it all on CI, in Eventually! But first things first. I'll tell you how to make friends with Cucumber ( http://habrahabr.ru/post/62958/ ) and Espresso ( http://habrahabr.ru/post/212425/ ) on a small example. Anyone who is tired of Appium, who wants to get away from Robotium and those who are not indifferent to testing Android, I ask under the cat.

Connection

We will use Gradle as a tool to build and resolve dependencies for our project. I highly recommend for those who have not yet seen, the site http://gradleplease.appspot.com/ . We give it the name of the module we are looking for, and it returns the string to connect it to Gradle.
')
Let's create a project and connect Espresso and the Cucumber modules necessary for our task, for this we supplement the dependency block of the build.gradle file as follows:

dependencies { androidTestCompile('com.jakewharton.espresso:espresso-support-v4:1.1-r3') androidTestCompile 'info.cukes:cucumber-core:1.1.8' androidTestCompile 'info.cukes:cucumber-java:1.1.8' androidTestCompile 'info.cukes:cucumber-html:0.2.3' androidTestCompile ('info.cukes:cucumber-android:1.2.2') androidTestCompile ('info.cukes:cucumber-junit:1.1.8') { exclude group: 'org.hamcrest', module: 'hamcrest-core' exclude group: 'org.hamcrest', module: 'hamcrest-integration' exclude group: 'org.hamcrest', module: 'hamcrest-library' } } 

In order for us to use the Espresso tools, we need to run the tests through GoogleInstrumentationTestRunner. So to connect, Cucumber needs to be inherited from this class, within which we will give it all control.

 public class CucuRunner extends GoogleInstrumentationTestRunner{ private CucumberInstrumentationCore helper; public CucuRunner() { helper = new CucumberInstrumentationCore(this); } @Override public void onCreate(Bundle arguments) { helper.create(arguments); super.onCreate(arguments); } @Override public void onStart() { helper.start(); } } 

Do not forget to specify our newly created instrumentation test runner in build.gradle

 defaultConfig { ... testInstrumentationRunner 'habrahabr.ru.myapplication.test.CucuRunner' ... } 

Steps

Now we need to create the steps that will be used in our test scenarios. In our case, they will be small tests, combined in one case. To do this, we create the appropriate class, which we inherit from the standard for Espresso test suite, in order to have access to all the necessary things. We add an annotation to this class, where we indicate that these are tests of Cucumber, and the result of their work should be placed in reports of appropriate formats in the necessary directories. Please note, Espresso tests are performed on the device, and therefore we do not have access to the computer directories. So we put everything in the directory of our application:

 @CucumberOptions(format = {"pretty","html:/data/data/habrahabr.ru.myapplication/html", "json:/data/data/habrahabr.ru.myapplication/jreport"},features = "features") public class CucumberActivitySteps extends ActivityInstrumentationTestCase2<MainActivity> { 

Now we can deal directly with the implementation of the steps. To do this, it is necessary to separate the methods according to their purpose in accordance with BDD, that is, Given, When and Then. For this purpose, annotations are used that contain a string to find matches in the script file based on regular expressions, the groups in which play the role of input arguments, and in the body of the steps we will use the Espresso calls:

  @Given("^    (\\d)$") public void givenLoginTryCounter(Integer counterValue) { String checkString = String.format(getActivity().getResources().getString(R.string.login_try_left), counterValue); onView(withId(R.id.lblCounter)).check(matches(withText(checkString))); } @When("^   $") public void clickOnBackButton() { ViewActions.pressBack(); } @When("^ '(.+)'      '(.+)'$") public void userLogin(String login, String password) { onView(withId(R.id.txtUsername)).perform(ViewActions.clearText()); onView(withId(R.id.txtPassword)).perform(ViewActions.clearText()); onView(withId(R.id.txtUsername)).perform(ViewActions.typeText(login)); onView(withId(R.id.txtPassword)).perform(ViewActions.typeText(password)); onView(withId(R.id.btnLogin)).perform(ViewActions.click()); } @Then("^     (\\d)$") public void checkLoginTryCounter(Integer counterValue) { givenLoginTryCounter(counterValue); } @Then("^   $") public void checkLoginButtonDisabled() { onView(withId(R.id.btnLogin)).check(matches(not(isEnabled()))); } 

Screenshots

In case of unsuccessful completion of the last step, we will take a screenshot and add the resulting image to the report. Cucumber will do the rest for us, and we will be able to see the status of the screen at the time of the error. The following method is not suitable, for example, to take a picture with a dialogue, but this is a topic for another conversation.

 @After public void embedScreenshot(Scenario scenario) { if(scenario.isFailed()) { Bitmap bitmap; final Activity activity = getActivity(); View view = getActivity().getWindow().getDecorView(); view.setDrawingCacheEnabled(true); bitmap = Bitmap.createBitmap(view.getDrawingCache()); view.setDrawingCacheEnabled(false); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); scenario.embed(stream.toByteArray(), "image/png"); } } 

Scenarios

Remained the most enjoyable part. Having in hands the steps described in CucumberActivitySteps, we can write the tests themselves in a human language that will be available not only to developers, but also to all other interested parties:

  Feature:  Scenario:   ,      Given     3 When  'RandomName'      'wrongPassword' Then      2 And   '    .' 

We save these scripts to the features directory, in which our performing class will look for them (see the CucumberOptions annotation).



Removing reports from the device

If we run the tests, they will pass, but the reports will remain on the device. It means that upon completion of testing, they must be taken from there. Go to the build.gradle file and write the corresponding task, which, using the adb utility and the pull command, will copy the report files to the specified directory.

 task afterTests(type: Exec, dependsOn:runCucuTests) { commandLine "${android.sdkDirectory}" + "/platform-tools/adb", 'pull', '/data/data/habrahabr.ru.myapplication/html', System.getProperty("user.dir") + "/cucumber_reports" } 

Now you can run everything through the IDE, you just need to create the appropriate launch configuration, and after completing the tests, execute our task to remove the reports.



Reports will be saved to the above directory.


Run on CI

But we do not want to run tests through the IDE, we want to run them from the console, and connectedCheck does not suit us. So we write a new task. And here, unfortunately, we did not come up with anything better than to build the application and install it on the device, and then send the command to start testing via adb. And after all this, pick up the reports described above by the task.

 task runCucuTests(type: Exec, dependsOn:'installDebugTest'){ commandLine "${android.sdkDirectory}" + "/platform-tools/adb", 'shell', 'am', 'instrument', '-w', 'habrahabr.ru.myapplication.test/.CucuRunner', 'echo', 'off' finalizedBy('afterTests') } 

In principle, this is already enough to run tests on CI.

At the output, we get the following reports:



And for each scenario that failed, we will have a screenshot attached:



On this, perhaps, dwell. There is a lot to be improved, for example, to get a normal output to the console during the execution of tests containing information about progress, I want to make the report file beautiful and much more. I hope there will be another such opportunity. For all those interested, the project itself is posted on Github: https://github.com/Stabilitron/espresso-cucumber-example

Thanks for attention. Stable releases to you!

Source: https://habr.com/ru/post/255763/


All Articles