2.50/Automated Testing/How to automate a test case
Define what you want to test
The first step is to get a high level definition of the functionality that will be tested.
Please take into account that tests should be simple, independent and stable. And that we try that most of our tests follow the principle described by the GUIUnit concept. So multiple small tests that focus on a single feature of the application are preferred to complex and huge ones.
Document the scenario and execute it manually
There has to be a manual process before trying to automate a test. It's necessary to write down the steps of the test and execute them manually many times.
Document your test following the guidelines described in How to document a test. Follow the documentation by yourself to be sure that you have specified all the required preconditions and that the steps of the scenario are simple and clear.
Try to keep the tests as simple as possible. If there are multiple conditions or the steps are too long you must split the test to make shorter ones that test a single path of actions.
Always try to keep tests as much independent as possible.
Understand the design
Please read the Automations design guidelines and implementation.
QA team at Openbravo has created a framework to ease test automation process and to assure that tests will be simple, reusable, stable and coherent.
You should follow the same guidelines, this way you won't have to start the automation from zero; and it's going to be easier for you to execute the tests and review executions.
Read the documentation and implementation of the main classes
The source code of tests cases is publicly available at Openbravo's mercurial server.
You can get it or configure Eclipse to see how the common classes and existing tests have been coded.
There's also a class diagram and you can find the Javadocs.
Look for code that you can reuse
Chances are that some of the code you require was already written by somebody else. You should start looking for classes that you can reuse or extend.
You will find classes used by multiple tests on com.openbravo.test.integration.common package. Some of them are explained at the Main classes section of design document. The rest should have a self-explanatory name and javadocs, but if you have doubts about something related to this package you can ask in the automation forum.
Classes created for tests already automated are grouped on packages with the name of the corresponding menu section. For example, there's a package named com.openbravo.test.integration.general.application' and another called com.openbravo.test.integration.procurement.transactions. If you are looking for a specific screen, check if there's a class for it in the package with the same name of it's parent menu item.
Code missing screen definitions
If the screen classes required for your test hasn't been created, you will have to create them and write the code to access HTML elements, execute and verify actions.
You should follow the same convention and store new classes on the package named after the menu item from which the screen is accessed.
Note: remember that classes must extend from FormWindow, ProcessWindow or PopUp, depending on the type of screen you are testing. Inside this parent classes you will find common variables and functions that might be useful for the test. |
If the screen classes already exist, but there are missing actions and verifications to complete your test, you should edit and extend those classes.
First, identify the HTML elements of the screen that you need to click, type to, select from or manipulate in some other way. Read the source of the page or use Firebug or Xpath extensions to find the identifier of the element.
Note: you might find some elements that can't be accessed by their identifier because they have none, or because it's not unique. In this case you can use Xpath to locate the element; but try to keep the Xpath expression as simple as possible. If the element is a button, text field, combo box or check box, please file a bug |
There must be a constant defined for each element. Define that constant in it's own screen class following the next rule:
/** Comments that describe the element */ private static final String ELEMENT_TYPE_ELEMENT_DESCRIPTION = "identifier";
where:
- ELEMENT_TYPE can be TEXT_FIELD, COMBO_BOX, LINK, TABLE, or something that clearly states the type of element you are referring to.
- ELEMENT_DESCRIPTION is the English label of the element. If it has no label you can use something that uniquely identifies it or it's purpose.
Example:
/** Identifier of the combo box with tax names */ private static final String COMBO_BOX_TAX = "reportC_Tax_ID_S";
After you have coded all the element constants, read test documentation again to define the functions and parameters required to execute it. For example, if the test says that a purchase order header for a business partner has to be created and then completed; you must define at least two functions: create and complete.
/** * Create a purchase order * * @param businessPartnerKey The search key of the busienss partner */ public void create(String businessPartnerKey) { clickCreateNewRecord(); selenium.click(BusinessPartnerSelector.BUTTON_BUSINESS_PARTNER); BusinessPartnerSelector businessPartnerSelector = new BusinessPartnerSelector(selenium); businessPartnerSelector.selectBusinessPartner(businessPartnerKey); selenium.selectWindow(MAIN_WINDOW); clickSave(); }
/** * Complete the purchase order */ public void complete() { switchToFormView() selenium.click(BUTTON_ACTION); ActionPopUp actionPopUp = new ActionPopUp(selenium); actionPopUp.action(ActionPopUp.LABEL_COMPLETE); selenium.selectWindow(MAIN_WINDOW); }
Finally, take from the documentation the things that you must verify to assure that the test passed or failed. To check a message, or to verify that a text field has the expected value are common verifications. This is the most important part of the test, and you must define additional functions with assertions for every verification.
/** * Verify the amounts displayed on purchase order header * * @param summedLines the expected summed lines amount * @param grandTotal the expected grand total */ public void verifyAmounts(String summedLines, String grandTotal) { switchToFormView() assertThat(selenium.getAttribute(TEXT_FIELD_SUMED_LINE_AMOUNT + VALUE.attribute()), is(equalTo(summedLines))); assertThat(selenium.getAttribute(TEXT_FIELD_GRAND_TOTAL_AMOUNT + VALUE.attribute()), is(equalTo(grandTotal))); }
Define the dependencies datasets
If there are preconditions for your test to be executed, or it requires some data before it can be run, the data should be inserted into the database using DbUnit. For this, precondition data has to be expressed as FlatXMLDataSet files that will be loaded into the database by DbUnit.
First define what data is required by the test. For example, in order to test that a Sales Order can be completed, there must be an existent opened Sales Order in the system.
Then, we recommend to follow all the steps in Openbravo ERP interface that are required to create that data. It is possible to define the dataset directly by hand but it requires a lot of knowledge of Openbravo ERP database model, it is hard and error prone.
Once that precondition data is present in database you will have to extract that data with a SQL query. For the example of the Sales Order, something like this will work:
select * from c_order where c_order_id = '{The ID of the last added order}';
After checking that the returned records are the ones required by your test, you can either export the rows as XML from your SQL client, or use the FlatXMLDataSets utility class.
Note: it is probable that your client doesn't export the XML on the format expected by DbUnit, so if you choose that option you will have to make sure that the XML file can be interpreted by DbUnit. We recommend to use the utility class, as the file exported can be directly used by DbUnit. |
Note: Some test will require more than one record, or records from more than one table. For example, a test that requires some Sales Order lines. We recommend to keep each dataset as small as possible and then composite them on a single dataset as explained later. A good idea would be to keep records only from one table on each dataset. |
At this moment you should have all the data as XML datasets. But this can't be directly inserted into the database because some values should be unique. On our example, if we try to add the Sales Order as was exported obviously it will fail because a record with the same ID already exists on database. To solve this we use placeholder objects that later will be replaced by UUIDs to assure that the same dataset can be inserted many times without problems. So, replace every value on your dataset that has to unique by something like [Placeholder].
Note: If the dataset is composed by multiple records related by foreign keys, the placeholder name should be the same to hold this relations. For example, on the case of a Sales Order and Sales Order Lines we set the value of c_order_id on the dataset to [C_ORDER_ID]. If we want this order to have some lines, on the field c_order_id of the lines we have to set the same placeholder: [C_ORDER_ID] |
An additional consideration has to be taken if we require to select the precondition data from a group of similar data. In these cases an additional placeholder that will be know by the test can be used as an identifier. For example, in order to complete the Sales Order it has to be selected on the grid view that shows all existing Sales Orders. If we define a placeholder for the document number (something like documentno=[DocumentNumber] on the XML dataset), then we will be able to locate our Sales Order from the rest and complete the correct one.
Now the XML files should be ready, so tehy can be handled by DbUnit. And we do this on the test controller.
Write the test controller
At this point, all requirements to run the test are coded and ready to be executed. The last class that has to be created is the test controller. In this class you will define every step of the test, calling the correct screen class to execute the desired action and verify that it was correctly executed.
The name of this class must begin with the identifier of the test, followed by a short description of the process being tested. In addition, it must inherit from OpenbravoERPTest because it includes preconditions and global variables required for every test.
This class will have one and only one function annotated with junit's @Test tag; and this function will have the same description of the test as it's name, prefixed by the word "test".
Example: from D_01_CreatePurchaseOrder
/** * Test purchase order creation */ @Test public void testCreatePurchaseOrder() { Login login = new Login(selenium); login.open(openbravoContext); login.login(userName, password); login.verify(true, userName); Menu menu = new Menu(selenium); menu.select(Menu.PROCUREMENT_MANAGEMENT, Menu.PROCUREMENT_MANAGEMENT_TRANSACTIONS, Menu.PURCHASE_ORDER); PurchaseOrderHeader purchaseOrderHeader = new PurchaseOrderHeader(selenium); purchaseOrderHeader.create("VA"); purchaseOrderHeader.verifySave(); // (...) // create some lines and verify their creation // (...) purchaseOrderHeader.complete(); purchaseOrderHeader.verifyProcessCompletedSuccessfully(); purchaseOrderHeader.verifyAmounts("2", "2.2"); }
If the test has some preconditions that will be inserted into database with DbUnit (as explained in previous section), some work has to be done before.
For this case, we should override the setup function and perform all DbUnit actions there.
@Override @Before public void setUp() throws Exception { // DbUnit actions here. super.setUp(); }
The first DbUnit action will be to get a connection to the database:
final Connection connection = DataAccess.connect(); final IDatabaseConnection dbUnitconnection = Connections.getDbUnitConnection(connection);
Then, if the dataset is split in various FlatXMLDataSet files, we will have to make a composite dataset out of them:
IDataSet compositeDataSet = CompositeDataSets.compositeFromFlatXmlDataSets(pathToDataset1, pathToDataset2);
After that, the placeholder objects should be replaced by the proper values that will be used on the test. Either UUIDs or a know value:
ReplacementDataSet replacementDataSet = new ReplacementDataSet(compositeDataSet); // Replace the column value that will be used to identify the order. replacementDataSet.addReplacementObject("[DocumentNumber]", documentNumber); // Replace the rest values with UUIDs. ReplacementDataSets.replaceWithUUIDs(replacementDataSet);
Finally, the REFRESH operation will be executed in order to insert the values into the Database:
try { DatabaseOperation.REFRESH.execute(dbUnitconnection, replacementDataSet); } finally { connection.close(); }
Test your test
Execute your test as many times as possible under multiple and variable environments. After you are sure that it will work as expected, and that you didn't forget preconditions, steps or verifications, you can add it to a testing suite and include it to a phase of your testing process.