Skip to content

Commit 0033247

Browse files
committed
Use CucumberOptions in CucumberInstrumentation, replacing RunWithCucumber. Closes #576.
1 parent b2ac1f8 commit 0033247

File tree

8 files changed

+63
-119
lines changed

8 files changed

+63
-119
lines changed

History.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* [Java, Jython] New `--snippet [underscore|camelcase]` option for more control over snippet style. ([#561](https://github.com/cucumber/cucumber-jvm/pull/561), [302](https://github.com/cucumber/cucumber-jvm/pull/302) Márton Mészáros, Aslak Hellesøy)
1616
* [Windows] Use uri instead of path in CucumberFeature ([#562](https://github.com/cucumber/cucumber-jvm/pull/562) Björn Rasmusson)
1717
* [Android] Better example for Cucumber-Android. ([#547](https://github.com/cucumber/cucumber-jvm/issues/547), [#574](https://github.com/cucumber/cucumber-jvm/issues/574))
18+
* [Android] Use @CucumberOptions instead of @RunWithCucumber. ([#576](https://github.com/cucumber/cucumber-jvm/issues/576))
1819

1920
## [1.1.4](https://github.com/cucumber/cucumber-jvm/compare/v1.1.3...v1.1.4) (2013-08-11)
2021

android/src/cucumber/api/android/CucumberInstrumentation.java

+17-69
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import android.os.Bundle;
77
import android.os.Looper;
88
import android.util.Log;
9-
import cucumber.runtime.Backend;
10-
import cucumber.runtime.CucumberException;
9+
import cucumber.api.CucumberOptions;
10+
import cucumber.runtime.*;
1111
import cucumber.runtime.Runtime;
12-
import cucumber.runtime.RuntimeOptions;
1312
import cucumber.runtime.android.AndroidFormatter;
1413
import cucumber.runtime.android.AndroidObjectFactory;
1514
import cucumber.runtime.android.AndroidResourceLoader;
@@ -24,15 +23,10 @@
2423
import gherkin.formatter.model.*;
2524

2625
import java.io.IOException;
27-
import java.lang.annotation.Annotation;
28-
import java.lang.reflect.Method;
2926
import java.util.ArrayList;
3027
import java.util.List;
31-
import java.util.Properties;
3228

3329
public class CucumberInstrumentation extends Instrumentation {
34-
public static final String ARGUMENT_TEST_CLASS = "class";
35-
public static final String ARGUMENT_TEST_PACKAGE = "package";
3630
public static final String REPORT_VALUE_ID = "InstrumentationTestRunner";
3731
public static final String REPORT_KEY_NUM_TOTAL = "numtests";
3832
public static final String REPORT_KEY_NUM_CURRENT = "current";
@@ -47,8 +41,6 @@ public class CucumberInstrumentation extends Instrumentation {
4741
private ResourceLoader resourceLoader;
4842
private ClassLoader classLoader;
4943
private Runtime runtime;
50-
private String packageOfTests;
51-
private String features;
5244

5345
@Override
5446
public void onCreate(Bundle arguments) {
@@ -63,46 +55,21 @@ public void onCreate(Bundle arguments) {
6355
String apkPath = context.getPackageCodePath();
6456
Reflections reflections = new DexReflections(newDexFile(apkPath));
6557

66-
// For glue and features either use the provided arguments or try to find a RunWithCucumber annotated class.
67-
// If nothing works, default values will be used instead.
68-
if (arguments.containsKey(ARGUMENT_TEST_CLASS) || arguments.containsKey(ARGUMENT_TEST_PACKAGE)) {
69-
70-
String testClass = arguments.getString(ARGUMENT_TEST_CLASS);
71-
testClass = testClass != null ? testClass : "null";
72-
packageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
73-
74-
try {
75-
Class<?> clazz = classLoader.loadClass(testClass);
76-
boolean annotationWasPresent = readRunWithCucumberAnnotation(clazz);
77-
78-
// If the class is not RunWithCucumber annotated, maybe it's Cucumber annotated?
79-
if (!annotationWasPresent) {
80-
SEARCH_ANNOTATION:
81-
for (Method m : clazz.getMethods()) {
82-
for (Annotation a : m.getAnnotations()) {
83-
if (a.annotationType().getName().startsWith("cucumber") && packageOfTests == null) {
84-
packageOfTests = testClass.substring(0, testClass.lastIndexOf("."));
85-
break SEARCH_ANNOTATION;
86-
}
87-
}
88-
}
89-
}
90-
} catch (ClassNotFoundException e) {
91-
Log.w(TAG, e.toString());
92-
}
93-
} else {
94-
for (Class<?> clazz : reflections.getDescendants(Object.class, context.getPackageName())) {
95-
if (readRunWithCucumberAnnotation(clazz)) break;
58+
Class<?> optionsAnnotatedClass = null;
59+
for (Class<?> clazz : reflections.getDescendants(Object.class, context.getPackageName())) {
60+
if (clazz.isAnnotationPresent(CucumberOptions.class)) {
61+
Log.d(TAG, "Found CucumberOptions in class " + clazz.getName());
62+
optionsAnnotatedClass = clazz;
63+
break; // We assume there is only one CucumberOptions annotated class.
9664
}
9765
}
66+
if (optionsAnnotatedClass == null) {
67+
throw new CucumberException("No CucumberOptions annotation");
68+
}
9869

99-
Properties properties = new Properties();
100-
packageOfTests = packageOfTests != null ? packageOfTests : defaultGlue();
101-
features = features != null ? features : defaultFeatures();
102-
103-
properties.setProperty("cucumber.options", String.format("-g %s %s", packageOfTests, features));
104-
runtimeOptions = new RuntimeOptions(properties);
105-
70+
@SuppressWarnings("unchecked")
71+
RuntimeOptionsFactory factory = new RuntimeOptionsFactory(optionsAnnotatedClass, new Class[]{CucumberOptions.class});
72+
runtimeOptions = factory.create();
10673
resourceLoader = new AndroidResourceLoader(context);
10774

10875
List<Backend> backends = new ArrayList<Backend>();
@@ -120,35 +87,16 @@ private DexFile newDexFile(String apkPath) {
12087
}
12188
}
12289

123-
/**
124-
* @return true if the class is RunWithCucumber annotated, false otherwise
125-
*/
126-
private boolean readRunWithCucumberAnnotation(Class<?> clazz) {
127-
RunWithCucumber annotation = clazz.getAnnotation(RunWithCucumber.class);
128-
if (annotation != null) {
129-
// isEmpty() only available in Android API 9+
130-
packageOfTests = annotation.glue().equals("") ? defaultGlue() : annotation.glue();
131-
features = annotation.features().equals("") ? defaultFeatures() : annotation.features();
132-
return true;
133-
}
134-
return false;
135-
}
136-
137-
private String defaultFeatures() {
138-
return "features";
139-
}
140-
141-
private String defaultGlue() {
142-
return getContext().getPackageName();
143-
}
144-
14590
@Override
14691
public void onStart() {
14792
Looper.prepare();
14893

14994
List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader);
15095
int numScenarios = 0;
15196

97+
// How many individual scenarios (test cases) exist - is there a better way to do this?
98+
// This is only relevant for reporting back to the Instrumentation and does not affect
99+
// execution.
152100
for (CucumberFeature feature : cucumberFeatures) {
153101
for (CucumberTagStatement statement : feature.getFeatureElements()) {
154102
if (statement instanceof CucumberScenario) {

android/src/cucumber/api/android/RunWithCucumber.java

-16
This file was deleted.

examples/android/android-test/README.md

+2-5
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ On Windows: use `%ANDROID_HOME%\tools` and `%ANDROID_HOME%\platform-tools` inste
1111

1212
On OS X: Note that for the path to work on the commandline and in IDE's started by launchd [you have to set it](http://stackoverflow.com/questions/135688/setting-environment-variables-in-os-x/588442) in `/etc/launchd.conf` and **NOT** in .bashrc or something else.
1313

14-
### Building
15-
**Using Maven:**
14+
### Building using Maven
1615

1716
`mvn package -pl examples/android/android-test -am -P android,android-examples`
1817

19-
### Debugging
18+
### Debugging using Maven
2019
Please read [the Android documentation on debugging](https://developer.android.com/tools/debugging/index.html).
2120

22-
**Using Maven:**
23-
2421
`mvn install -pl examples/android/android-test -am -P android,android-examples`

examples/android/android-test/src/cucumber/example/android/test/CucumberActivity.java

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import android.app.Activity;
44
import android.os.Bundle;
5-
import cucumber.android.test.R;
65

76
public class CucumberActivity extends Activity {
87
public void onCreate(Bundle savedInstanceState) {

examples/android/android-test/src/cucumber/example/android/test/CucumberActivitySteps.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,32 @@
33
import android.app.Instrumentation;
44
import android.test.ActivityInstrumentationTestCase2;
55
import android.util.Log;
6+
import cucumber.api.CucumberOptions;
67
import cucumber.api.android.CucumberInstrumentation;
78
import cucumber.api.java.After;
89
import cucumber.api.java.Before;
910
import cucumber.api.java.en.Given;
1011
import cucumber.api.java.en.Then;
1112
import cucumber.api.java.en.When;
1213

13-
// Glue code classes need to extend Android test classes
14-
// in order to have access to Context and Instrumentation.
14+
/**
15+
* We extend ActivityInstrumentationTestCase2 in order to have access to methods like getActivity
16+
* and getInstrumentation. Depending on what methods we are going to need, we can put our
17+
* step definitions inside classes extending any of the following Android test classes:
18+
* <p/>
19+
* ActivityInstrumentationTestCase2
20+
* InstrumentationTestCase
21+
* AndroidTestCase
22+
* <p/>
23+
* The CucumberOptions annotation is mandatory for exactly one of the classes in the test project.
24+
* Only the first annotated class that is found will be used, others are ignored. If no class is
25+
* annotated, an exception is thrown.
26+
* <p/>
27+
* The options need to at least specify features = "features". The default value that is set by
28+
* Cucumber internally does not work because features are not on the classpath under Android.
29+
* Features must be placed inside assets/features/ of the test project (or a subdirectory thereof).
30+
*/
31+
@CucumberOptions(features = "features")
1532
public class CucumberActivitySteps extends ActivityInstrumentationTestCase2<CucumberActivity> {
1633
private int steps;
1734

examples/android/cukeulator-test/src/cucumber/example/android/cukeulator/test/CalculatorActivitySteps.java

+24-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.test.ActivityInstrumentationTestCase2;
44
import android.widget.TextView;
5+
import cucumber.api.CucumberOptions;
56
import cucumber.api.java.After;
67
import cucumber.api.java.Before;
78
import cucumber.api.java.en.Given;
@@ -12,29 +13,38 @@
1213

1314
import static cucumber.example.android.cukeulator.test.Utils.clickOnView;
1415

16+
/**
17+
* We extend ActivityInstrumentationTestCase2 in order to have access to methods like getActivity
18+
* and getInstrumentation. Depending on what methods we are going to need, we can put our
19+
* step definitions inside classes extending any of the following Android test classes:
20+
* <p/>
21+
* ActivityInstrumentationTestCase2
22+
* InstrumentationTestCase
23+
* AndroidTestCase
24+
* <p/>
25+
* The CucumberOptions annotation is mandatory for exactly one of the classes in the test project.
26+
* Only the first annotated class that is found will be used, others are ignored. If no class is
27+
* annotated, an exception is thrown.
28+
* <p/>
29+
* The options need to at least specify features = "features". Features must be placed inside
30+
* assets/features/ of the test project (or a subdirectory thereof).
31+
*/
32+
@CucumberOptions(features = "features")
1533
public class CalculatorActivitySteps extends ActivityInstrumentationTestCase2<CalculatorActivity> {
16-
private CalculatorActivity activity;
1734

1835
public CalculatorActivitySteps() {
1936
super(CalculatorActivity.class);
2037
}
2138

22-
@Before
23-
public void before() {
24-
}
25-
26-
@After
27-
public void after() {
28-
}
29-
3039
@Given("^I have a CalculatorActivity$")
3140
public void I_have_a_CalculatorActivity() {
32-
activity = getActivity();
33-
assertNotNull(activity);
41+
assertNotNull(getActivity());
3442
}
3543

3644
@When("^I press (\\d)$")
3745
public void I_press_d(int d) {
46+
CalculatorActivity activity = getActivity();
47+
3848
switch (d) {
3949
case 0:
4050
clickOnView(activity, R.id.btn_d_0);
@@ -71,6 +81,8 @@ public void I_press_d(int d) {
7181

7282
@When("^I press ([+–x\\/=])$")
7383
public void I_press_op(char op) {
84+
CalculatorActivity activity = getActivity();
85+
7486
switch (op) {
7587
case '+':
7688
clickOnView(activity, R.id.btn_op_add);
@@ -92,7 +104,7 @@ public void I_press_op(char op) {
92104

93105
@Then("^I should see (\\S+) on the display$")
94106
public void I_should_see_s_on_the_display(String s) {
95-
TextView display = (TextView) activity.findViewById(R.id.txt_calc_display);
107+
TextView display = (TextView) getActivity().findViewById(R.id.txt_calc_display);
96108
String displayed_result = display.getText().toString();
97109
assertEquals(s, displayed_result);
98110
}

examples/android/cukeulator-test/src/cucumber/example/android/cukeulator/test/RunCukes.java

-14
This file was deleted.

0 commit comments

Comments
 (0)