Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "JFace Data Binding/Conformance Tests"

(Added information about IObservableSet and other cleanup)
(No difference)

Revision as of 19:06, 30 September 2007

The JFace Data Binding TCK is a suite of tests and other files that allow for asserting the conformance of implementations to the abstractions provided by the library.

The TCK is not yet available but will be soon for early adopters.

Observables

The TCK provides tests for the assertion of conformance to the observable specifications. Tests are currently provided for implementations of:

  • IObservable
  • IObservableValue
  • IObservableCollection
  • IObservableList
  • IObservableSet

Tests are broken up into mutable and immutable test cases (e.g. ObservableValueContractTest and MutableObservableValueContractTest). The reason for this is that not all implementations allow for a consumer to mutate the observable via its API. The TCK tests assert the following when appropriate:

  • Change events and their diffs
  • Realm checking
  • ObservableTracker.getterCalled(IObservable) invocations
  • Values and value types

The TCK tests don't assert the observed object state. Because the observed object can be of any type and the value can be in any form this isn't something that we feel we can reliably provide. It would be more straightforward for these to remain in your own tests.

Delegates

In order to take advantage of the tests developers will need to create a contract delegate. The delegates allow for implementation specific details to be provided to the TCK. The IObservableContractDelegate is provided below as an example:

public interface IObservableContractDelegate {
	/**
	 * Notifies the delegate of the start of a test.
	 */
	public void setUp();

	/**
	 * Notifies the delegate of the end of a test.
	 */
	public void tearDown();

	/**
	 * Invokes an operation to set the stale state of the provided
	 * observable.
	 * 
	 * @param observable
	 * @param stale
	 */
	public void setStale(IObservable observable, boolean stale);

	/**
	 * Creates a new observable.
	 * 
	 * @param realm realm of the observable
	 * @return observable
	 */
	public IObservable createObservable(Realm realm);

	/**
	 * Invokes a change operation on the observable resulting in a change event
	 * being fired from the observable.
	 * 
	 * @param observable
	 */
	public void change(IObservable observable);
}

The delegate API follows the standard JUnit conventions of setUp() and tearDown(). The other methods will be invoked when necessary by the tests.

The delegates provided are:

  • org.eclipse.jface.databinding.conformance.delegate.IObservableContractDelegate
  • org.eclipse.jface.databinding.conformance.delegate.IObservableValueContractDelegate
  • org.eclipse.jface.databinding.conformance.delegate.IObservableCollectionContractDelegate

Your observable implementation will determine which delegate to construct. Abstract implementations are provided to simplify implementing a delegate.

Integration into Tests

Since the tests are JUnit3 tests you can integrate them into your tests in standard ways. The two most common ways are by subclassing or creating a suite.

Subclassing

public class ButtonObservableValueTest extends SWTObservableValueContractTest {
	public ButtonObservableValueTest() {
		super(new Delegate());
	}
	
	/* package */ static class Delegate extends AbstractObservableValueContractDelegate {
		private Shell shell;
		private Button button;

		public void setUp() {
			shell = new Shell();
			button = new Button(shell, SWT.CHECK);
		}
		
		public void tearDown() {
			shell.dispose();
		}
		
		public IObservableValue createObservableValue(Realm realm) {
			return new ButtonObservableValue(realm, button);
		}
		
		public void change(IObservable observable) {
			boolean value = button.getSelection();
			button.setSelection(!value);
			button.notifyListeners(SWT.Selection, null);
		}
		
		public Object createValue(IObservableValue observable) {
			return (Boolean.TRUE.equals(observable.getValue()) ? Boolean.FALSE : Boolean.TRUE);
		}
		
		public Object getValueType(IObservableValue observable) {
			return Boolean.TYPE;
		}
	}
}

When subclassing, because of single inheritance, you will will have to create multiple implementations to test the mutable and immutable use cases (e.g. there would need to be a ButtonMutableObservableValueTest as well to test the mutable cases). The rest of the implementation should be straightforward. The only thing we ask is that you don't depend upon API other than the constructors. Tests are public because JUnit requires them to be, not because we want to commit to them as API. Over time we would like to have the opportunity to rename, add, remove, or optimize the test methods to ensure that we're getting the best coverage as possible. Because of the issues outlined above the preferred method is creating a JUnit suite.

JUnit suite()

public class ButtonObservableValueTest extends TestCase {
	public static Test suite() {
		Delegate delegate = new Delegate();

		return new SuiteBuilder().addTests(ButtonObservableValueTest.class)
				.addObservableContractTest(
						SWTObservableValueContractTest.class, delegate)
				.addObservableContractTest(
						SWTMutableObservableValueContractTest.class, delegate)
				.build();
	}

	/* package */ static class Delegate extends AbstractObservableValueContractDelegate {
		private Shell shell;
		private Button button;

		public void setUp() {
			shell = new Shell();
			button = new Button(shell, SWT.CHECK);
		}
		
		public void tearDown() {
			shell.dispose();
		}
		
		public IObservableValue createObservableValue(Realm realm) {
			return new ButtonObservableValue(realm, button);
		}
		
		public void change(IObservable observable) {
			boolean value = button.getSelection();
			button.setSelection(!value);
			button.notifyListeners(SWT.Selection, null);
		}
		
		public Object createValue(IObservableValue observable) {
			return (Boolean.TRUE.equals(observable.getValue()) ? Boolean.FALSE : Boolean.TRUE);
		}
		
		public Object getValueType(IObservableValue observable) {
			return Boolean.TYPE;
		}
	}
} 

By creating a suite() method you can create a custom suite of tests to run. This will allow you to run multiple TestCases from a single test eliminating the need to create multiple implementations for the mutable and immutable cases. The SuiteBuilder implementation allows for a straightforward way to build these suites. The downside to building tests in this fashion is that when ran they don't contain the context of a parent class. In the JUnit view in Eclipse they are children of a junit.framework.TestSuite rather than a named test. As a way around this the failure message contains information about the context of the failure (e.g. Test class name and delegate name).

Back to the top