├── android
├── frutilla-android
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ └── values
│ │ │ │ │ └── strings.xml
│ │ │ └── AndroidManifest.xml
│ │ └── androidTest
│ │ │ └── java
│ │ │ └── org
│ │ │ └── frutilla
│ │ │ └── android
│ │ │ ├── FrutillaExamplesWithRuleTest.java
│ │ │ └── FrutillaExamplesWithAnnotationTest.java
│ ├── proguard-rules.pro
│ └── build.gradle
├── example-android-app
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ └── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ └── AndroidManifest.xml
│ │ └── androidTest
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ └── build.gradle
├── settings.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── build.gradle
├── gradle.properties
├── .gitignore
├── gradlew.bat
└── gradlew
├── design
└── frutilla.jpg
├── java
├── .gitignore
├── src
│ ├── main
│ │ └── java
│ │ │ └── org
│ │ │ └── frutilla
│ │ │ ├── ExceptionUtils.java
│ │ │ ├── annotations
│ │ │ └── Frutilla.java
│ │ │ ├── FrutillaRule.java
│ │ │ ├── FrutillaTestRunner.java
│ │ │ └── FrutillaParser.java
│ └── test
│ │ └── java
│ │ └── org
│ │ └── frutilla
│ │ ├── ExceptionUtilsTest.java
│ │ ├── examples
│ │ ├── FrutillaExamplesWithAnnotationTest.java
│ │ └── FrutillaExamplesWithRuleTest.java
│ │ ├── FrutillaParserTest.java
│ │ └── FrutillaParserAnnotationsTest.java
├── build.gradle
├── gradle.properties
└── maven-push.gradle
├── .travis.yml
├── LICENSE
└── README.md
/android/frutilla-android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/example-android-app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':example-android-app', ':frutilla-android'
2 |
--------------------------------------------------------------------------------
/design/frutilla.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ignaciotcrespo/frutilla/HEAD/design/frutilla.jpg
--------------------------------------------------------------------------------
/android/example-android-app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Frutilla
3 |
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ignaciotcrespo/frutilla/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/frutilla-android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FrutillaAndroid
3 |
4 |
--------------------------------------------------------------------------------
/android/example-android-app/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/java/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
2 |
3 | *.iml
4 |
5 | ## Directory-based project format:
6 | .idea/
7 | out/
8 | javadocs/
--------------------------------------------------------------------------------
/android/frutilla-android/src/androidTest/java/org/frutilla/android/FrutillaExamplesWithRuleTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla.android;
2 |
3 | public class FrutillaExamplesWithRuleTest extends org.frutilla.examples.FrutillaExamplesWithRuleTest {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/android/frutilla-android/src/androidTest/java/org/frutilla/android/FrutillaExamplesWithAnnotationTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla.android;
2 |
3 | public class FrutillaExamplesWithAnnotationTest extends org.frutilla.examples.FrutillaExamplesWithAnnotationTest {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/android/example-android-app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 09 09:37:15 CEST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: java
3 | script: gradle -b java/build.gradle check
4 | jdk:
5 | - openjdk7
6 |
7 | # get us on the new container based builds, we're not using any fancyness
8 | sudo: false
9 |
10 | branches:
11 | only:
12 | - master
13 | notifications:
14 | email:
15 | - itcrespo@gmail.com
16 |
17 | after_success:
18 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/android/example-android-app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/android/frutilla-android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.3.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/android/frutilla-android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/crespo/tools/android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/android/example-android-app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/crespo/tools/android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/android/frutilla-android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.0"
6 | defaultConfig {
7 | minSdkVersion 10
8 | targetSdkVersion 23
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 | productFlavors {
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:23.0.0'
25 | compile 'com.github.ignaciotcrespo:frutilla:0.7.0'
26 | //testCompile 'junit:junit:4.12'
27 | androidTestCompile 'com.android.support.test:runner:0.3'
28 | }
29 |
--------------------------------------------------------------------------------
/android/example-android-app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.0"
6 | defaultConfig {
7 | applicationId "org.frutilla.example.android"
8 | minSdkVersion 10
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | productFlavors {
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:appcompat-v7:23.0.0'
26 | testCompile 'junit:junit:4.12'
27 | testCompile 'com.github.ignaciotcrespo:frutilla:0.7.0'
28 | testCompile project(':frutilla-android')
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/java/src/main/java/org/frutilla/ExceptionUtils.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | /**
6 | * Utilities for java exceptions.
7 | */
8 | class ExceptionUtils {
9 |
10 | /**
11 | * Addes a message at the beginning of the stacktrace.
12 | */
13 | public static void insertMessage(Throwable onObject, String msg) {
14 | try {
15 | Field field = Throwable.class.getDeclaredField("detailMessage"); //Method("initCause", new Class[]{Throwable.class});
16 | field.setAccessible(true);
17 | if (onObject.getMessage() != null) {
18 | field.set(onObject, "\n[\n" + msg + "\n]\n[\nMessage: " + onObject.getMessage() + "\n]");
19 | } else {
20 | field.set(onObject, "\n[\n" + msg + "]\n");
21 | }
22 | } catch (RuntimeException e) {
23 | throw e;
24 | } catch (Exception e) {
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/java/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'jacoco'
3 |
4 | buildscript {
5 | repositories {
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:1.3.0'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 | compile 'junit:junit:4.12'
26 | testCompile "org.mockito:mockito-core:1.10.19"
27 | testCompile 'org.jacoco:org.jacoco.core:0.7.5.201505241946'
28 | }
29 |
30 | // thanks to: http://vorba.ch/2015/java-gradle-travis-jacoco-codecov.html
31 | jacocoTestReport {
32 | reports {
33 | xml.enabled = true
34 | html.enabled = true
35 | }
36 | }
37 |
38 | check.dependsOn jacocoTestReport
39 |
40 |
41 | apply from: 'maven-push.gradle'
--------------------------------------------------------------------------------
/java/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Frutilla
2 | POM_ARTIFACT_ID=frutilla
3 | POM_PACKAGING=aar
4 | VERSION_NAME=0.7.1
5 | VERSION_CODE=71
6 | GROUP=com.github.ignaciotcrespo
7 |
8 | POM_DESCRIPTION=Frutilla lets java software development teams describe the tests in plain text, and link them to the specifications.
9 | POM_URL=https://github.com/ignaciotcrespo/frutilla
10 | POM_SCM_URL=https://github.com/ignaciotcrespo/frutilla.git
11 | POM_SCM_CONNECTION=https://github.com/ignaciotcrespo/frutilla.git
12 | POM_SCM_DEV_CONNECTION=https://github.com/ignaciotcrespo/frutilla.git
13 | POM_LICENCE_NAME=MIT License
14 | POM_LICENCE_URL=http://www.opensource.org/licenses/mit-license.php
15 | POM_LICENCE_DIST=repo
16 | POM_DEVELOPER_ID=itcrespo
17 | POM_DEVELOPER_NAME=Ignacio Tomas Crespo
18 |
19 | NEXUS_USERNAME=
20 | NEXUS_PASSWORD=
21 |
22 | SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots
23 | RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2
24 |
25 | signing.keyId=49BE6234
26 | signing.password=
27 | signing.secretKeyRingFile=/Users/crespo/.gnupg/secring.gpg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2015 Frutilla contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
2 |
3 | *.iml
4 |
5 | ## Directory-based project format:
6 | .idea/
7 | # if you remove the above rule, at least ignore the following:
8 |
9 | # User-specific stuff:
10 | # .idea/workspace.xml
11 | # .idea/tasks.xml
12 | # .idea/dictionaries
13 |
14 | # Sensitive or high-churn files:
15 | # .idea/dataSources.ids
16 | # .idea/dataSources.xml
17 | # .idea/sqlDataSources.xml
18 | # .idea/dynamic.xml
19 | # .idea/uiDesigner.xml
20 |
21 | # Gradle:
22 | # .idea/gradle.xml
23 | # .idea/libraries
24 |
25 | # Mongo Explorer plugin:
26 | # .idea/mongoSettings.xml
27 |
28 | ## File-based project format:
29 | *.ipr
30 | *.iws
31 |
32 | ## Plugin-specific files:
33 |
34 | # IntelliJ
35 | /out/
36 |
37 | # mpeltonen/sbt-idea plugin
38 | .idea_modules/
39 |
40 | # JIRA plugin
41 | atlassian-ide-plugin.xml
42 |
43 | # Crashlytics plugin (for Android Studio and IntelliJ)
44 | com_crashlytics_export_strings.xml
45 | crashlytics.properties
46 | crashlytics-build.properties
47 |
48 | .gradle
49 | /local.properties
50 | /.idea/workspace.xml
51 | /.idea/libraries
52 | .DS_Store
53 | /build
54 | /captures
55 |
--------------------------------------------------------------------------------
/java/src/test/java/org/frutilla/ExceptionUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 |
7 | import static org.frutilla.ExceptionUtils.insertMessage;
8 | import static org.junit.Assert.assertTrue;
9 |
10 | @RunWith(value = FrutillaTestRunner.class)
11 | public class ExceptionUtilsTest {
12 |
13 | public static final String MESSAGE = "original message";
14 | public static final String NEW_MESSAGE = "new inserted message";
15 |
16 | @Frutilla(
17 | Given = "an exception",
18 | When = "I insert a text in the exception message",
19 | Then = {"the new text must exist in the exception message",
20 | "and the original message must not be deleted"
21 | }
22 | )
23 | @Test
24 | public void testInsertMessage() throws Exception {
25 | final RuntimeException exception = new RuntimeException(MESSAGE);
26 |
27 | insertMessage(exception, NEW_MESSAGE);
28 |
29 | assertTrue(exception.getMessage().contains(MESSAGE));
30 | assertTrue(exception.getMessage().contains(NEW_MESSAGE));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/java/src/main/java/org/frutilla/annotations/Frutilla.java:
--------------------------------------------------------------------------------
1 | package org.frutilla.annotations;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | /**
10 | * Describes a test using sentences in plain text, similar to Cucumber.
11 | * This description is included in many places:
12 | *
13 | * - the stacktrace of the failed tests
14 | * - the javadocs
15 | * - the logs in the console
16 | *
17 | */
18 | @Retention(RetentionPolicy.RUNTIME)
19 | @Target(ElementType.METHOD)
20 | @Documented
21 | public @interface Frutilla {
22 |
23 | /**
24 | * Describes the scenario of the use case.
25 | * @return the sentences for the scenario
26 | */
27 | String[] Scenario() default "";
28 |
29 | /**
30 | * Describes the entry point of the use case.
31 | * @return a sentence or group of sentences
32 | */
33 | String[] Given();
34 |
35 | /**
36 | * Describes what the use case does, the main action of the use case.
37 | * @return a sentence or group of sentences
38 | */
39 | String[] When();
40 |
41 | /**
42 | * Describes what is the expected behavior after doing the main action.
43 | * @return a sentence or group of sentences
44 | */
45 | String[] Then();
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/java/src/test/java/org/frutilla/examples/FrutillaExamplesWithAnnotationTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla.examples;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 |
7 | import static org.junit.Assert.assertTrue;
8 |
9 | /**
10 | * Examples using Frutilla with annotations.
11 | */
12 | @RunWith(value = org.frutilla.FrutillaTestRunner.class)
13 | public class FrutillaExamplesWithAnnotationTest {
14 |
15 | @Frutilla(
16 | Given = "a test with Frutilla annotations",
17 | When = "it fails",
18 | Then = {
19 | "it shows the test description in the stacktrace",
20 | "and in the logs"
21 | }
22 | )
23 | @Test
24 | public void testFailed() {
25 | // assertTrue(false);
26 | }
27 |
28 | @Frutilla(
29 | Given = "a test with Frutilla annotations",
30 | When = "it fails due to error",
31 | Then = {
32 | "it shows the test description in the stacktrace",
33 | "and in the logs"
34 | }
35 | )
36 | @Test
37 | public void testError() {
38 | // throw new RuntimeException("forced error");
39 | }
40 |
41 | @Frutilla(
42 | Given = "a test with Frutilla annotations",
43 | When = "it passes",
44 | Then = "it shows the test description in the logs"
45 | )
46 | @Test
47 | public void testPassed() {
48 | assertTrue(true);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/java/src/main/java/org/frutilla/FrutillaRule.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.junit.rules.TestWatcher;
4 | import org.junit.runner.Description;
5 |
6 | /**
7 | * JUnit rule to write Frutilla sentences at the beginning of tests.
8 | */
9 | public class FrutillaRule extends TestWatcher {
10 |
11 | private FrutillaParser.AbstractSentence mRoot;
12 |
13 | /**
14 | * Starts writing sentences. This is the description of the scenario of the use case.
15 | * To add more sentences to the scenario use
16 | * {@link org.frutilla.FrutillaParser.Scenario#and(String)}
17 | * {@link org.frutilla.FrutillaParser.Scenario#but(String)}
18 | * @param text the sentence.
19 | * @return a {@link org.frutilla.FrutillaParser.Given} to continue writing sentences.
20 | */
21 | public FrutillaParser.Scenario scenario(String text) {
22 | mRoot = FrutillaParser.scenario(text);
23 | return (FrutillaParser.Scenario) mRoot;
24 | }
25 |
26 | /**
27 | * Starts writing sentences. This is the entry point of the use case.
28 | * To add more sentences to the entry point use
29 | * {@link org.frutilla.FrutillaParser.Given#and(String)}
30 | * {@link org.frutilla.FrutillaParser.Given#but(String)}
31 | * @param text the sentence.
32 | * @return a {@link org.frutilla.FrutillaParser.Given} to continue writing sentences.
33 | */
34 | public FrutillaParser.Given given(String text) {
35 | mRoot = FrutillaParser.given(text);
36 | return (FrutillaParser.Given) mRoot;
37 | }
38 |
39 | @Override
40 | protected void failed(Throwable e, Description description) {
41 | if (mRoot != null && !mRoot.isEmpty()) {
42 | ExceptionUtils.insertMessage(e, mRoot.popSentence());
43 | }
44 | super.failed(e, description);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/java/src/test/java/org/frutilla/examples/FrutillaExamplesWithRuleTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla.examples;
2 |
3 | import org.frutilla.FrutillaRule;
4 | import org.junit.Rule;
5 | import org.junit.Test;
6 |
7 | import static org.junit.Assert.assertTrue;
8 |
9 | /**
10 | * Examples using Frutilla with JUnit rules.
11 | */
12 | public class FrutillaExamplesWithRuleTest {
13 |
14 | @Rule
15 | public FrutillaRule mScenario = new FrutillaRule();
16 |
17 | @Test
18 | public void testPassed() throws Exception {
19 | mScenario.given("there is a test").and("uses frutilla rule")
20 | .when("result is passed")
21 | .then("must be passed and displayed");
22 |
23 | assertTrue(true);
24 | }
25 |
26 | @Test
27 | public void testPassedWithoutEnd() throws Exception {
28 | mScenario.given("there is a test").and("uses frutilla rule")
29 | .when("result is passed").and("has no end")
30 | .then("result must be marked as passed");
31 |
32 | assertTrue(true);
33 | }
34 |
35 | @Test
36 | public void testFailedWithoutEnd() throws Exception {
37 | mScenario.given("there is a test").and("uses frutilla rule")
38 | .when("result is failed")
39 | .then("must be marked as failed");
40 |
41 | // assertTrue(false);
42 | }
43 |
44 | @Test
45 | public void testErrorWithoutEnd() throws Exception {
46 | mScenario.given("there is a test").and("uses frutilla rule")
47 | .when("result is error").then("must be marked as error");
48 |
49 | // throw new RuntimeException();
50 | }
51 |
52 | @Test
53 | public void testFailed() throws Exception {
54 | mScenario.given("there is a test").and("uses frutilla rule")
55 | .when("result is failed")
56 | .then("must be failed and displayed");
57 |
58 | // assertTrue(false);
59 | }
60 |
61 | @Test
62 | public void testError() throws Exception {
63 | mScenario.given("there is a test").and("uses frutilla rule")
64 | .when("result is error")
65 | .then("must be error and displayed");
66 |
67 | // throw new RuntimeException("forced exception");
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/java/src/main/java/org/frutilla/FrutillaTestRunner.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 | import org.junit.runners.BlockJUnit4ClassRunner;
5 | import org.junit.runners.model.FrameworkMethod;
6 | import org.junit.runners.model.InitializationError;
7 | import org.junit.runners.model.Statement;
8 |
9 | import static org.frutilla.FrutillaParser.given;
10 | import static org.frutilla.FrutillaParser.scenario;
11 |
12 | /**
13 | * Process Frutilla annotations in top of tests.
14 | */
15 | public class FrutillaTestRunner extends BlockJUnit4ClassRunner {
16 |
17 | /**
18 | * Constructor inherited from {@link BlockJUnit4ClassRunner}
19 | */
20 | public FrutillaTestRunner(Class> klass) throws InitializationError {
21 | super(klass);
22 | }
23 |
24 | @Override
25 | protected Statement methodBlock(FrameworkMethod method) {
26 | return new FrutillaStatement(super.methodBlock(method), method);
27 | }
28 |
29 | class FrutillaStatement extends Statement {
30 |
31 | private final Statement mStatement;
32 | private final FrameworkMethod mMethod;
33 |
34 | public FrutillaStatement(Statement statement, FrameworkMethod method) {
35 | mStatement = statement;
36 | mMethod = method;
37 | }
38 |
39 | @Override
40 | public void evaluate() throws Throwable {
41 | String text = preEvaluate();
42 | try {
43 | mStatement.evaluate();
44 | } catch (Throwable exc) {
45 | if (!text.isEmpty()) {
46 | ExceptionUtils.insertMessage(exc, text);
47 | }
48 | throw exc;
49 | }
50 | }
51 |
52 | private String preEvaluate() {
53 | StringBuffer sb = new StringBuffer();
54 | sb.append("" + "\n");
55 | sb.append("/------------------------------------------\\" + "\n");
56 | sb.append("Test: " + mMethod.getName() + "\n");
57 | sb.append("--------------------------------------------" + "\n");
58 | String text = "";
59 | try {
60 | text = scenario(mMethod.getAnnotation(Frutilla.class));
61 | } catch (Throwable exc) {
62 | // do nothing
63 | }
64 | if (!text.isEmpty()) {
65 | sb.append(text + "\n");
66 | }
67 | sb.append("\\------------------------------------------/" + "\n");
68 | sb.append("" + "\n");
69 | System.out.println(sb.toString());
70 | return text;
71 | }
72 |
73 | }
74 |
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/java/maven-push.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 |
20 | def isReleaseBuild() {
21 | return VERSION_NAME.contains("SNAPSHOT") == false
22 | }
23 |
24 | def getReleaseRepositoryUrl() {
25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
27 | }
28 |
29 | def getSnapshotRepositoryUrl() {
30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
31 | : "https://oss.sonatype.org/content/repositories/snapshots/"
32 | }
33 |
34 | def getRepositoryUsername() {
35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
36 | }
37 |
38 | def getRepositoryPassword() {
39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
40 | }
41 |
42 | afterEvaluate { project ->
43 | uploadArchives {
44 | repositories {
45 | mavenDeployer {
46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
47 |
48 | pom.groupId = GROUP
49 | pom.artifactId = POM_ARTIFACT_ID
50 | pom.version = VERSION_NAME
51 |
52 | repository(url: getReleaseRepositoryUrl()) {
53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
54 | }
55 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
57 | }
58 |
59 | pom.project {
60 | name POM_NAME
61 | packaging POM_PACKAGING
62 | description POM_DESCRIPTION
63 | url POM_URL
64 |
65 | scm {
66 | url POM_SCM_URL
67 | connection POM_SCM_CONNECTION
68 | developerConnection POM_SCM_DEV_CONNECTION
69 | }
70 |
71 | licenses {
72 | license {
73 | name POM_LICENCE_NAME
74 | url POM_LICENCE_URL
75 | distribution POM_LICENCE_DIST
76 | }
77 | }
78 |
79 | developers {
80 | developer {
81 | id POM_DEVELOPER_ID
82 | name POM_DEVELOPER_NAME
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | signing {
91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
92 | sign configurations.archives
93 | }
94 |
95 | //task androidJavadocs(type: Javadoc) {
96 | //source = android.sourceSets.main.allJava
97 | //}
98 |
99 | task myJavadocs(type: Javadoc) {
100 | source = sourceSets.main.allJava
101 | }
102 |
103 | task javadocsJar(type: Jar, dependsOn: myJavadocs) {
104 | classifier = 'javadoc'
105 | from myJavadocs.destinationDir
106 | }
107 | //
108 | task sourcesJar(type: Jar, dependsOn:classes) {
109 | classifier = 'sources'
110 | from sourceSets.main.allJava
111 | //sourceSets.main.java.sourceFiles
112 | }
113 |
114 | artifacts {
115 | archives javadocsJar
116 | archives sourcesJar
117 | }
118 | }
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |  [](https://travis-ci.org/ignaciotcrespo/frutilla) [](http://codecov.io/github/ignaciotcrespo/frutilla?branch=master) [ ](https://github.com/ignaciotcrespo/frutilla/blob/master/LICENSE)
4 |
5 | [](http://android-arsenal.com/details/1/2452)
6 |
7 | [](https://github.com/ignaciotcrespo/frutilla/issues)
8 | [](https://github.com/ignaciotcrespo/frutilla/pulls)
9 |
10 | # Frutilla 0.7.1
11 |
12 | `UPDATE: this started as an experiment in my projects and I received a lot of nice feedback about it, positive and negative. I love all the technical knowledge I exchanged with many of you. This project is deprecated now, after a long technical session I agree the best way to see useful information in the UT is to add detailed messages in the assertions. I will change Frutilla, analyzing how to generate a report extracting the javadocs directly from the source of the tests.`
13 |
14 | Frutilla lets java software development teams describe the tests in plain text, and link them to the specifications.
15 |
16 | I like the **[Cucumber](https://cucumber.io/)** way to describe tests using **GIVEN + WHEN + THEN** sentences, and I think JUnit needs something to help UT to be more descriptive. Cucumber has a java API but I think it is very complex to use, linking sentences to java methods. Creating a UT should be a simple task.
17 |
18 | Tests must be readable and less lines of code is better. I really appreciate a self descriptive test, you know exactly the use case in seconds. It doesnt matter if it is a small unit test or an integration test, a proper description is always welcome. The problem I have found is **the readable code can not be read in the test reports**, and sometimes the reports are opened by people without access to the code, or you as developer received a report but temporary dont have access to the code.
19 |
20 | Also sometimes I really appreciate a javadoc in top of a UT describing what is being tested, some tests are hard to write in a readable way, could be due to the poor architecture of the current code. The same disadvantage than before, a javadoc cant be included in the .class file.
21 |
22 | I created 2 ways or flavors of adding descriptions that will be included in the compiled classes: with annotations and with JUnit rules.
23 |
24 | # Flavor 1: Annotations
25 |
26 | Using annotations needs a specific runner and looks like the following:
27 |
28 | ```java
29 | @RunWith(value = org.frutilla.FrutillaTestRunner.class)
30 | public class FrutillaExamplesWithAnnotationTest {
31 |
32 | @Frutilla(
33 | Given = "a test with Frutilla annotations",
34 | When = "it fails due to an error",
35 | Then = "it shows the test description in the stacktrace"
36 | )
37 | @Test
38 | public void testError() {
39 | throw new RuntimeException("forced error");
40 | }
41 |
42 | }
43 | ```
44 |
45 | It supports also adding **AND** sentences on every block GIVEN, WHEN or THEN.
46 |
47 | One advantage of using annotations is they can be collapsed by default, and the test in your IDE will be
48 |
49 | ```java
50 | @{...}
51 | public void testError() {
52 | throw new RuntimeException("forced error");
53 | }
54 | ```
55 |
56 | # Flavor 2: JUnit rules
57 |
58 | In case annotations is not your cup of tea I included a way to do it using the powerful JUnit rules. In this case there is no need to run with FrutillaTestRunner, but the rule needs to be declared.
59 |
60 | ```java
61 | public class FrutillaExamplesWithRuleTest {
62 |
63 | @Rule
64 | public FrutillaRule mScenario = new FrutillaRule();
65 |
66 | @Test
67 | public void testError() throws Exception {
68 | mScenario.given("a test with Frutilla rule")
69 | .when("it fails due to and error")
70 | .then("it shows the test description in the stacktrace").end();
71 |
72 | throw new RuntimeException("forced exception");
73 | }
74 | ```
75 |
76 | I see pretty invasive to include the description inside the test, but the alternative is there for you if you like it. Another disadvantage is you can not collapse the descriptions block.
77 |
78 | # The stacktrace in the reports
79 |
80 | What I added to test reports is the description in top of the stacktrace errors. I dont care the tests that passed, I care about those that failed, and I want to know fast what is the problem.
81 | Using Frutilla the stacktrace looks like this:
82 |
83 | java.lang.RuntimeException:
84 | [
85 | GIVEN a test with Frutilla annotations
86 | WHEN it fails due to an error
87 | THEN it shows the test description in the stacktrace
88 | ]
89 | [
90 | Message: forced error
91 | ]
92 | at org.frutilla.android.FrutillaExamplesWithAnnotationTest.testError(FrutillaExamplesWithAnnotationTest.java:38)
93 | at java.lang.reflect.Method.invokeNative(Native Method)
94 | at java.lang.reflect.Method.invoke(Method.java:511)
95 | ...
96 |
97 | It is helpful to see those descriptions in CI servers like jenkins. I really hate to see a failed UT in jenkins and start searching for it in the code to understand what is doing due to the name is something like "testParsingDataValidWhenNoUser". WTF, that method name can be a hundred of things. What is the data? Why is not valid? And more.
98 | With the proper description I know exactly what is failing, and if the descriptions are linked to the specifications then we are 1 click of knowing the complete scenario to understand the problem.
99 |
100 | # Android
101 |
102 | Frutilla can be used also in **Android** using the excellent JUnit4 instrumentation runner **[AndroidJUnitRunner](http://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)**
103 |
104 | Add it in gradle using:
105 |
106 | androidTestCompile 'com.android.support.test:runner:0.3'
107 |
108 | and add the instrumentation in your manifest
109 |
110 |
113 |
114 | # Installation
115 |
116 | Use the group id for maven and gradle, e.g.:
117 |
118 | ```
119 | testCompile 'com.github.ignaciotcrespo:frutilla:0.7.1'
120 | ```
121 |
122 | ***
123 |
124 | Frutilla is still in development, but functional. I appreciate your feedback to itcrespo@gmail.com
125 |
126 | Pending:
127 | - exporting xml/html reports with the descriptions
128 | - linking reports to official specifications
129 |
130 | ***
131 |
132 | # Why Frutilla?
133 |
134 | "Frutilla" means strawberry in spanish, in my land when something good was added to another good thing we say "es la frutilla del postre", similar to "the icing on the cake".
135 |
136 | JUnit is good, just needs some flavor on it ;)
137 |
138 |
139 |
--------------------------------------------------------------------------------
/java/src/test/java/org/frutilla/FrutillaParserTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 | import org.junit.After;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 |
9 | import static org.frutilla.FrutillaParser.given;
10 | import static org.frutilla.FrutillaParser.has;
11 | import static org.frutilla.FrutillaParser.reset;
12 | import static org.frutilla.FrutillaParser.scenario;
13 | import static org.junit.Assert.assertEquals;
14 | import static org.junit.Assert.assertFalse;
15 | import static org.junit.Assert.assertTrue;
16 |
17 | @RunWith(value = FrutillaTestRunner.class)
18 | public class FrutillaParserTest {
19 |
20 | private static final String GIVEN = "given";
21 | private static final String WHEN = "when";
22 | private static final String THEN = "then";
23 | private static final String ONE = "one";
24 | private static final String TWO = "two";
25 | private static final String THREE = "three";
26 | public static final String BUT = "pero";
27 | public static final String SCENARIO = "my scenario desc";
28 |
29 | @Before
30 | public void setUp() throws Exception {
31 | reset();
32 | }
33 |
34 | // --- TESTS BEGIN -----------------------------------------------------------------------------
35 |
36 | @Frutilla(
37 | Scenario = "FrutillaParser not used",
38 | Given = "nothing declared",
39 | When = "checking if something exists",
40 | Then = "nothing exists"
41 | )
42 | @Test
43 | public void testEmptyByDefault() throws Exception {
44 | assertTrue(FrutillaParser.isEmpty());
45 | }
46 |
47 | // ---------------------------------------------------------------------------------------------
48 |
49 | @Frutilla(
50 | Given = "a 'given' sentence",
51 | When = "is declared",
52 | Then = "the sentence exists"
53 | )
54 | @Test
55 | public void testGiven() throws Exception {
56 | given(GIVEN);
57 |
58 | assertHas(GIVEN);
59 | }
60 |
61 | // ---------------------------------------------------------------------------------------------
62 |
63 | @Frutilla(
64 | Given = "multiple 'given' sentences",
65 | When = "are added",
66 | Then = "all sentences exist"
67 | )
68 | @Test
69 | public void testGivenMultiple() throws Exception {
70 | given(GIVEN).and(ONE).and(TWO);
71 |
72 | assertHas(ONE);
73 | assertHas(TWO);
74 | }
75 |
76 | // ---------------------------------------------------------------------------------------------
77 |
78 | @Frutilla(
79 | Given = "multiple 'given' sentences",
80 | When = "are added to the same 'given' instance variable",
81 | Then = "all sentences exist"
82 | )
83 | @Test
84 | public void testGivenMultipleMultiline() throws Exception {
85 | FrutillaParser.Given given = given(GIVEN);
86 | given.and(ONE);
87 | given.and(TWO);
88 |
89 | assertHas(ONE);
90 | assertHas(TWO);
91 | }
92 |
93 | // ---------------------------------------------------------------------------------------------
94 |
95 | @Frutilla(
96 | Given = {
97 | "all kind of sentences",
98 | "BUT starting with 'given'"
99 | },
100 | When = "are added",
101 | Then = "all exist"
102 | )
103 | @Test
104 | public void testAllTogetherStartingWithGiven() throws Exception {
105 | given(GIVEN).and(ONE).when(WHEN).and(TWO).then(THEN).and(THREE);
106 |
107 | assertHas(GIVEN);
108 | assertHas(WHEN);
109 | assertHas(THEN);
110 | assertHas(ONE);
111 | assertHas(TWO);
112 | assertHas(THREE);
113 | }
114 |
115 | @Frutilla(
116 | Given = "all kind of sentences",
117 | When = "are added",
118 | Then = "all exist"
119 | )
120 | @Test
121 | public void testAllTogetherStartingWithScenario() throws Exception {
122 | scenario(SCENARIO).given(GIVEN).and(ONE).when(WHEN).and(TWO).then(THEN).and(THREE);
123 |
124 | assertHas(SCENARIO);
125 | assertHas(GIVEN);
126 | assertHas(WHEN);
127 | assertHas(THEN);
128 | assertHas(ONE);
129 | assertHas(TWO);
130 | assertHas(THREE);
131 | }
132 |
133 | // ---------------------------------------------------------------------------------------------
134 |
135 | @Frutilla(
136 | Given = "some sentences added",
137 | When = "clear all sentences",
138 | Then = "there are no sentences"
139 | )
140 | @Test
141 | public void testReset() throws Exception {
142 | given(GIVEN).and(ONE).when(WHEN).and(TWO).then(THEN).and(THREE);
143 |
144 | reset();
145 |
146 | assertFalse(has(GIVEN));
147 | assertFalse(has(WHEN));
148 | assertFalse(has(THEN));
149 | assertFalse(has(ONE));
150 | assertFalse(has(TWO));
151 | assertFalse(has(THREE));
152 | }
153 |
154 | // ---------------------------------------------------------------------------------------------
155 |
156 | @Frutilla(
157 | Given = "some sentences are added",
158 | When = "getting complete sentence",
159 | Then = {
160 | "the sentence is correct",
161 | "and is empty after reading sentence"
162 | }
163 | )
164 | @Test
165 | public void testPopSentence() throws Exception {
166 | given(GIVEN).and(ONE).but(BUT).when(WHEN).and(TWO).then(THEN).and(THREE);
167 |
168 | final String sentence = String.format("GIVEN %s\n AND %s\n BUT %s\nWHEN %s\n AND %s\nTHEN %s\n AND %s",
169 | GIVEN, ONE, BUT, WHEN, TWO, THEN, THREE);
170 | assertEquals(sentence, FrutillaParser.popSentence());
171 | assertTrue(FrutillaParser.isEmpty());
172 | }
173 |
174 | // ---------------------------------------------------------------------------------------------
175 |
176 | @Frutilla(
177 | Given = "a 'when' sentence",
178 | When = "is declared",
179 | Then = "the sentence exists"
180 | )
181 | @Test
182 | public void testWhen() throws Exception {
183 | given(GIVEN).when(WHEN);
184 |
185 | assertHas(WHEN);
186 | }
187 |
188 | // ---------------------------------------------------------------------------------------------
189 |
190 | @Frutilla(
191 | Given = "multiple 'when' sentences",
192 | When = "are added",
193 | Then = "all sentences exist"
194 | )
195 | @Test
196 | public void testWhenMultiple() throws Exception {
197 | given(GIVEN).when(WHEN).and(ONE).and(TWO);
198 |
199 | assertHas(ONE);
200 | assertHas(TWO);
201 | }
202 |
203 |
204 | // ---------------------------------------------------------------------------------------------
205 |
206 | @Frutilla(
207 | Given = "a 'then' sentence",
208 | When = "is declared",
209 | Then = "the sentence exists"
210 | )
211 | @Test
212 | public void testThen() throws Exception {
213 | given(GIVEN).when(WHEN).then(THEN);
214 |
215 | assertHas(THEN);
216 | }
217 |
218 | // ---------------------------------------------------------------------------------------------
219 |
220 | @Frutilla(
221 | Given = "multiple 'then' sentences",
222 | When = "are added",
223 | Then = "all sentences exist"
224 | )
225 | @Test
226 | public void testThenMultiple() throws Exception {
227 | given(GIVEN).when(WHEN).then(THEN).and(ONE).and(TWO);
228 |
229 | assertHas(ONE);
230 | assertHas(TWO);
231 | }
232 |
233 | // ---------------------------------------------------------------------------------------------
234 |
235 | @Frutilla(
236 | Given = "a 'scenario' sentence",
237 | When = "is added",
238 | Then = "the sentence exist"
239 | )
240 | @Test
241 | public void testScenario() throws Exception {
242 | scenario(SCENARIO);
243 |
244 | assertHas(SCENARIO);
245 | }
246 |
247 | // ---------------------------------------------------------------------------------------------
248 |
249 | @Frutilla(
250 | Given = {
251 | "a 'scenario' sentence",
252 | "AND a 'given' sentence"
253 | },
254 | When = "are added",
255 | Then = "the sentences exist"
256 | )
257 | @Test
258 | public void testScenarioGiven() throws Exception {
259 | scenario(SCENARIO).given(GIVEN);
260 |
261 | assertHas(SCENARIO);
262 | assertHas(GIVEN);
263 | }
264 |
265 | // ---------------------------------------------------------------------------------------------
266 |
267 | @Frutilla(
268 | Given = {
269 | "a 'scenario' sentence",
270 | "AND an 'and' sentence",
271 | "AND a 'but' sentence"
272 | },
273 | When = "are added",
274 | Then = "the sentences exist"
275 | )
276 | @Test
277 | public void testScenarioAndBut() throws Exception {
278 | scenario(SCENARIO).and(ONE).but(TWO);
279 |
280 | assertHas(SCENARIO);
281 | assertHas(ONE);
282 | assertHas(TWO);
283 | }
284 |
285 | // --- TESTS END -------------------------------------------------------------------------------
286 |
287 | @After
288 | public void tearDown() throws Exception {
289 | reset();
290 | }
291 |
292 | private void assertHas(String sentence) {
293 | assertTrue(has(sentence));
294 | }
295 |
296 | }
297 |
--------------------------------------------------------------------------------
/java/src/main/java/org/frutilla/FrutillaParser.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 |
5 | import java.util.LinkedList;
6 | import java.util.List;
7 | import java.util.Locale;
8 |
9 | /**
10 | * Parses the Frutilla JUnit sentences.
11 | */
12 | class FrutillaParser {
13 |
14 | private static final String AND = " AND";
15 | private static final String BUT = " BUT";
16 | private static AbstractSentence sRoot;
17 |
18 | /**
19 | * Describes the scenario of the use case.
20 | *
21 | * @param text the sentence describing the scenario
22 | * @return a Scenario object to conitnue adding sentences
23 | */
24 | static Scenario scenario(String text) {
25 | reset();
26 | final Scenario scenario = new Scenario(text);
27 | sRoot = scenario;
28 | return scenario;
29 | }
30 |
31 | /**
32 | * Describes the entry point of the use case.
33 | *
34 | * @param text the sentence describing the entry point
35 | * @return a Given object to continue adding sentences
36 | */
37 | static Given given(String text) {
38 | reset();
39 | final Given given = new Given(text);
40 | sRoot = given;
41 | return given;
42 | }
43 |
44 | static boolean has(String sentence) {
45 | return sRoot != null && sRoot.has(sentence);
46 | }
47 |
48 | static void reset() {
49 | if (sRoot != null) {
50 | sRoot.reset();
51 | sRoot = null;
52 | }
53 | }
54 |
55 | static boolean isEmpty() {
56 | return sRoot == null || sRoot.isEmpty();
57 | }
58 |
59 | static String popSentence() {
60 | if (sRoot != null) {
61 | return sRoot.popSentence();
62 | }
63 | return "";
64 | }
65 |
66 | static String scenario(Frutilla annotation) {
67 | String value = "";
68 | if (annotation != null) {
69 | final Scenario scenario = new Scenario(annotation.Scenario());
70 | scenario.given(annotation.Given())
71 | .when(annotation.When())
72 | .then(annotation.Then());
73 | value = scenario.popSentence();
74 | }
75 | return value;
76 | }
77 |
78 | //----------------------------------------------------------------------------------------------
79 |
80 | static abstract class AbstractSentence {
81 |
82 | private String mSentence;
83 | private final List mChildren = new LinkedList<>();
84 | private final String mHeader;
85 |
86 | private AbstractSentence(String sentence, String header) {
87 | if (sentence != null && sentence.trim().toLowerCase(Locale.ENGLISH).startsWith(header.trim().toLowerCase(Locale.ENGLISH) + " ")) {
88 | mSentence = sentence.trim().substring((header.trim().toLowerCase(Locale.ENGLISH) + " ").length());
89 | } else {
90 | mSentence = sentence;
91 | }
92 | mHeader = header;
93 | }
94 |
95 | AbstractSentence(String[] sentences, String header) {
96 | this(sentences == null || sentences.length == 0 ? "" : sentences[0], header);
97 | if (sentences != null) {
98 | AbstractSentence child = this;
99 | for (int i = 1; i < sentences.length; i++) {
100 | final String sentence = sentences[i];
101 | if (sentence != null && sentence.trim().toLowerCase(Locale.ENGLISH).startsWith("and ")) {
102 | child = child.and(sentence);
103 | } else if (sentence != null && sentence.trim().toLowerCase(Locale.ENGLISH).startsWith("but ")) {
104 | child = child.but(sentence);
105 | } else {
106 | child = child.and(sentence);
107 | }
108 | }
109 | }
110 | }
111 |
112 | boolean has(String sentence) {
113 | boolean has = mSentence.equals(sentence);
114 | if (!has) {
115 | for (AbstractSentence child : mChildren) {
116 | has = child.has(sentence);
117 | if (has) {
118 | break;
119 | }
120 | }
121 | }
122 | return has;
123 | }
124 |
125 | void reset() {
126 | mSentence = null;
127 | for (AbstractSentence child : mChildren) {
128 | child.reset();
129 | }
130 | mChildren.clear();
131 | }
132 |
133 | /**
134 | * Adds another sentence to the current group, starting with AND.
135 | *
136 | * @param sentence the sentence in plain text
137 | * @return the current group of sentences
138 | */
139 | public abstract AbstractSentence and(String sentence);
140 |
141 | boolean isEmpty() {
142 | return mSentence == null && mChildren.isEmpty();
143 | }
144 |
145 | AbstractSentence addChild(AbstractSentence child) {
146 | mChildren.add(child);
147 | return child;
148 | }
149 |
150 | String popSentence() {
151 | StringBuilder sentence = new StringBuilder();
152 | if (mSentence != null && mSentence.trim().length() > 0) {
153 | sentence.append(header());
154 | sentence.append(" ");
155 | sentence.append(mSentence);
156 | }
157 | for (AbstractSentence child : mChildren) {
158 | final String text = child.popSentence();
159 | if (!text.trim().isEmpty()) {
160 | if (sentence.length() > 0) {
161 | sentence.append("\n");
162 | }
163 | sentence.append(text);
164 | }
165 | }
166 | reset();
167 | return sentence.toString();
168 | }
169 |
170 | String header() {
171 | return mHeader;
172 | }
173 |
174 | /**
175 | * Adds another sentence to the current group, starting with BUT.
176 | *
177 | * @param sentence the sentence in plain text
178 | * @return the current group of sentences
179 | */
180 | public abstract AbstractSentence but(String sentence);
181 |
182 | }
183 |
184 | //----------------------------------------------------------------------------------------------
185 |
186 | /**
187 | * Group of sentences describing the scenario of the use case, using plain text.
188 | */
189 | public static class Scenario extends AbstractSentence {
190 |
191 | private Scenario(String sentence) {
192 | super(sentence, "SCENARIO");
193 | }
194 |
195 | private Scenario(String[] sentences) {
196 | super(sentences, "SCENARIO");
197 | }
198 |
199 | private Scenario(String sentence, String header) {
200 | super(sentence, header);
201 | }
202 |
203 | /**
204 | * Starts describing the action executed in the use case.
205 | *
206 | * @param sentence the sentence in plain text
207 | * @return the current group of sentences
208 | */
209 | public Given given(String sentence) {
210 | Given given = new Given(sentence);
211 | addChild(given);
212 | return given;
213 | }
214 |
215 | Given given(String[] sentences) {
216 | Given given = new Given(sentences);
217 | addChild(given);
218 | return given;
219 | }
220 |
221 |
222 | @Override
223 | public Scenario and(String sentence) {
224 | return (Scenario) addChild(new Scenario(sentence, AND));
225 | }
226 |
227 | @Override
228 | public Scenario but(String sentence) {
229 | return (Scenario) addChild(new Scenario(sentence, BUT));
230 | }
231 |
232 | }
233 |
234 | /**
235 | * Group of sentences describing the entry point of the use case, using plain text.
236 | */
237 | public static class Given extends AbstractSentence {
238 |
239 | private Given(String sentence) {
240 | super(sentence, "GIVEN");
241 | }
242 |
243 | private Given(String[] sentences) {
244 | super(sentences, "GIVEN");
245 | }
246 |
247 | private Given(String sentence, String header) {
248 | super(sentence, header);
249 | }
250 |
251 | /**
252 | * Starts describing the action executed in the use case.
253 | *
254 | * @param sentence the sentence in plain text
255 | * @return the current group of sentences
256 | */
257 | public When when(String sentence) {
258 | When when = new When(sentence);
259 | addChild(when);
260 | return when;
261 | }
262 |
263 | When when(String[] sentences) {
264 | When when = new When(sentences);
265 | addChild(when);
266 | return when;
267 | }
268 |
269 |
270 | @Override
271 | public Given and(String sentence) {
272 | return (Given) addChild(new Given(sentence, AND));
273 | }
274 |
275 | @Override
276 | public Given but(String sentence) {
277 | return (Given) addChild(new Given(sentence, BUT));
278 | }
279 |
280 | }
281 |
282 | //----------------------------------------------------------------------------------------------
283 |
284 | /**
285 | * A group of sentences describing the action to execute in the use case, using plain text.
286 | */
287 | public static class When extends AbstractSentence {
288 |
289 | private When(String sentence) {
290 | super(sentence, "WHEN");
291 | }
292 |
293 | private When(String[] sentences) {
294 | super(sentences, "WHEN");
295 | }
296 |
297 | private When(String sentence, String header) {
298 | super(sentence, header);
299 | }
300 |
301 | /**
302 | * Starts describing in plain text the expected behavior after executing the use case.
303 | *
304 | * @param sentence the sentence in plain text
305 | * @return the current group of sentences
306 | */
307 | public Then then(String sentence) {
308 | return (Then) addChild(new Then(sentence));
309 | }
310 |
311 | Then then(String[] sentences) {
312 | return (Then) addChild(new Then(sentences));
313 | }
314 |
315 | @Override
316 | public When and(String sentence) {
317 | return (When) addChild(new When(sentence, AND));
318 | }
319 |
320 | @Override
321 | public When but(String sentence) {
322 | return (When) addChild(new When(sentence, BUT));
323 | }
324 |
325 |
326 | }
327 |
328 | //----------------------------------------------------------------------------------------------
329 |
330 | /**
331 | * Describes in plain text the expected behavior after executing the use case.
332 | */
333 | public static class Then extends AbstractSentence {
334 |
335 | private Then(String sentence) {
336 | super(sentence, "THEN");
337 | }
338 |
339 | private Then(String[] sentences) {
340 | super(sentences, "THEN");
341 | }
342 |
343 | private Then(String sentence, String header) {
344 | super(sentence, header);
345 | }
346 |
347 | @Override
348 | public Then and(String sentence) {
349 | return (Then) addChild(new Then(sentence, AND));
350 | }
351 |
352 | @Override
353 | public Then but(String sentence) {
354 | return (Then) addChild(new Then(sentence, BUT));
355 | }
356 |
357 | }
358 |
359 | }
360 |
--------------------------------------------------------------------------------
/java/src/test/java/org/frutilla/FrutillaParserAnnotationsTest.java:
--------------------------------------------------------------------------------
1 | package org.frutilla;
2 |
3 | import org.frutilla.annotations.Frutilla;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.mockito.Mock;
8 | import org.mockito.MockitoAnnotations;
9 |
10 | import static org.frutilla.FrutillaParser.reset;
11 | import static org.frutilla.FrutillaParser.scenario;
12 | import static org.junit.Assert.assertEquals;
13 | import static org.mockito.Mockito.when;
14 |
15 | @RunWith(value = FrutillaTestRunner.class)
16 | public class FrutillaParserAnnotationsTest {
17 |
18 | private static final String SCENARIO = "scenario";
19 | private static final String GIVEN = "given";
20 | private static final String WHEN = "when";
21 | private static final String THEN = "then";
22 | private static final String ONE = "one";
23 | private static final String TWO = "two";
24 | public static final String ONETWO = "\n AND " + ONE + "\n AND " + TWO;
25 | public static final String ONEBUTTWO = "\n AND " + ONE + "\n BUT " + TWO;
26 |
27 | @Mock
28 | Frutilla mMockFrutilla;
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | reset();
33 | MockitoAnnotations.initMocks(this);
34 | }
35 |
36 | // --- BEGIN TESTS -----------------------------------------------------------------------------
37 |
38 | @Frutilla(
39 | Given = {
40 | "a 'given' annotation",
41 | "AND has null sentence"
42 | },
43 | When = "process it",
44 | Then = "sentence is empty text"
45 | )
46 | @Test
47 | public void testGivenFromAnnotationNull() throws Exception {
48 | when(mMockFrutilla.Given()).thenReturn(null);
49 | String sentence = scenario(mMockFrutilla);
50 |
51 | assertEquals("", sentence);
52 | }
53 |
54 | // ---------------------------------------------------------------------------------------------
55 |
56 | @Frutilla(
57 | Given = {
58 | "a 'given' annotation",
59 | "AND has empty array sentence"
60 | },
61 | When = "process it",
62 | Then = "sentence is empty text"
63 | )
64 | @Test
65 | public void testGivenFromAnnotationEmpty() throws Exception {
66 | when(mMockFrutilla.Given()).thenReturn(new String[]{});
67 | String sentence = scenario(mMockFrutilla);
68 |
69 | assertEquals("", sentence);
70 | }
71 |
72 | // ---------------------------------------------------------------------------------------------
73 |
74 | @Frutilla(
75 | Given = {
76 | "a 'given' annotation",
77 | "AND has one empty string sentence"
78 | },
79 | When = "process it",
80 | Then = "sentence is empty text"
81 | )
82 | @Test
83 | public void testGivenFromAnnotationEmptyString() throws Exception {
84 | mockGivenAnnotation("");
85 | String sentence = scenario(mMockFrutilla);
86 |
87 | assertEquals("", sentence);
88 | }
89 |
90 | // ---------------------------------------------------------------------------------------------
91 |
92 | @Frutilla(
93 | Given = {
94 | "a 'scenario' annotation",
95 | "AND has a sentence"
96 | },
97 | When = "process it",
98 | Then = "sentence has the correct text"
99 | )
100 | @Test
101 | public void testScenarioFromAnnotation() throws Exception {
102 | mockScenarioAnnotation(SCENARIO);
103 | String sentence = scenario(mMockFrutilla);
104 |
105 | assertEquals("SCENARIO " + SCENARIO, sentence);
106 | }
107 |
108 | // ---------------------------------------------------------------------------------------------
109 |
110 | @Frutilla(
111 | Given = {
112 | "a 'given' annotation",
113 | "AND has a sentence"
114 | },
115 | When = "process it",
116 | Then = "sentence has the correct text"
117 | )
118 | @Test
119 | public void testGivenFromAnnotation() throws Exception {
120 | mockGivenAnnotation(GIVEN);
121 | String sentence = scenario(mMockFrutilla);
122 |
123 | assertEquals("GIVEN " + GIVEN, sentence);
124 | }
125 |
126 | // ---------------------------------------------------------------------------------------------
127 |
128 | @Frutilla(
129 | Given = {
130 | "a 'given' annotation",
131 | "AND has two sentences"
132 | },
133 | When = "process it",
134 | Then = {
135 | "sentence has GIVEN",
136 | "AND the two sentences"
137 | }
138 | )
139 | @Test
140 | public void testGivenMultipleFromAnnotation() throws Exception {
141 | mockGivenAnnotation(GIVEN, ONE, TWO);
142 | String sentence = scenario(mMockFrutilla);
143 |
144 | assertEquals("GIVEN " + GIVEN + ONETWO, sentence);
145 | }
146 |
147 | // ---------------------------------------------------------------------------------------------
148 |
149 | @Frutilla(
150 | Given = {
151 | "a 'scenario' annotation",
152 | "AND has two sentences"
153 | },
154 | When = "process it",
155 | Then = {
156 | "sentence has SCENARIO",
157 | "AND the two sentences"
158 | }
159 | )
160 | @Test
161 | public void testScenarioMultipleFromAnnotation() throws Exception {
162 | mockScenarioAnnotation(SCENARIO, ONE, TWO);
163 | String sentence = scenario(mMockFrutilla);
164 |
165 | assertEquals("SCENARIO " + SCENARIO + ONETWO, sentence);
166 | }
167 |
168 | // ---------------------------------------------------------------------------------------------
169 |
170 | @Frutilla(
171 | Given = {
172 | "a 'given' annotation",
173 | "AND a 'when' annotation",
174 | "AND both have two sentences"
175 | },
176 | When = "process them",
177 | Then = {
178 | "sentence has GIVEN",
179 | "AND sentence has WHEN",
180 | "AND both GIVEN and WHEN have the two sentences"
181 | }
182 | )
183 | @Test
184 | public void testGivenWhenMultipleFromAnnotation() throws Exception {
185 | mockGivenAnnotation(GIVEN, ONE, TWO);
186 | mockWhenAnnotation(WHEN, ONE, TWO);
187 | String sentence = scenario(mMockFrutilla);
188 |
189 | assertEquals("GIVEN " + GIVEN + ONETWO
190 | + "\nWHEN " + WHEN + ONETWO
191 | , sentence);
192 | }
193 |
194 | // ---------------------------------------------------------------------------------------------
195 |
196 | @Frutilla(
197 | Given = {
198 | "a 'given' annotation",
199 | "AND a 'when' annotation",
200 | "AND a 'then' annotation",
201 | "AND all have two sentences"
202 | },
203 | When = "process them",
204 | Then = {
205 | "sentence has GIVEN",
206 | "AND sentence has WHEN",
207 | "AND sentence has THEN",
208 | "AND all GIVEN, WHEN and THEN have the two sentences"
209 | }
210 | )
211 | @Test
212 | public void testGivenWhenThenMultipleFromAnnotation() throws Exception {
213 | mockGivenAnnotation(GIVEN, ONE, TWO);
214 | mockWhenAnnotation(WHEN, ONE, TWO);
215 | mockThenAnnotation(THEN, ONE, TWO);
216 | String sentence = scenario(mMockFrutilla);
217 |
218 | assertEquals("GIVEN " + GIVEN + ONETWO
219 | + "\nWHEN " + WHEN + ONETWO
220 | + "\nTHEN " + THEN + ONETWO
221 | , sentence);
222 | }
223 |
224 | // ---------------------------------------------------------------------------------------------
225 |
226 | @Frutilla(
227 | Given = {
228 | "a 'given' annotation",
229 | "AND a 'when' annotation",
230 | "AND a 'then' annotation",
231 | "AND all have a sentence starting with 'and ' (ignoring case and spaces at left)",
232 | "AND all have a sentence starting with 'but ' (ignoring case and spaces at left)"
233 | },
234 | When = "process them",
235 | Then = {
236 | "sentence has GIVEN",
237 | "AND sentence has WHEN",
238 | "AND sentence has THEN",
239 | "AND all GIVEN, WHEN and THEN have the two sentences",
240 | "AND the sentences starting with 'and ' is reformatted starting with 'AND ' ",
241 | "AND the sentences starting with 'but ' is reformatted starting with 'BUT ' ",
242 | }
243 | )
244 | @Test
245 | public void testGivenWhenThenMultipleFromAnnotationHardcodedAndBut() throws Exception {
246 | mockGivenAnnotation(GIVEN, "and one", " BuT two");
247 | mockWhenAnnotation(WHEN, " And one", "but two");
248 | mockThenAnnotation(THEN, " AND one", "BUT two ");
249 | String sentence = scenario(mMockFrutilla);
250 |
251 | assertEquals("GIVEN " + GIVEN + ONEBUTTWO
252 | + "\nWHEN " + WHEN + ONEBUTTWO
253 | + "\nTHEN " + THEN + ONEBUTTWO
254 | , sentence);
255 | }
256 |
257 | // ---------------------------------------------------------------------------------------------
258 |
259 | @Frutilla(
260 | Given = {
261 | "a 'given' annotation",
262 | "AND a 'when' annotation",
263 | "AND a 'then' annotation",
264 | "AND all have a null sentences"
265 | },
266 | When = "process them",
267 | Then = "sentence is empty"
268 | )
269 | @Test
270 | public void testGivenWhenThenMultipleFromAnnotationAllNull() throws Exception {
271 | mockGivenAnnotation(null, null, null);
272 | mockWhenAnnotation(null, null, null);
273 | mockThenAnnotation(null, null, null);
274 | String sentence = scenario(mMockFrutilla);
275 |
276 | assertEquals("", sentence);
277 | }
278 |
279 | // ---------------------------------------------------------------------------------------------
280 |
281 | @Frutilla(
282 | Given = {
283 | "a 'scenario' annotation",
284 | "AND a 'given' annotation",
285 | "AND a 'when' annotation",
286 | "AND a 'then' annotation",
287 | "AND all have a empty text sentences (ignoring spaces)"
288 | },
289 | When = "process them",
290 | Then = "sentence is empty"
291 | )
292 | @Test
293 | public void testGivenWhenThenMultipleFromAnnotationAllEmpty() throws Exception {
294 | mockScenarioAnnotation(" ", " ", " ");
295 | mockGivenAnnotation("", " ", "");
296 | mockWhenAnnotation(" ", " ", " ");
297 | mockThenAnnotation(" ", "", " ");
298 | String sentence = scenario(mMockFrutilla);
299 |
300 | assertEquals("", sentence);
301 | }
302 |
303 | // --- END TESTS -------------------------------------------------------------------------------
304 |
305 | private void mockScenarioAnnotation(String... text) {
306 | when(mMockFrutilla.Scenario()).thenReturn(text);
307 | }
308 |
309 | private void mockGivenAnnotation(String... text) {
310 | when(mMockFrutilla.Given()).thenReturn(text);
311 | }
312 |
313 | private void mockWhenAnnotation(String... text) {
314 | when(mMockFrutilla.When()).thenReturn(text);
315 | }
316 |
317 | private void mockThenAnnotation(String... text) {
318 | when(mMockFrutilla.Then()).thenReturn(text);
319 | }
320 |
321 | }
322 |
--------------------------------------------------------------------------------