Is it possible to skip a scenario with Cucumber-JV

2020-06-04 04:21发布

I want to add a tag @skiponchrome to a scenario, this should skip the scenario when running a Selenium test with the Chrome browser. The reason to-do this is because some scenario's work in some environments and not in others, this might not even be browser testing specific and could be applied in other situation for example OS platforms.

Example hook:

@Before("@skiponchrome") // this works
public void beforeScenario() {
  if(currentBrowser == 'chrome') { // this works
    // Skip scenario code here
  }
}

I know it is possible to define ~@skiponchrome in the cucumber tags to skip the tag, but I would like to skip a tag at run-time. This way I don't have to think about which steps to skip in advance when I starting a test run on a certain environment.

I would like to create a hook that catches the tag and skips the scenario without reporting a fail/error. Is this possible?

7条回答
够拽才男人
2楼-- · 2020-06-04 05:03

I realized that this is a late update to an already answered question, but I want to add one more option directly supported by cucumber-jvm:

@Before //(cucumber one)
public void setup(){
    Assume.assumeTrue(weAreInPreProductionEnvironment);
}

"and the scenario will be marked as ignored (but the test will pass) if weAreInPreProductionEnvironment is false."

You will need to add

import org.junit.Assume;

The major difference with the accepted answer is that JUnit assume failures behave just like pending

Important Because of a bug fix you will need cucumber-jvm release 1.2.5 which as of this writing is the latest. For example, the above will generate a failure instead of a pending in cucumber-java8-1.2.3.jar

查看更多
Animai°情兽
3楼-- · 2020-06-04 05:06

It's actually really easy. If you dig though the Cucumber-JVM and JUnit 4 source code, you'll find that JUnit makes skipping during runtime very easy (just undocumented).

Take a look at the following source code for JUnit 4's ParentRunner, which Cucumber-JVM's FeatureRunner (which is used in Cucumber, the default Cucumber runner):

@Override
public void run(final RunNotifier notifier) {
    EachTestNotifier testNotifier = new EachTestNotifier(notifier,
            getDescription());
    try {
        Statement statement = classBlock(notifier);
        statement.evaluate();
    } catch (AssumptionViolatedException e) {
        testNotifier.fireTestIgnored();
    } catch (StoppedByUserException e) {
        throw e;
    } catch (Throwable e) {
        testNotifier.addFailure(e);
    }
}

This is how JUnit decides what result to show. If it's successful it will show a pass, but it's possible to @Ignore in JUnit, so what happens in that case? Well, an AssumptionViolatedException is thrown by the RunNotifier (or Cucumber FeatureRunner in this case).

So your example becomes:

@Before("@skiponchrome") // this works
public void beforeScenario() {
  if(currentBrowser == 'chrome') { // this works
    throw new AssumptionViolatedException("Not supported on Chrome")
  }
}

If you've used vanilla JUnit 4 before, you'd remember that @Ignore takes an optional message that is displayed when a test is ignored by the runner. AssumptionViolatedException carries the message, so you should see it in your test output after a test is skipped this way without having to write your own custom runner.

查看更多
姐就是有狂的资本
4楼-- · 2020-06-04 05:09

If you're using Maven, you could read use a browser profile and then set the appropriate ~ exclude tags there?

Unless you're asking how to run this from command line, in which case you tag the scenario with @skipchrome and then when you run cucumber set the cucumber options to tags = {"~@skipchrome"}

查看更多
趁早两清
5楼-- · 2020-06-04 05:15

I've implemented a customized junit runner as below. The idea is to add tags during runtime.

So say for a scenario we need new users, we tag the scenarios as "@requires_new_user". Then if we run our test in an environment (say production environment which dose not allow you to register new user easily), then we will figure out that we are not able to get new user. Then the ""not @requires_new_user" will be added to cucumber options to skip the scenario.

This is the most clean solution I can imagine now.

