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 "SCA/SCA Component/SCA Testing SCA Applications Unit Example"

< SCA
(Generating Test Project and Classes)
(Runnning the Tests)
Line 549: Line 549:
 
== Runnning the Tests ==  
 
== Runnning the Tests ==  
  
We know have written a TestCase for SCA.
+
We now have written a TestCase for SCA and we need to run it on a SCA platform. For this example, I have run it manually on a Tuscany runtime (i.e. I have added Tuscany jars to my project class path). In the future, SCA platforms will be registered as servers in Eclipse (like J2EE servers - registration through the SOAS sub-project of STP). For the moment, [http://tuscany.apache.org/ Tuscany], [http://fabric3.codehaus.org/ Fabric3] and [http://petals.objectweb.org/ PEtALS] (with its SCA service engine based on [http://www.scorware.org/projects/en Frascati]) are expected to be supported.
We know need to run it on a SCA platform. For this example, I have run it manually on a Tuscany runtime (i.e. I have added Tuscany jars to my project class path). In the future, SCA platforms will be registered as servers in Eclipse (like J2EE servers - registration through the SOAS sub-project of STP). For the moment, Tuscany, Fabric3 and PEtALS (with its SCA service engine based on Frascati) are expected to be supported.
+
  
Running test is made by right-clicking on the TestCase to run and by selecting SCA > Run > ''platform'', where ''platform '' is a registered runtime server.
+
Running test is made by right-clicking on the TestCase to run and by selecting SCA > Run > ''platform'', where ''platform '' is a registered runtime server. It results in the deployment of the tester application and in the generation of a client to call this application.
It results in the deployment of the tester application and in the generation of a client to call this application.
+
  
 
The server...
 
The server...
Line 592: Line 590:
 
</source>
 
</source>
  
In Eclipse, results should rather be displayed in the usual JUnit view instead of being printed in the console.
+
 
With the given code, when both are run, it prints:
+
When both of these classes are run, it prints:
 
<pre>
 
<pre>
 
4 tests were run.
 
4 tests were run.
Line 605: Line 603:
 
==> Test failed.
 
==> Test failed.
 
</pre>
 
</pre>
 +
 +
 +
In Eclipse, results should rather be displayed in the usual JUnit view instead of being printed in the console.

Revision as of 11:35, 22 July 2008

The Application to Test

Let's first begin with the description of the application we are going to test. It would probably have been better if we had taken the use case Restaurant given in the samples, but this example uses another "simple" application.

So, we have an SCA Java project in our workspace which contains the composite, the interfaces and the implementations.

SCAtestSample applicationBasisProject.png


Our application is made up of three components:

  • HandlePurchaseComponent is the first component. It has one service which promoted by the composite. And it has two references toward the two next components.
  • ClientManagerComponent is the second component. It has one service wired with a reference of HandlePurchaseComponent. It has no reference.
  • PurchaseProcessorComponent is the third component. It also has one service wired with a reference of HandlePurchaseComponent. It has no reference.


Using the SCA Composite Designer, we got this representation.

SCAtestSample applicationBusinessComposite.png


The composite file is the following.

<?xml version="1.0" encoding="ISO-8859-15"?>
<sca:composite xmlns:sca="http://www.osoa.org/xmlns/sca/1.0" name="PurchaseComposite">
 
  <sca:component name="HandlePurchaseComponent">
    <sca:implementation.java class="org.eclipse.stp.sca.example.HandlePurchaseServiceImpl"/>
    <sca:service name="HandlePurchaseService">
      <sca:interface.java interface="org.eclipse.stp.sca.example.HandlePurchaseService"/>
      <tuscany:binding.rmi xmlns:tuscany="http://tuscany.apache.org/xmlns/sca/1.0" host="localhost" port="8098" serviceName="PurchaseApplication"/>
    </sca:service>
    <sca:reference name="clientManager"/>
    <sca:reference name="purchaseProcessor"/>
  </sca:component>
 
  <sca:component name="ClientManagerComponent">
    <sca:implementation.java class="org.eclipse.stp.sca.example.ClientManagerServiceImpl"/>
    <sca:service name="ClientManagerService">
      <sca:interface.java interface="org.eclipse.stp.sca.example.ClientManagerService"/>
    </sca:service>
  </sca:component>
 
  <sca:component name="PurchaseProcessorComponent">
    <sca:implementation.java class="org.eclipse.stp.sca.example.PurchaseProcessorServiceImpl"/>
    <sca:service name="PurchaseProcessorService">
      <sca:interface.java interface="org.eclipse.stp.sca.example.PurchaseProcessorService"/>
    </sca:service>
  </sca:component>
 
  <sca:wire source="HandlePurchaseComponent/clientManager" target="ClientManagerComponent/ClientManagerService"/>
  <sca:wire source="HandlePurchaseComponent/purchaseProcessor" target="PurchaseProcessorComponent/PurchaseProcessorService"/>
 
  <sca:service name="HandlePurchaseService" promote="HandlePurchaseComponent/HandlePurchaseService"/>
</sca:composite>


What we are going to do is write and run unit tests for the first component HandlePurchaseComponent in the same way SCA testing tools would allow us to do.

The interface of the component services are the following:

/**
 * Service interface for a HandlePurchaseComponent service.
 */
public interface HandlePurchaseService {
 
	/**
	 * Handle purchase of items.
	 * 
	 * @param clientName the client name.
	 * @param purchasedItemReferences an array of item references. Never null.
	 * @return a status indicating the result of the purchase processing. 
	 */
	public PurchaseProcessingStatus handlePurchase( 
			String clientName, String[] purchasedItemReferences );
}


/**
 * Service interface for a PurchaseProcessorComponent service.
 */
public interface PurchaseProcessorService {
 
	/**
	 * Process purchase.
	 * @param purchasedItemReferences an array of item references. Never null.
	 * @return true if the processing succeeded, false if an error occurred. 
	 */
	public boolean processPurchase( String[] purchasedItemReferences );
}


/**
 * Service interface for a ClientManagerComponent service.
 */
public interface ClientManagerService {
 
	/**
	 * Check whether a client is already registered or not.
	 * @param clientName the client name.
	 * @return true if the client is already known, false otherwise.
	 */
	public boolean isRegistered( String clientName );
 
	/**
	 * Keep track of a client in the history.
	 * Should not be called if <i>clientName</i> is already registered.
	 * 
	 * @param clientName the client name.
	 * @return true if the registration succeeded, false otherwise.
	 */
	public boolean registerClient(  String clientName );
}


The implementation class of HandlePurchaseComponent is the following:

/**
 * Implementation for HandlePurchaseComponent.
 */
@Service( HandlePurchaseService.class )
public class HandlePurchaseServiceImpl implements HandlePurchaseService {
	private ClientManagerService clientManager;
	private PurchaseProcessorService purchaseProcessor;
 
 
	@Reference
	public void setClientManager(ClientManagerService clientManager) {
		this.clientManager = clientManager;
	}
 
	@Reference
	public void setPurchaseProcessor(PurchaseProcessorService purchaseProcessor) {
		this.purchaseProcessor = purchaseProcessor;
	}
 
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.stp.sca.example.HandlePurchaseService#handlePurchase(java.lang.String, java.lang.String[])
	 */
	public PurchaseProcessingStatus handlePurchase( 
			String clientName, String[] purchasedItemReferences ) {
 
		// Registration.
		boolean isRegistered = true;
		if( !clientManager.isRegistered( clientName )) 
			isRegistered = clientManager.registerClient( clientName );	
		if( !isRegistered )
			return PurchaseProcessingStatus.REGISTRATION_FAILED;
 
		// Purchase processing.
		boolean processingDone = purchaseProcessor.processPurchase( purchasedItemReferences );
		if( !processingDone )
			return PurchaseProcessingStatus.PROCESSING_FAILED;
 
		return PurchaseProcessingStatus.OK;
	}
}


Notice that I have used Tuscany to run these examples. I also used Tuscany jars for the Java annotations. A STP testing tool would use the annotations defined in the STP project. This choice does not prevent us from seeing the portability of the implementation and of the testing mechanism.

You can also notice that the application is not very smart. The goal of this example is not to show a smart SCA application, but how we can perform unit test directly with the composite elements.


Generating Test Project and Classes

First thing, we want to separate the business code from the testing code. Since SCA projects can be big, it seems better to create a distinct project in a workspace to test this application. We first make a right-click on the SCA project we want to test and we select SCA > Create Test Project.

A project whose name is <projectName>Test is created if it does not already exists (<projectName> is the name of the project to test). This project is an SCA Java project, associated through one of its properties with the SCA project to test. For commodity reasons, "the project to test" will be called "tested project" and the project containing the test code will be called "tester project". The tester project has EasyMock and a custom library adapted for SCA test in its class path. JUnit and SCA libraries (SCA Java annotations) are already present in Eclipse.


Now this is done, we would like to write unit tests for the first component HandlePurchaseComponent. We maker a right-click on the component / composite to test and we select SCA > Create Unit Test. This right-click can be made in the SCA Composite Designer or in the XML editor for *.composite files. It results in the generation of files in the tester project. Let's take a look at the interesting ones for instance.

The first thing is a composite file, which defines a composite with two components and one service. The first component is a "JUnit component", whose implementation includes a TestCase. The second component is the component to test, HandlePurchaseComponent (an exact copy-paste of the component, with its implementation and its interface imported or referenced by the project). Here is the composite diagram...

SCAtestSample applicationTestComposite.png


... and its source.

<?xml version="1.0" encoding="ISO-8859-15"?>
<sca:composite xmlns:sca="http://www.osoa.org/xmlns/sca/1.0" xmlns="http://www.osoa.org/xmlns/sca/1.0" name="TestHandlePurchase">
 
  <sca:component name="TestComponent">
    <sca:implementation.java class="org.eclipse.stp.sca.example.test.run.ScaTestRunner"/>
    <sca:reference name="handlePurchaseService">
      <sca:binding.sca/>
    </sca:reference>
    <sca:service name="RunTestService">
      <tuscany:binding.rmi xmlns:tuscany="http://tuscany.apache.org/xmlns/sca/1.0" host="localhost" port="8099" serviceName="TestSca"/>
    </sca:service>
    <sca:service name="ClientManagerService">
      <sca:binding.sca/>
    </sca:service>
    <sca:service name="PurchaseProcessorService">
      <sca:binding.sca/>
    </sca:service>
  </sca:component>
 
  <sca:component name="HandlePurchaseComponent">
    <sca:implementation.java class="org.eclipse.stp.sca.example.HandlePurchaseServiceImpl"/>
    <sca:service name="HandlePurchaseService">
      <sca:binding.sca/>
    </sca:service>
    <sca:reference name="clientManager">
      <sca:binding.sca/>
    </sca:reference>
    <sca:reference name="purchaseProcessor">
      <sca:binding.sca/>
    </sca:reference>
  </sca:component>
 
  <sca:wire source="TestComponent/handlePurchaseService" target="HandlePurchaseComponent/HandlePurchaseService"/>
  <sca:wire source="HandlePurchaseComponent/clientManager" target="TestComponent/ClientManagerService"/>
  <sca:wire source="HandlePurchaseComponent/purchaseProcessor" target="TestComponent/PurchaseProcessorService"/>
 
  <sca:service name="RunTestService" promote="TestComponent/RunTestService"/>
</sca:composite>


The elements of the second component are already known. They come from the tested component. You can notice that its references are wired to services of the JUnit component. These services' implementations will delegate the calls to mock objects.

The JUnit component has one service which is promoted by the tester composite. Its interface is the following one:

/**
 * Interface for the JUnit component.
 */
public interface RunTestService {
 
	/**
	 * Run a TestCase and return the result.
	 * @return the result of a TestCase as a serializable object.
	 */
	public SerializableTestResult runScaTest();
}

SerializableTestResult is a class that embeds the fields of the TestResult (the result of a JUnit test) and which is serializable, so that we can pass it through RMI. It is contained in the SCA Test library (not described here).

The implementation of this component is made up of two important classes. The implementation itself implements the service given above and all the references of the tested component. In this example, it means we implement RunTestService, ClientManagerService and PurchaseProcessorService.

/**
 * Implementation for the JUnit component.
 */
@Service(interfaces={ RunTestService.class, ClientManagerService.class, PurchaseProcessorService.class })
public class ScaTestRunner implements RunTestService, ClientManagerService, PurchaseProcessorService {
	private HandlePurchaseService handlePurchaseService;
 
	/**
	 * Run a HandlePurchaseComponentTestCase instance.
	 * @see org.eclipse.stp.sca.example.test.generated.RunTestService#runScaTest()
	 */
	public SerializableTestResult runScaTest() {		
 
		TestSuite testSuite = new TestSuite( "Sca Test Suite"  );
		for( Method m : HandlePurchaseComponentTestCase.class.getMethods()) {
			if( !m.getName().startsWith( "test" ))
				continue;
 
			testSuite.addTest( new HandlePurchaseComponentTestCase( m.getName(), handlePurchaseService ));
		}
 
		TestResult tcResult = new TestResult();
		testSuite.run( tcResult );
 
		SerializableTestResult serialTc = new SerializableTestResult( tcResult );
		return serialTc;
	}
 
	/**
	 * @param handlePurchaseService the handlePurchaseService to set
	 */
	@Reference
	public void setHandlePurchaseService(HandlePurchaseService handlePurchaseService) {
		this.handlePurchaseService = handlePurchaseService;
	}
 
	/**
	 * Delegate the call to the mock.
	 * @see ClientManagerServiceMock#isRegistered(String)
	 */
	public boolean isRegistered(String clientName) {
		ClientManagerService mock = 
			(ClientManagerService) ScaMock.getInstance().get( ClientManagerService.class );
		return mock.isRegistered( clientName );
	}
 
	/**
	 * Delegate the call to the mock.
	 * @see ClientManagerServiceMock#registerClient(String)
	 */
	public boolean registerClient(String clientName) {
		ClientManagerService mock = 
			(ClientManagerService) ScaMock.getInstance().get( ClientManagerService.class );
		return mock.registerClient( clientName );
	}
 
	/**
	 * Delegate the call to the mock.
	 * @see PurchaseProcessorService#processPurchase(String[])
	 */
	public boolean processPurchase(String[] purchasedItemReferences) {
		PurchaseProcessorService mock = 
			(PurchaseProcessorService) ScaMock.getInstance().get( PurchaseProcessorService.class );
		return mock.processPurchase( purchasedItemReferences );
	}
}


Notice that the service to test is present as a reference in this class. Let's now take a look at the methods of this class.

All the references' operations have the same behaviour. They get a mock object from a singleton class and delegate the call to this object.

The main method, runScaTest, creates a TestSuite, instanciates a TestCase and register all the methods which begins with "test". It then runs this TestSuite and returns the result as a SerializableTestResult.


The last generated class is a TestCase (only its skeleton is generated, testing methods have to be written by the user).

// @ScaTest( composite="{targetNamespace}compositeName" name="componentA" )
// Annotation not yet defined. Attributes to review.
// Useful only inside an Eclipse project, when using a builder.
public class HandlePurchaseComponentTestCase extends TestCase {
	// Mocks.
	/** The mock for ClientManagerService. */
	private ClientManagerService clientManagerMock = 
		(ClientManagerService) ScaMock.getInstance().get( ClientManagerService.class );
 
	/** The mock for PurchaseProcessorService. */
	private PurchaseProcessorService purchaseProcessorMock = 
		(PurchaseProcessorService) ScaMock.getInstance().get( PurchaseProcessorService.class );
 
	// References.
	private HandlePurchaseService handlePurchaseService;
 
	/**
	 * @param fName
	 * @param handlePurchaseService the handlePurchaseService to set
	 */
	public HandlePurchaseComponentTestCase( String fName, HandlePurchaseService handlePurchaseService ) {
		super( fName );
		this.handlePurchaseService = handlePurchaseService;
	}
 
	@Override
	protected void tearDown() throws Exception {
		reset( clientManagerMock );
		reset( purchaseProcessorMock );
	}
 
	/* TODO: write testing methods after this comment. */
}


This skeleton has two attributes, which are the mocked up references. It has a third attribute which the service of the component to test (set in the constructor). Eventually, we configure the tearDown method to reset the behavior of the mocks between each run.


Until now, all of this has been generated. The only thing the user has to do is writing testing methods.

// @ScaTest( composite="{targetNamespace}compositeName" name="componentA" )
// Annotation not yet defined. Attributes to review.
// Useful only inside an Eclipse project, when using a builder.
public class HandlePurchaseComponentTestCase extends TestCase {
	// Mocks.
	/** The mock for ClientManagerService. */
	private ClientManagerService clientManagerMock = 
		(ClientManagerService) ScaMock.getInstance().get( ClientManagerService.class );
 
	/** The mock for PurchaseProcessorService. */
	private PurchaseProcessorService purchaseProcessorMock = 
		(PurchaseProcessorService) ScaMock.getInstance().get( PurchaseProcessorService.class );
 
	// References.
	private HandlePurchaseService handlePurchaseService;
 
	/**
	 * @param fName
	 * @param handlePurchaseService the handlePurchaseService to set
	 */
	public HandlePurchaseComponentTestCase( String fName, HandlePurchaseService handlePurchaseService ) {
		super( fName );
		this.handlePurchaseService = handlePurchaseService;
	}
 
	@Override
	protected void tearDown() throws Exception {
		reset( clientManagerMock );
		reset( purchaseProcessorMock );
	}
 
	/* TODO: write testing methods after this comment. */
 
	/**
	 * Test 1.
	 * Client registration worked but processing fails.
	 * Check the result.
	 */
	public void test1() {
		String clientName = "James Sawyer";
		String[] items = new String[] { "item1", "item5" };
 
		// Mock 1: expect a call to isRegistered with clientName as argument, and return true.
		expect( clientManagerMock.isRegistered( clientName )).andReturn( true );
		replay( clientManagerMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Mock 2: expect a call to processPurchase with items as argument, and return false.
		expect( purchaseProcessorMock.processPurchase( aryEq( items ))).andReturn( false );
		replay( purchaseProcessorMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Call the service and check the result.
		PurchaseProcessingStatus result = handlePurchaseService.handlePurchase( clientName, items );
		assertEquals( PurchaseProcessingStatus.PROCESSING_FAILED, result );
	}
 
	/**
	 * Test 2.
	 * Client registration and processing works.
	 * Check the result.
	 */
	public void test2() {
		String clientName = "James Sawyer";
		final String[] items = new String[] { "item1", "item5" };
 
		// Mock 1: expect a call to isRegistered with clientName as argument, and return true.
		expect( clientManagerMock.isRegistered( clientName )).andReturn( true );
		replay( clientManagerMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Mock 2: expect a call to processPurchase with items as argument, and return true.
		expect( purchaseProcessorMock.processPurchase( aryEq( items ))).andReturn( true );
		replay( purchaseProcessorMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Call the service and check the result.
		PurchaseProcessingStatus result = handlePurchaseService.handlePurchase( clientName, items );
		assertEquals( PurchaseProcessingStatus.OK, result );
	}
 
	/**
	 * Test 3.
	 * Client not registered.
	 * Check the result. 
	 * 
	 * Should throw an exception because the implementation should register the client 
	 * and this is not foreseen in the mock. 
	 * Just to test TestCase failures.
	 */
	public void test3() {
		String clientName = "James Sawyer";
		String[] items = new String[] { "item1", "item5" };
 
		// Mock 1: expect a call to isRegistered with clientName as argument, and return true.
		expect( clientManagerMock.isRegistered( clientName )).andReturn( false );
		replay( clientManagerMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Mock 2: expect a call to processPurchase with items as argument, and return true.
		expect( purchaseProcessorMock.processPurchase( aryEq( items ))).andReturn( true );
		replay( purchaseProcessorMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Call the service and check the result.
		PurchaseProcessingStatus result = handlePurchaseService.handlePurchase( clientName, items );
		assertEquals( PurchaseProcessingStatus.OK, result );
	}
 
	/**
	 * Test 4.
	 * Client not registered.
	 * Check the result. 
	 * 
	 * The same than test3 but this time, we foresee the call to registerClient in the mock. 
	 */
	public void test4() {
		String clientName = "James Sawyer";
		String[] items = new String[] { "item1", "item5" };
 
		// Mock 1: expect a call to isRegistered with clientName as argument, and return true.
		expect( clientManagerMock.isRegistered( clientName )).andReturn( false );
		expect( clientManagerMock.registerClient( clientName )).andReturn( true );
		replay( clientManagerMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Mock 2: expect a call to processPurchase with items as argument, and return true.
		expect( purchaseProcessorMock.processPurchase( aryEq( items ))).andReturn( true );
		replay( purchaseProcessorMock );
 
		// Any other call to this reference should throw an exception, not managed here.
 
		// Call the service and check the result.
		PurchaseProcessingStatus result = handlePurchaseService.handlePurchase( clientName, items );
		assertEquals( PurchaseProcessingStatus.OK, result );
	}
}

In these testing methods, we defined the behavior of the "references" (the mock objects) by using EasyMock methods, and we check assertions on service operation results. The difficulty here is how you can foresee the behavior of the references. It requires you to know how the references are used in the implementation of the component. From this point of view, you do not test only the service service contract but also the way and how the service is implemented.


Runnning the Tests

We now have written a TestCase for SCA and we need to run it on a SCA platform. For this example, I have run it manually on a Tuscany runtime (i.e. I have added Tuscany jars to my project class path). In the future, SCA platforms will be registered as servers in Eclipse (like J2EE servers - registration through the SOAS sub-project of STP). For the moment, Tuscany, Fabric3 and PEtALS (with its SCA service engine based on Frascati) are expected to be supported.

Running test is made by right-clicking on the TestCase to run and by selecting SCA > Run > platform, where platform is a registered runtime server. It results in the deployment of the tester application and in the generation of a client to call this application.

The server...

/**
 * Deploy the SCA test composite on a Tuscany runtime.
 */
public class ScaTestServer {
 
	public static void main( String[] args ) throws IOException {
		System.out.println( "Starting Test Application" );
		SCADomain scaDomain = SCADomain.newInstance( "TestHandlePurchase.composite" );
 
		System.out.println( "Press any key to Exit" );
		System.in.read();
		scaDomain.close();
		System.out.println( "Done." );
		System.exit( 0 );
	}
}

... and the client.

/**
 * A simple test client to call the SCA test application via RMI and print the result of the test.
 */
public class ScaTestClient {
 
	public static void main( String[] args ) throws MalformedURLException, RemoteException, NotBoundException {
		RunTestService scaTestRunner = (RunTestService) Naming.lookup( "//localhost:8099/TestSca" );
		SerializableTestResult tc = scaTestRunner.runScaTest();
		System.out.println( tc.toString());
	}
}


When both of these classes are run, it prints:

4 tests were run.

Failure(s): 0

Error(s): 1
+ test3(org.eclipse.stp.sca.example.test.unit.HandlePurchaseComponentTestCase): 
  Unexpected method call registerClient("James Sawyer"):

==> Test failed.


In Eclipse, results should rather be displayed in the usual JUnit view instead of being printed in the console.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.