Skip to content

robot-pattern #2212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions robot-pattern/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
layout: pattern
title: Robot Pattern
folder: robot-pattern
permalink: /patterns/pipeline/
categories: Testing
language: en
tags:
- UI testing
---

## Intent

Robot design pattern intends to separate the "how" of UI testing from the "what". When testing the UI,
the purpose should be to test how the view works, and not explicitly test the fields and their values.

## Explanation

Robot design pattern suggests building a "testing robot" for each UI screen, which is responsible for
finding the relevant buttons and fields on the screen. The test itself should just specify the values and buttons
to be tested - not their locations or logistical details.

Real world example

> Suppose a developer wants to test a login screen, they can write a test which specifies the username
and password, along with with the button to be clicked. They don't have to specify the variables
being tested or make assertion statements - the robot should abstract all those details from the
developer

In plain words

> Robot design pattern abstracts the "what" of UI testing and lets the developer focus on
the "how"

Wikipedia says

> The Robot Pattern was designed by Jake Wharton at Square back in 2016. The power of this pattern
is its ability to create an abstraction layer in order to interact with the UI in a declarative mode.
Once created, it is then possible to perform multiple tests in order to verify our use cases without
boilerplate code, as well as without maintenance problems related to a refactor.


**Programmatic Example**

For a login screen, a standard automated UI test for email and password assertion
would look like -

```java
onView(withId(R.id.edt_email)).perform(typeText(user), closeSoftKeyboard());
onView(withId(R.id.edt_pass)).perform(typeText(pass), closeSoftKeyboard());
onView(withId(R.id.btn_login)).perform(click());
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN FAILED")));
```

However, the aim of the robot pattern is to abstract the technicalities and represent
the test as this instead -

```java
new LoginRobot().username("[email protected]").password("sunflowers").login().resultSuccess();```

## Class diagram

![alt text](./etc/robot-pattern.png “Robot pattern class diagram")

## Applicability

Use the Robot design pattern when you want to

* Build automated UI tests for an entire app
* Unsure about the exact location and variables of the UI elements and want
to maintain the longevity of the tests


## Credits

* [UI testing with Espresso](https://guides.codepath.com/android/ui-testing-with-espresso)
* [UI testing in Android](https://codingwithmitch.com/blog/ui-testing-with-espresso-android/)
Binary file added robot-pattern/etc/robot-pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions robot-pattern/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).

The MIT License
Copyright © 2014-2022 Ilkka Seppälä

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>promise</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<configuration>
<archive>
<manifest>
<mainClass>com.example.robotdesignpattern.LoginScreen</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions robot-pattern/src/.gradle/buildOutputCleanup/cache.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Sat Oct 29 14:15:17 AEDT 2022
gradle.version=4.10.1
Binary file not shown.
Empty file.
1 change: 1 addition & 0 deletions robot-pattern/src/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
30 changes: 30 additions & 0 deletions robot-pattern/src/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.espressoexample"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

}
21 changes: 21 additions & 0 deletions robot-pattern/src/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.example.robotdesignpattern;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

public class LoginRobot {

public LoginRobot username(String user) {
onView(withId(R.id.edt_email)).perform(typeText(user), closeSoftKeyboard());
return this;
}

public LoginRobot password(String pass){
onView(withId(R.id.edt_pass)).perform(typeText(pass), closeSoftKeyboard());
return this;
}

public LoginRobot login(){
onView(withId(R.id.btn_login)).perform(click());
return this;
}

public LoginRobot resultFail(){
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN FAILED")));
return this;

}

public LoginRobot resultSuccess(){
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN SUCCESS!")));
return this;

}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.robotdesignpattern;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class LoginTest {

@Rule
public ActivityTestRule<LoginScreen> activityRule
= new ActivityTestRule<>(LoginScreen.class);

@Test
public void loginFailed() {
new LoginRobot().username("[email protected]").password("roses").login().resultFail();
}

@Test
public void loginSuccess() {
new LoginRobot().username("[email protected]").password("sunflowers").login().resultSuccess();

}

}
21 changes: 21 additions & 0 deletions robot-pattern/src/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.robotdesignpattern">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.example.robotdesignpattern.LoginScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.example.robotdesignpattern;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class
LoginScreen extends AppCompatActivity {

EditText Email, Password;
Button Login;
TextView Result;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Email = findViewById(R.id.edt_email);
Password = findViewById(R.id.edt_pass);
Result = findViewById(R.id.tv_result);
Login = findViewById(R.id.btn_login);

Login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

if (Email.getText().toString().equalsIgnoreCase("[email protected]")
&& Password.getText().toString().equalsIgnoreCase("sunflowers")) {
Result.setText("LOGIN SUCCESS!");
} else {
Result.setText("LOGIN FAILED");
}

}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
Loading