public class WebuiCucumberRunner extends ParentRunner<FeatureRunner> {
    private final JUnitReporter jUnitReporter;
    private final List<FeatureRunner> children = new ArrayList<FeatureRunner>();
    private final Runtime runtime;
    private final Formatter formatter;

    /**
     * Constructor called by JUnit.
     *
     * @param clazz the class with the @RunWith annotation.
     * @throws java.io.IOException                         if there is a problem
     * @throws org.junit.runners.model.InitializationError if there is another problem
     */
    public WebuiCucumberRunner(Class clazz) throws InitializationError, IOException {
        super(clazz);
        ClassLoader classLoader = clazz.getClassLoader();
        Assertions.assertNoCucumberAnnotatedMethods(clazz);

        RuntimeOptionsFactory runtimeOptionsFactory = new RuntimeOptionsFactory(clazz);
        RuntimeOptions runtimeOptions = runtimeOptionsFactory.create();

        addTagFiltersAsPerTestRuntimeEnvironment(runtimeOptions);

        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        runtime = createRuntime(resourceLoader, classLoader, runtimeOptions);
        formatter = runtimeOptions.formatter(classLoader);
        final JUnitOptions junitOptions = new JUnitOptions(runtimeOptions.getJunitOptions());
        final List<CucumberFeature> cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader, runtime.getEventBus());
        jUnitReporter = new JUnitReporter(runtime.getEventBus(), runtimeOptions.isStrict(), junitOptions);
        addChildren(cucumberFeatures);
    }

    private void addTagFiltersAsPerTestRuntimeEnvironment(RuntimeOptions runtimeOptions) 
    {
        String channel = Configuration.TENANT_NAME.getValue().toLowerCase();
        runtimeOptions.getTagFilters().add("@" + channel);

        if (!TestEnvironment.getEnvironment().isNewUserAvailable()) {
            runtimeOptions.getTagFilters().add("not @requires_new_user");
        }

    }
...
}

Or you can extends the official Cucumber Junit test runner cucumber.api.junit.Cucumber and override method

    /**
     * Create the Runtime. Can be overridden to customize the runtime or backend.
     *
     * @param resourceLoader used to load resources
     * @param classLoader    used to load classes
     * @param runtimeOptions configuration
     * @return a new runtime
     * @throws InitializationError if a JUnit error occurred
     * @throws IOException         if a class or resource could not be loaded
     * @deprecated Neither the runtime nor the backend or any of the classes involved in their construction are part of
     * the public API. As such they should not be  exposed. The recommended way to observe the cucumber process is to
     * listen to events by using a plugin. For example the JSONFormatter.
     */
    @Deprecated
    protected Runtime createRuntime(ResourceLoader resourceLoader, ClassLoader classLoader,
                                    RuntimeOptions runtimeOptions) throws InitializationError, IOException {
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        return new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
    }

You can manipulate runtimeOptions here as you wish. But the method is marked as deprecated, so use it with caution.

查看更多
爷、活的狠高调
6楼-- · 2020-06-04 05:20

If you wish simply to temporarily skip a scenario (for example, while writing the scenarios), you can comment it out (ctrl+/ in Eclipse or Intellij).

查看更多
女痞
7楼-- · 2020-06-04 05:22

I really prefer to be explicit about which tests are being run, by having separate run configurations defined for each environment. I also like to keep the number of tags I use to a minimum, to keep the number of configurations manageable.

I don't think it's possible to achieve what you want with tags alone. You would need to write a custom jUnit test runner to use in place of @RunWith(Cucumber.class). Take a look at the Cucumber implementation to see how things work. You would need to alter the RuntimeOptions created by the RuntimeOptionsFactory to include/exclude tags depending on the browser, or other runtime condition.

Alternatively, you could consider writing a small script which invokes your test suite, building up a list of tags to include/exclude dynamically, depending on the environment you're running in. I would consider this to be a more maintainable, cleaner solution.

查看更多
登录 后发表回答