├── 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 | * 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 | ![Frutilla Logo](https://raw.githubusercontent.com/ignaciotcrespo/frutilla/master/design/frutilla.jpg) 2 | 3 | ![build status](https://img.shields.io/badge/build-info =>-yellow.svg) [![Build Status](https://travis-ci.org/ignaciotcrespo/frutilla.svg?branch=master)](https://travis-ci.org/ignaciotcrespo/frutilla) [![codecov.io](http://codecov.io/github/ignaciotcrespo/frutilla/coverage.svg?branch=master)](http://codecov.io/github/ignaciotcrespo/frutilla?branch=master) [![MIT License](http://img.shields.io/badge/license-MIT-green.svg) ](https://github.com/ignaciotcrespo/frutilla/blob/master/LICENSE) 4 | 5 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Frutilla-blue.svg?style=flat)](http://android-arsenal.com/details/1/2452) 6 | 7 | [![Github Issues](http://githubbadges.herokuapp.com/ignaciotcrespo/frutilla/issues.svg?style=flat-square)](https://github.com/ignaciotcrespo/frutilla/issues) 8 | [![Pending Pull-Requests](http://githubbadges.herokuapp.com/ignaciotcrespo/frutilla/pulls.svg?style=flat-square)](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 | --------------------------------------------------------------------------------