Skip to content

Commit 7ec37bf

Browse files
robot-pattern
1 parent e4990df commit 7ec37bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+946
-0
lines changed

robot-pattern/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
layout: pattern
3+
title: Robot Pattern
4+
folder: robot-pattern
5+
permalink: /patterns/pipeline/
6+
categories: Testing
7+
language: en
8+
tags:
9+
- UI testing
10+
---
11+
12+
## Intent
13+
14+
Robot design pattern intends to separate the "how" of UI testing from the "what". When testing the UI,
15+
the purpose should be to test how the view works, and not explicitly test the fields and their values.
16+
17+
## Explanation
18+
19+
Robot design pattern suggests building a "testing robot" for each UI screen, which is responsible for
20+
finding the relevant buttons and fields on the screen. The test itself should just specify the values and buttons
21+
to be tested - not their locations or logistical details.
22+
23+
Real world example
24+
25+
> Suppose a developer wants to test a login screen, they can write a test which specifies the username
26+
and password, along with with the button to be clicked. They don't have to specify the variables
27+
being tested or make assertion statements - the robot should abstract all those details from the
28+
developer
29+
30+
In plain words
31+
32+
> Robot design pattern abstracts the "what" of UI testing and lets the developer focus on
33+
the "how"
34+
35+
Wikipedia says
36+
37+
> The Robot Pattern was designed by Jake Wharton at Square back in 2016. The power of this pattern
38+
is its ability to create an abstraction layer in order to interact with the UI in a declarative mode.
39+
Once created, it is then possible to perform multiple tests in order to verify our use cases without
40+
boilerplate code, as well as without maintenance problems related to a refactor.
41+
42+
43+
**Programmatic Example**
44+
45+
For a login screen, a standard automated UI test for email and password assertion
46+
would look like -
47+
48+
```java
49+
onView(withId(R.id.edt_email)).perform(typeText(user), closeSoftKeyboard());
50+
onView(withId(R.id.edt_pass)).perform(typeText(pass), closeSoftKeyboard());
51+
onView(withId(R.id.btn_login)).perform(click());
52+
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN FAILED")));
53+
```
54+
55+
However, the aim of the robot pattern is to abstract the technicalities and represent
56+
the test as this instead -
57+
58+
```java
59+
new LoginRobot().username("[email protected]").password("sunflowers").login().resultSuccess();```
60+
61+
## Class diagram
62+
63+
![alt text](./etc/robot-pattern.png “Robot pattern class diagram")
64+
65+
## Applicability
66+
67+
Use the Robot design pattern when you want to
68+
69+
* Build automated UI tests for an entire app
70+
* Unsure about the exact location and variables of the UI elements and want
71+
to maintain the longevity of the tests
72+
73+
74+
## Credits
75+
76+
* [UI testing with Espresso](https://guides.codepath.com/android/ui-testing-with-espresso)
77+
* [UI testing in Android](https://codingwithmitch.com/blog/ui-testing-with-espresso-android/)

robot-pattern/etc/robot-pattern.png

422 KB
Loading

robot-pattern/pom.xml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
5+
6+
The MIT License
7+
Copyright © 2014-2022 Ilkka Seppälä
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
-->
28+
<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">
29+
<modelVersion>4.0.0</modelVersion>
30+
<parent>
31+
<groupId>com.iluwatar</groupId>
32+
<artifactId>java-design-patterns</artifactId>
33+
<version>1.26.0-SNAPSHOT</version>
34+
</parent>
35+
<artifactId>promise</artifactId>
36+
<dependencies>
37+
<dependency>
38+
<groupId>org.junit.jupiter</groupId>
39+
<artifactId>junit-jupiter-engine</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.mockito</groupId>
44+
<artifactId>mockito-core</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
<build>
49+
<plugins>
50+
<plugin>
51+
<groupId>org.apache.maven.plugins</groupId>
52+
<artifactId>maven-assembly-plugin</artifactId>
53+
<executions>
54+
<execution>
55+
<configuration>
56+
<archive>
57+
<manifest>
58+
<mainClass>com.example.robotdesignpattern.LoginScreen</mainClass>
59+
</manifest>
60+
</archive>
61+
</configuration>
62+
</execution>
63+
</executions>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
</project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

robot-pattern/src/.gradle/4.10.1/gc.properties

Whitespace-only changes.
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.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#Sat Oct 29 14:15:17 AEDT 2022
2+
gradle.version=4.10.1
Binary file not shown.

robot-pattern/src/.gradle/vcs-1/gc.properties

Whitespace-only changes.

robot-pattern/src/app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

robot-pattern/src/app/build.gradle

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apply plugin: 'com.android.application'
2+
3+
android {
4+
compileSdkVersion 28
5+
defaultConfig {
6+
applicationId "com.example.espressoexample"
7+
minSdkVersion 15
8+
targetSdkVersion 28
9+
versionCode 1
10+
versionName "1.0"
11+
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12+
}
13+
buildTypes {
14+
release {
15+
minifyEnabled false
16+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17+
}
18+
}
19+
}
20+
21+
dependencies {
22+
implementation fileTree(dir: 'libs', include: ['*.jar'])
23+
implementation 'com.android.support:appcompat-v7:28.0.0'
24+
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25+
testImplementation 'junit:junit:4.12'
26+
androidTestImplementation 'com.android.support.test:runner:1.0.2'
27+
androidTestImplementation 'com.android.support.test:rules:1.0.2'
28+
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
29+
30+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.example.robotdesignpattern;
2+
3+
import static android.support.test.espresso.Espresso.onView;
4+
import static android.support.test.espresso.action.ViewActions.click;
5+
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
6+
import static android.support.test.espresso.action.ViewActions.typeText;
7+
import static android.support.test.espresso.assertion.ViewAssertions.matches;
8+
import static android.support.test.espresso.matcher.ViewMatchers.withId;
9+
import static android.support.test.espresso.matcher.ViewMatchers.withText;
10+
11+
public class LoginRobot {
12+
13+
public LoginRobot username(String user) {
14+
onView(withId(R.id.edt_email)).perform(typeText(user), closeSoftKeyboard());
15+
return this;
16+
}
17+
18+
public LoginRobot password(String pass){
19+
onView(withId(R.id.edt_pass)).perform(typeText(pass), closeSoftKeyboard());
20+
return this;
21+
}
22+
23+
public LoginRobot login(){
24+
onView(withId(R.id.btn_login)).perform(click());
25+
return this;
26+
}
27+
28+
public LoginRobot resultFail(){
29+
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN FAILED")));
30+
return this;
31+
32+
}
33+
34+
public LoginRobot resultSuccess(){
35+
onView(withId(R.id.tv_result)).check(matches(withText("LOGIN SUCCESS!")));
36+
return this;
37+
38+
}
39+
40+
41+
42+
43+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.example.robotdesignpattern;
2+
3+
import android.support.test.rule.ActivityTestRule;
4+
import android.support.test.runner.AndroidJUnit4;
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
9+
import static android.support.test.espresso.action.ViewActions.click;
10+
import static android.support.test.espresso.matcher.ViewMatchers.withId;
11+
import static android.support.test.espresso.matcher.ViewMatchers.withText;
12+
13+
@RunWith(AndroidJUnit4.class)
14+
public class LoginTest {
15+
16+
@Rule
17+
public ActivityTestRule<LoginScreen> activityRule
18+
= new ActivityTestRule<>(LoginScreen.class);
19+
20+
@Test
21+
public void loginFailed() {
22+
new LoginRobot().username("[email protected]").password("roses").login().resultFail();
23+
}
24+
25+
@Test
26+
public void loginSuccess() {
27+
new LoginRobot().username("[email protected]").password("sunflowers").login().resultSuccess();
28+
29+
}
30+
31+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.example.robotdesignpattern">
4+
5+
<application
6+
android:allowBackup="true"
7+
android:icon="@mipmap/ic_launcher"
8+
android:label="@string/app_name"
9+
android:roundIcon="@mipmap/ic_launcher_round"
10+
android:supportsRtl="true"
11+
android:theme="@style/AppTheme">
12+
<activity android:name="com.example.robotdesignpattern.LoginScreen">
13+
<intent-filter>
14+
<action android:name="android.intent.action.MAIN" />
15+
16+
<category android:name="android.intent.category.LAUNCHER" />
17+
</intent-filter>
18+
</activity>
19+
</application>
20+
21+
</manifest>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.example.robotdesignpattern;
2+
3+
import android.support.v7.app.AppCompatActivity;
4+
import android.os.Bundle;
5+
import android.view.View;
6+
import android.widget.Button;
7+
import android.widget.EditText;
8+
import android.widget.TextView;
9+
10+
public class
11+
LoginScreen extends AppCompatActivity {
12+
13+
EditText Email, Password;
14+
Button Login;
15+
TextView Result;
16+
17+
@Override
18+
protected void onCreate(Bundle savedInstanceState) {
19+
super.onCreate(savedInstanceState);
20+
setContentView(R.layout.activity_main);
21+
22+
Email = findViewById(R.id.edt_email);
23+
Password = findViewById(R.id.edt_pass);
24+
Result = findViewById(R.id.tv_result);
25+
Login = findViewById(R.id.btn_login);
26+
27+
Login.setOnClickListener(new View.OnClickListener() {
28+
@Override
29+
public void onClick(View v) {
30+
31+
if (Email.getText().toString().equalsIgnoreCase("[email protected]")
32+
&& Password.getText().toString().equalsIgnoreCase("sunflowers")) {
33+
Result.setText("LOGIN SUCCESS!");
34+
} else {
35+
Result.setText("LOGIN FAILED");
36+
}
37+
38+
}
39+
});
40+
}
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:aapt="http://schemas.android.com/aapt"
3+
android:width="108dp"
4+
android:height="108dp"
5+
android:viewportWidth="108"
6+
android:viewportHeight="108">
7+
<path
8+
android:fillType="evenOdd"
9+
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"
10+
android:strokeWidth="1"
11+
android:strokeColor="#00000000">
12+
<aapt:attr name="android:fillColor">
13+
<gradient
14+
android:endX="78.5885"
15+
android:endY="90.9159"
16+
android:startX="48.7653"
17+
android:startY="61.0927"
18+
android:type="linear">
19+
<item
20+
android:color="#44000000"
21+
android:offset="0.0" />
22+
<item
23+
android:color="#00000000"
24+
android:offset="1.0" />
25+
</gradient>
26+
</aapt:attr>
27+
</path>
28+
<path
29+
android:fillColor="#FFFFFF"
30+
android:fillType="nonZero"
31+
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"
32+
android:strokeWidth="1"
33+
android:strokeColor="#00000000" />
34+
</vector>

0 commit comments

Comments
 (0)