Skip to content

Commit

Permalink
Introduce concurrency tests for Spring's JUnit 4 support
Browse files Browse the repository at this point in the history
Whereas the existing TestContextConcurrencyTests verify support for
concurrency in the TestContextManager and TestContext, this commit
introduces SpringJUnit4ConcurrencyTests that verify support for
concurrent test execution in Spring's JUnit 4 support, namely in the
SpringRunner, SpringClassRule, and SpringMethodRule.

The tests executed by this new test class come from a hand-picked
collection of test classes within the test suite that is intended to
cover most categories of tests that are currently supported by the
TestContext Framework on JUnit 4.

Note, however, that the chosen test classes intentionally do not
include any classes that fall under the following categories.

 - tests that make use of Spring's @DirtiesContext support

 - tests that make use of JUnit 4's @FixMethodOrder support

 - tests that commit changes to the state of a shared in-memory database

Issue: SPR-5863
  • Loading branch information
sbrannen committed Sep 5, 2016
1 parent e822e4c commit 2699504
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*
* @author Sam Brannen
* @since 5.0
* @see org.springframework.test.context.junit4.concurrency.SpringJUnit4ConcurrencyTests
*/
public class TestContextConcurrencyTests {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,17 +18,25 @@

import java.lang.reflect.Constructor;

import org.junit.experimental.ParallelComputer;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;

import org.springframework.beans.BeanUtils;

import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Collection of utilities for testing the execution of JUnit tests.
* Collection of utilities for testing the execution of JUnit 4 based tests.
*
* <p>Note that these utilities use {@link Assertions} from JUnit Jupiter,
* but that should not result in any adverse side effects in terms of
* proper test failure for failed assertions.
*
* @author Sam Brannen
* @since 4.2
Expand All @@ -38,8 +46,8 @@ public class JUnitTestingUtils {

/**
* Run the tests in the supplied {@code testClass}, using the {@link Runner}
* it is configured with (i.e., via {@link RunWith @RunWith}) or the default
* JUnit runner, and assert the expectations of the test execution.
* configured via {@link RunWith @RunWith} or the default JUnit runner, and
* assert the expectations of the test execution.
*
* @param testClass the test class to run with JUnit
* @param expectedStartedCount the expected number of tests that started
Expand All @@ -65,7 +73,7 @@ public static void runTestsAndAssertCounters(Class<?> testClass, int expectedSta
* (i.e., via {@link RunWith @RunWith}) or the default JUnit runner.
*
* @param runnerClass the explicit runner class to use or {@code null}
* if the implicit runner should be used
* if the default JUnit runner should be used
* @param testClass the test class to run with JUnit
* @param expectedStartedCount the expected number of tests that started
* @param expectedFailedCount the expected number of tests that failed
Expand Down Expand Up @@ -93,11 +101,54 @@ public static void runTestsAndAssertCounters(Class<? extends Runner> runnerClass
junit.run(testClass);
}

assertEquals("tests started for [" + testClass + "]:", expectedStartedCount, listener.getTestStartedCount());
assertEquals("tests failed for [" + testClass + "]:", expectedFailedCount, listener.getTestFailureCount());
assertEquals("tests finished for [" + testClass + "]:", expectedFinishedCount, listener.getTestFinishedCount());
assertEquals("tests ignored for [" + testClass + "]:", expectedIgnoredCount, listener.getTestIgnoredCount());
assertEquals("failed assumptions for [" + testClass + "]:", expectedAssumptionFailedCount, listener.getTestAssumptionFailureCount());
// @formatter:off
assertAll(
() -> assertEquals(expectedStartedCount, listener.getTestStartedCount(), "tests started for [" + testClass + "]"),
() -> assertEquals(expectedFailedCount, listener.getTestFailureCount(), "tests failed for [" + testClass + "]"),
() -> assertEquals(expectedFinishedCount, listener.getTestFinishedCount(), "tests finished for [" + testClass + "]"),
() -> assertEquals(expectedIgnoredCount, listener.getTestIgnoredCount(), "tests ignored for [" + testClass + "]"),
() -> assertEquals(expectedAssumptionFailedCount, listener.getTestAssumptionFailureCount(), "failed assumptions for [" + testClass + "]")
);
// @formatter:on
}

/**
* Run all tests in the supplied test classes according to the policies of
* the supplied {@link Computer}, using the {@link Runner} configured via
* {@link RunWith @RunWith} or the default JUnit runner, and assert the
* expectations of the test execution.
*
* <p>To have all tests executed in parallel, supply {@link ParallelComputer#methods()}
* as the {@code Computer}. To have all tests executed serially, supply
* {@link Computer#serial()} as the {@code Computer}.
*
* @param computer the JUnit {@code Computer} to use
* @param expectedStartedCount the expected number of tests that started
* @param expectedFailedCount the expected number of tests that failed
* @param expectedFinishedCount the expected number of tests that finished
* @param expectedIgnoredCount the expected number of tests that were ignored
* @param expectedAssumptionFailedCount the expected number of tests that
* resulted in a failed assumption
* @param testClasses one or more test classes to run
*/
public static void runTestsAndAssertCounters(Computer computer, int expectedStartedCount, int expectedFailedCount,
int expectedFinishedCount, int expectedIgnoredCount, int expectedAssumptionFailedCount,
Class<?>... testClasses) throws Exception {

JUnitCore junit = new JUnitCore();
TrackingRunListener listener = new TrackingRunListener();
junit.addListener(listener);
junit.run(computer, testClasses);

// @formatter:off
assertAll(
() -> assertEquals(expectedStartedCount, listener.getTestStartedCount(), "tests started"),
() -> assertEquals(expectedFailedCount, listener.getTestFailureCount(), "tests failed"),
() -> assertEquals(expectedFinishedCount, listener.getTestFinishedCount(), "tests finished"),
() -> assertEquals(expectedIgnoredCount, listener.getTestIgnoredCount(), "tests ignored"),
() -> assertEquals(expectedAssumptionFailedCount, listener.getTestAssumptionFailureCount(), "failed assumptions")
);
// @formatter:on
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.test.context.junit4.concurrency;

import org.junit.Test;
import org.junit.experimental.ParallelComputer;

import org.springframework.test.context.hierarchies.web.DispatcherWacRootWacEarTests;
import org.springframework.test.context.junit4.InheritedConfigSpringJUnit4ClassRunnerAppCtxTests;
import org.springframework.test.context.junit4.MethodLevelTransactionalSpringRunnerTests;
import org.springframework.test.context.junit4.SpringJUnit47ClassRunnerRuleTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit4.TimedTransactionalSpringRunnerTests;
import org.springframework.test.context.junit4.rules.BasicAnnotationConfigWacSpringRuleTests;
import org.springframework.test.context.junit4.rules.ParameterizedSpringRuleTests;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.test.context.web.RequestAndSessionScopedBeansWacTests;
import org.springframework.test.context.web.socket.WebSocketServletServerContainerFactoryBeanTests;
import org.springframework.test.web.client.samples.SampleTests;
import org.springframework.test.web.servlet.samples.context.JavaConfigTests;
import org.springframework.test.web.servlet.samples.context.WebAppResourceTests;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;

import static org.springframework.test.context.junit4.JUnitTestingUtils.runTestsAndAssertCounters;

/**
* Concurrency tests for the {@link SpringRunner}, {@link SpringClassRule}, and
* {@link SpringMethodRule} that use JUnit 4's experimental {@link ParallelComputer}
* to execute tests in parallel.
*
* <p>The tests executed by this test class come from a hand-picked collection of test
* classes within the test suite that is intended to cover most categories of tests
* that are currently supported by the TestContext Framework on JUnit 4.
*
* <p>The chosen test classes intentionally do <em>not</em> include any classes that
* fall under the following categories.
*
* <ul>
* <li>tests that make use of Spring's {@code @DirtiesContext} support
* <li>tests that make use of JUnit 4's {@code @FixMethodOrder} support
* <li>tests that commit changes to the state of a shared in-memory database
* </ul>
*
* <p><strong>NOTE</strong>: these tests only run if the {@link TestGroup#LONG_RUNNING
* LONG_RUNNING} test group is enabled.
*
* @author Sam Brannen
* @since 5.0
* @see org.springframework.test.context.TestContextConcurrencyTests
*/
public class SpringJUnit4ConcurrencyTests {

// @formatter:off
private static final Class<?>[] testClasses = new Class[] {
// Basics
/* 9 */ SpringJUnit4ClassRunnerAppCtxTests.class,
/* 9 */ InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.class,
/* 2 */ SpringJUnit47ClassRunnerRuleTests.class,
/* 2 */ ParameterizedSpringRuleTests.class,
// Transactional
/* 2 */ MethodLevelTransactionalSpringRunnerTests.class,
/* 4 */ TimedTransactionalSpringRunnerTests.class,
// Web and Scopes
/* 1 */ DispatcherWacRootWacEarTests.class, /* 2 ignored */
/* 3 */ BasicAnnotationConfigWacSpringRuleTests.class,
/* 2 */ RequestAndSessionScopedBeansWacTests.class,
/* 1 */ WebSocketServletServerContainerFactoryBeanTests.class,
// Spring MVC Test
/* 2 */ JavaConfigTests.class,
/* 3 */ WebAppResourceTests.class,
/* 4 */ SampleTests.class
};
// @formatter:on

/**
* The number of tests in all {@link #testClasses}.
*
* <p>The current number of tests per test class is tracked as a comment
* before each class reference above. The following constant must therefore
* be the sum of those values.
*
* <p>This is admittedly fragile, but there's unfortunately not really a
* better way to count the number of tests without re-implementing JUnit 4's
* discovery algorithm. Plus, the presence of parameterized tests makes it
* even more difficult to count programmatically.
*/
private static final int TESTS = 44;
private static final int FAILED = 0;
private static final int IGNORED = 2;
private static final int ABORTED = 0;


@Test
public void runAllTestsConcurrently() throws Exception {

Assume.group(TestGroup.LONG_RUNNING);

runTestsAndAssertCounters(new ParallelComputer(true, true), TESTS, FAILED, TESTS, IGNORED, ABORTED,
testClasses);
}

}

0 comments on commit 2699504

Please sign in to comment.