This is an repost of
my blog entry
which itself was an extension of a hint posted by my good friend Björn Mårtensson that allowed
all tests to be run
inside Eclipse.
If you've ever done any programming inside Eclipse, and wanted to use a suite of
JUnit tests
, then you'll know that you either have to run each test individually inside Eclipse, or create a test suite (normally called
AllTests
) that encompasses all the tests you want to run. Having to manually update that class each time you want to run a new test is less than productive use of your time, and besides which, may result in you forgetting to add tests when they're created.
Fortunately, you don't need to specify a hard-coded list of tests to run; you can run all tests in an Eclipse project in one go. All you need to do is calculate the list of classes, and then pass those into the standard test runner and get the answer back again. And there's a really easy way of doing this using the
TestCollector
interface (and the
ClassPathTestCollector
implementation).
I've adapted Björn's original example to include a check for non-
abstract
classes, and also to allow the test to be run in a plugin as well as in Eclipse. This is necessary when doing Eclipse platform development, since your code may assume a particular behaviour of (say)
Plugin.getStateLocation()
, which wouldn't be available if you were just using ordinary JUnit testing. The
AllTests
looks for
*Test
on the classpath, filtering out
*PlatformTest
if not running as an Eclipse plugin. It does this by providing a second TestCollector implementation; if you want to copy and paste this code on non-plugin projects, just delete this implementation.
Owing to the fact that it's not possible (AFAIK) to determine the bundle name from the class that is being run (especially from static methods), the code makes the assumption that the package that contains the
AllTests
class is the same name as the plugin. So if you wanted to test the
org.example.foo
plugin, then you'd put it into the
org.example.foo
package. This can be changed in the source code.
The latest version should be available at
AllTests.java
but at the time of writing, the public CVS web repository is several days behind the HEAD that's been checked in, so YMMV. (Maybe this is why they're encouraging people to switch over to Subversion...) In the meantime, here's the contents of the code. It's released under the EPL, so you can use it in your own projects:
// Copyright (c) 2006 Alex Blewitt
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// Alex Blewitt - Initial API and implementation
//
package org.rcpapps.base;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;
import junit.framework.Test;
import junit.framework.TestSuite;
import junit.runner.ClassPathTestCollector;
import junit.runner.TestCollector;
import org.eclipse.core.runtime.Platform;
/**
* Run all the tests in this project, either computed from the classpath or from
* the bundlepath. To use this as-is, drop it into a non-default package that
* has the same name as the plugin. For example, if the plugin is called
*
org.example.foo
, this should be placed in a package
*
org.example.foo
, and all tests should live under the
*
org.example.foo
package structure (either directly, or in any
* subpackage). By default this will include all non-abstract classes named
*
XxxTest
, excluding
XxxPlatformTest
if running
* outside of the platform.
*/
public class AllTests {
/**
* Detects classes from the bundle PLUGIN_NAME's entries. Uses
*
bundle.findEntries
to obtain a list of classes that live
* in the specified PACKAGE_NAME, and adds those to the test path, providing
* that they are {@link AllTests#isValidTest(String, boolean) valid}.
*/
private static class BundleTestDetector implements TestCollector {
/*
* @see junit.runner.TestCollector#collectTests()
*/
public Enumeration collectTests() {
final Vector tests = new Vector();
try {
Enumeration entries = Platform.getBundle(PLUGIN_NAME).findEntries("/", "*" + SUFFIX + ".class", true);
while (entries.hasMoreElements()) {
URL entry = (URL) entries.nextElement();
// Change the URLs to have Java class names
String path = entry.getPath().replace('/', '.');
int start = path.indexOf(PACKAGE_NAME);
String name = path.substring(start, path.length()
- ".class".length());
if (isValidTest(name, true)) {
tests.add(name);
}
}
} catch (Exception e) {
// If we get here, the Platform isn't installed and so we fail
// quietly. This isn't a problem; we might be outside of the
// Platform framework and just running tests locally. It's not
// even worth printing anything out to the error log as it would
// just confuse people investigating stack traces etc.
}
return tests.elements();
}
}
/**
* Searches the current classpath for tests, which are those ending with
* SUFFIX, excluding those which end in IN_CONTAINER_SUFFIX, providing that
* they are {@link AllTests#isValidTest(String, boolean) valid}.
*/
private static class ClassFileDetector extends ClassPathTestCollector {
/*
* @see junit.runner.ClassPathTestCollector#isTestClass(java.lang.String)
*/
protected boolean isTestClass(String classFileName) {
return classFileName.endsWith(SUFFIX + ".class")
&& isValidTest(classNameFromFile(classFileName), false);
}
}
/**
* All tests should end in XxxTest
*/
public static final String SUFFIX = "Test";
/**
* All in-container tests should end in XxxPlatformTest
*/
public static final String IN_CONTAINER_SUFFIX = "Platform" + SUFFIX;
/**
* The base package name of the tests to run. This defaults to the name of
* the package that the AllTests class is in for ease of management but may
* be trivially changed if required. Note that at least some identifiable
* part must be provided here (so default package names are not allowed)
* since the URL that comes up in the bundle entries have a prefix that is
* not detectable automatically. Even if this is "org" or "com" that should
* be enough.
*/
public static final String PACKAGE_NAME = AllTests.class.getPackage()
.getName();
/**
* The name of the plugin to search if the platform is loaded. This defaults
* to the name of the package that the AllTests class is in for ease of
* management but may be trivially changed if required.
*/
public static final String PLUGIN_NAME = AllTests.class.getPackage()
.getName();
/**
* Add the tests reported by collector to the list of tests to run
* @param collector the test collector to run
* @param suite the suite to add the tests to
*/
private static void addTestsToSuite(TestCollector collector, TestSuite suite) {
Enumeration e = collector.collectTests();
while (e.hasMoreElements()) {
String name = (String) e.nextElement();
try {
suite.addTestSuite(Class.forName(name));
} catch (ClassNotFoundException e1) {
System.err.println("Cannot load test: " + e1);
}
}
}
/**
* Is the test a valid test?
* @param name the name of the test
* @param inContainer true if we want to include the inContainer tests
* @return true if the name is a valid class (can be loaded), that it is not
* abstract, and that it ends with SUFFIX, and that either
* inContainer tests are to be included or the name does not end
* with IN_CONTAINER_SUFFIX
*/
private static boolean isValidTest(String name, boolean inContainer) {
try {
return name.endsWith(SUFFIX)
&& (inContainer || !name.endsWith(IN_CONTAINER_SUFFIX))
&& ((Class.forName(name).getModifiers() & Modifier.ABSTRACT) == 0);
} catch (ClassNotFoundException e) {
System.err.println(e.toString());
return false;
}
}
/**
* Return all the tests. If we're in a platform, return everything. If not,
* we return those tests that end in SUFFIX but excluding those ending in
* IN_CONTAINER_SUFFIX.
* @return a suite of tests for JUnit to run
* @throws Error if there are no tests to run.
*/
public static Test suite() {
TestSuite suite = new TestSuite(AllTests.class.getName());
addTestsToSuite(new ClassFileDetector(), suite);
addTestsToSuite(new BundleTestDetector(), suite);
if (suite.countTestCases() == 0) {
throw new Error("There are no test cases to run");
} else {
return suite;
}
}
}
I'm not sure i agree with this "you'll know that you either have to run each test individually inside Eclipse, or create a test suite (normally called AllTests) that encompasses all the tests you want to run".
Actually, there is an option in each junit test launch configuration that allows you to run the tests of an entire project, source folder, or package. You have to look for your test's launch configuration by using the 'Run...' menu item.
See the screenshot attached (taken on eclipse 3.1.0)
"Having to manually update that class each time you want to run a new test is less than productive use of your time...".
Actually if you have a regular AllTests suite, Eclipse allows you to update it automatically (granted it is not fully automatic since it requires you to ask for it). In order to do that, right click on AllTests and then select 'Recreate Test Suite' from the context menu. It will show up a list of unit tests found in the project and update the class for you.
Cool. I didn't know Eclipse could recreate the test suite.
The AllTests allows you to run the tests as a Plug-in development test (and in automated builds outside of the Eclipse IDE). Whilst you may be able to run tests in plug-in-test mode within the IDE, what you really want is to be able to automate that.
Secondly, if you have 20 projects, you just need to create a 21st project, add the other 20 as entries on the ClassPath, and then run the AllTestts in the 21st project, and you get to run *all* tests in your project set, not just limited to all the tests in one project.
Also, I had to put in the filter to only enable some tests when the platform was up and running (rather than just all tests) because some of them don't work when running outside of a platform (e.g. WorkspaceClosedExecption).
None the less, I should have included this in my post ... thanks for catching me out
Fixing this problem was one of my major goals when I created TestNG: I didn't want to have to recompile anything whenever a new test is added (except the new test of course). Also, I didn't want to have to recompile my tests whenever I want to run a different set.
Which is why groups are so convenient: add a test method, declare what groups it belongs to, compile, you're done.
After that, you can invoke whatever groups you need without having to recompile anything.
With TestNG, you don't call your tests: your tests call you
Running all tests within an Eclipse project
At 8:40 PM on Mar 6, 2006, Alex Blewitt
wrote:
This is an repost of my blog entry which itself was an extension of a hint posted by my good friend Björn Mårtensson that allowed all tests to be run inside Eclipse.
If you've ever done any programming inside Eclipse, and wanted to use a suite of JUnit tests , then you'll know that you either have to run each test individually inside Eclipse, or create a test suite (normally called
AllTests) that encompasses all the tests you want to run. Having to manually update that class each time you want to run a new test is less than productive use of your time, and besides which, may result in you forgetting to add tests when they're created.Fortunately, you don't need to specify a hard-coded list of tests to run; you can run all tests in an Eclipse project in one go. All you need to do is calculate the list of classes, and then pass those into the standard test runner and get the answer back again. And there's a really easy way of doing this using the TestCollector interface (and the ClassPathTestCollector implementation).
I've adapted Björn's original example to include a check for non-
abstractclasses, and also to allow the test to be run in a plugin as well as in Eclipse. This is necessary when doing Eclipse platform development, since your code may assume a particular behaviour of (say)Plugin.getStateLocation(), which wouldn't be available if you were just using ordinary JUnit testing. TheAllTestslooks for*Teston the classpath, filtering out*PlatformTestif not running as an Eclipse plugin. It does this by providing a second TestCollector implementation; if you want to copy and paste this code on non-plugin projects, just delete this implementation.Owing to the fact that it's not possible (AFAIK) to determine the bundle name from the class that is being run (especially from static methods), the code makes the assumption that the package that contains the
AllTestsclass is the same name as the plugin. So if you wanted to test theorg.example.fooplugin, then you'd put it into theorg.example.foopackage. This can be changed in the source code.The latest version should be available at AllTests.java but at the time of writing, the public CVS web repository is several days behind the HEAD that's been checked in, so YMMV. (Maybe this is why they're encouraging people to switch over to Subversion...) In the meantime, here's the contents of the code. It's released under the EPL, so you can use it in your own projects:
5 replies so far (
Post your own)
Re: Running all tests within an Eclipse project
I'm not sure i agree with thisActually, there is an option in each junit test launch configuration that allows you to run the tests of an entire project, source folder, or package. You have to look for your test's launch configuration by using the 'Run...' menu item.
See the screenshot attached (taken on eclipse 3.1.0)
"Having to manually update that class each time you want to run a new test is less than productive use of your time...".
Actually if you have a regular AllTests suite, Eclipse allows you to update it automatically (granted it is not fully automatic since it requires you to ask for it). In order to do that, right click on AllTests and then select 'Recreate Test Suite' from the context menu. It will show up a list of unit tests found in the project and update the class for you.
Hope it helps.
Re: Running all tests within an Eclipse project
Cool. I didn't know Eclipse could recreate the test suite.The AllTests allows you to run the tests as a Plug-in development test (and in automated builds outside of the Eclipse IDE). Whilst you may be able to run tests in plug-in-test mode within the IDE, what you really want is to be able to automate that.
Secondly, if you have 20 projects, you just need to create a 21st project, add the other 20 as entries on the ClassPath, and then run the AllTestts in the 21st project, and you get to run *all* tests in your project set, not just limited to all the tests in one project.
Also, I had to put in the filter to only enable some tests when the platform was up and running (rather than just all tests) because some of them don't work when running outside of a platform (e.g. WorkspaceClosedExecption).
None the less, I should have included this in my post ... thanks for catching me out
Alex.
Re: Running all tests within an Eclipse project
Still, this is very contrived.Fixing this problem was one of my major goals when I created TestNG: I didn't want to have to recompile anything whenever a new test is added (except the new test of course). Also, I didn't want to have to recompile my tests whenever I want to run a different set.
Which is why groups are so convenient: add a test method, declare what groups it belongs to, compile, you're done.
After that, you can invoke whatever groups you need without having to recompile anything.
With TestNG, you don't call your tests: your tests call you
--
Cedric
TestNG
Re: Running all tests within an Eclipse project
What about running all tests in the workspace?? Is there a way to do this?Re: Running all tests within an Eclipse project
No.