Creating unit tests with data isolation

In this instance, our test case will create data that will used to test that the records are inserted correctly and the methods perform the correct behavior.

The test will be to create a number of vehicle service records and test the validateField, modifiedField, and insert methods. We also want to test creating a number of these records to test performance of this table.

The steps involved in this scenario are the same as before, except that we need to tear down the data we created. We will also see that a test case can actually contain far more code than the code that we are testing. This is normal, and we expect to see a 3:1 ratio of test code to business logic.

The framework supports automatic teardown of test data using company isolation. This is done by creating a temporary company and deleting it after the test method or class completes.

Note

Do not try and tear down the data using ttsAbort, or else you will roll back the test result data.

We control the isolation of our test case by overriding the createSuite method and returning a new instance of one of the following methods:

  • SysTestSuite: This does not provide automatic teardown, and must be manually cleaned up in the class's tear-down method
  • SysTestSuiteCompanyIsolateClass: A company will be created, the test methods executed, and then the company is deleted
  • SysTestSuiteCompanyIsolateMethod: A company will be created per test method, isolating each test method's data from each other

We will use the SysTestSuiteCompanyIsolateClass suite for our test, as we want to create a reasonable amount of setup data and test each method on this data.

There is a second part to this, and that is whether the test case should run in a single instance. The default is false, which means that the class is instantiated for each test method. This means that the setup method will be executed for each test method with no teardown or cognizance of the other instances of the class. So, if we are using company isolation by class, we normally do want the class to execute as one instance and prevent the data setup execution for subsequent test methods.

The steps used to create the unit test case are as follows:

  1. Right-click on the TestSuite node and choose New | Class.
  2. Rename the class to ConFMSVehServiceTableTest.
  3. As before, we need to decorate the declaration, but we will have variables that we can use to build the test data, as follows:
    [SysTestTargetAttribute(tableStr(ConFMSVehServiceTable), UtilElementType::Table)]
    class ConFMSVehServiceTableTest extends SysTestCase
    {
        int numberOfServiceRecordsToCreate;
        int numberOfVehicles;
        // set up is run for each test method
        // we don't want the data created for each
        // test method as we will isolate at class level
        boolean dataIsReady;
    }
  4. The setup data method is a little more complicated, so we will create a method for this and we will call it from the overridden setUp method. It is written as follows:
    private void createTestSetupData()
    {
        ConFMSVehicleTable    vehicle;
        ConFMSVehicleGroup    vehGroup;
        ConFMSVehicleSchedule schedule;
        Counter               idx;
        ConFMSVehicleId       vehicleId;
    
        vehGroup.clear();
        vehGroup.VehicleGroupId = "TestGroup";
        vehGroup.insert();
    
        schedule.clear();
        schedule.VehicleScheduleId = "1";
        schedule.Days = 30;
        schedule.Distance = 2000;
        schedule.insert();
    
        schedule.clear();
        schedule.VehicleScheduleId = "2";
        schedule.Days = 60;
        schedule.Distance = 4000;
        schedule.insert();
        schedule.clear();
    
        schedule.VehicleScheduleId = "3";
        schedule.Days = 365;
        schedule.Distance = 30000;
        schedule.insert();
    
        for(idx = 1; idx <= numberOfVehicles; idx++)
        {
            vehicleId = strFmt("V%1",
                              strReplace(
                                  strRFix(int2str(idx),5),
                                  " ", "0"));
            vehicle.clear();
            vehicle.VehicleId = vehicleId;
            vehicle.Name = strFmt("Vehicle %1", vehicleId);
            vehicle.VehicleGroupId = vehGroup.VehicleGroupId;
            vehicle.VehicleSchedId = int2str((idx mod 3) + 1);
            vehicle.insert();
        }
        dataIsReady = true;
    }
  5. To set up the data for the test, we override the setUp method—right-click on the class and go to Override method | setUp.
  6. Our setup data is used simply to select the first record in the view, as shown here:
    public void setUp()
    {
        // These parameter variables will be also
        // be used by the test methods
        numberOfServiceRecordsToCreate = 20;
        numberOfVehicles = 200;
        if(!isDataReady)
            this.createTestSetupData();
        super();
    }
  7. We now need to specify the isolation level, which is done by overriding createSuite so that it returns a new instance of SysTestSuiteCompanyIsolateClass:
    public SysTestSuite createSuite()
    {
        return new SysTestSuiteCompanyIsolateClass(this);
    }
  8. The final step, prior to creating our test methods, is to ensure the test case will run as a single instance, otherwise the isDataReady variable will always be false. Override the useSingleInstance method and return true, as shown here:
    public boolean useSingleInstance()
    {
        return true;
    }
  9. The code for the test methods is as follows:
    [SysTestMethodAttribute]
    public void testInitFromConFMSVehicleTable()
    {
        ConFMSVehicleTable    vehicle;
        ConFMSVehServiceTable vehServices;
        while select vehicle
        {
            vehServices.clear();
            vehServices.initFromConFMSVehicleTable(vehicle);
            this.assertEquals(vehicle.VehicleId,
                              vehServices.VehicleId,
                              strFmt("Vehicle %1 initialised",
                                     vehicle.VehicleId));
        }
    }
    [SysTestMethodAttribute]
    public void testValidateField()
    {
        ConFMSVehicleTable    vehicle;
        ConFMSVehServiceTable vehServices;
        Counter               idx;
        ConFMSVehicleSchedId  serviceSchedId;
        Days                  serviceDaysExpected;
        Distance              serviceDistanceExpected;
    
        while select vehicle
        {
            for(idx = 1;
                idx<= numberOfServiceRecordsToCreate;
                idx ++)
            {
                vehServices.clear();
                vehServices.VehicleId = vehicle.VehicleId;
                // test invalid data
                vehServices.ServiceDate = systemDateGet() + 5;
                vehServices.NextServiceDate = 
                                vehServices.ServiceDate - 2;
                this.assertFalse(vehServices.validateField(
                                fieldNum(ConFMSVehServiceTable,
                                         NextServiceDate)));
    
                vehServices.CurrentOdometer = 1000;
                vehServices.NextServiceOdo = 
                                vehServices.CurrentOdometer-2;
                this.assertFalse(vehServices.validateField(
                                fieldNum(ConFMSVehServiceTable,
                                         NextServiceOdo)));
                // test valid data
                vehServices.ServiceDate = systemDateGet() + 5;
                vehServices.modifiedField(
                                fieldNum(ConFMSVehServiceTable,
                                         ServiceDate));
                this.assertTrue(vehServices.validateField(
                                fieldNum(ConFMSVehServiceTable,
                                         NextServiceDate)));
                vehServices.CurrentOdometer = 1000;
                vehServices.modifiedField(
                                fieldNum(ConFMSVehServiceTable,
                                         CurrentOdometer));
                this.assertTrue(vehServices.validateField(
                                fieldNum(ConFMSVehServiceTable,
                                         NextServiceOdo)));
            }
        }
    }
    [SysTestMethodAttribute]
    public void testModifiedField()
    {
        ConFMSVehicleTable    vehicle;
        ConFMSVehServiceTable vehServices;
        Counter               idx;
        ConFMSVehicleSchedId  schedId;
        Days                  serviceDaysExpected;
        Distance              serviceDistanceExpected;
        TransDate             serviceDateExpected;
        while select vehicle
        {
            for(idx = 1;
                idx<= numberOfServiceRecordsToCreate;
                idx++)
            {
                vehServices.clear();
                vehServices.VehicleId = vehicle.VehicleId;
                vehServices.ServiceDate = systemDateGet();
                vehServices.modifiedField(
                                fieldNum(ConFMSVehServiceTable,
                                         ServiceDate));
    
                schedId = vehicle.VehicleSchedId;
                serviceDateExpected = dateNull();
                if(ConFMSVehicleSchedule::exist(schedId))
                {
                    serviceDaysExpected = 
                            ConFMSVehicleSchedule::
                                    find(schedId).Days;
                    serviceDateExpected = 
                            vehServices.ServiceDate +
                            serviceDaysExpected;
                }
                this.assertEquals(serviceDateExpected,
                                  vehServices.NextServiceDate);
    
                vehServices.CurrentOdometer = 1000;
                vehServices.modifiedField(
                                fieldNum(ConFMSVehServiceTable,
                                         CurrentOdometer));
    
                serviceDistanceExpected = 0;
                serviceSchedId = vehicle.VehicleSchedId;
                if(ConFMSVehicleSchedule::exist(schedId))
                {
                    serviceDistanceExpected = 
                                vehServices.CurrentOdometer +
                                ConFMSVehicleSchedule::
                                 find(schedId).Distance;
                }
                this.assertEquals(serviceDistanceExpected,
                                  vehServices.NextServiceOdo);
            }
        }
    }
    [SysTestMethodAttribute]
    public void testInsert()
    {
        ConFMSVehicleTable    vehicle;
        ConFMSVehServiceTable vehServices;
        Counter               idx;
        while select vehicle
        {
            for(idx = 1;
                idx<= numberOfServiceRecordsToCreate;
                idx++)
            {
                vehServices.clear();
                vehServices.initFromConFMSVehicleTable(
                                                  vehicle);
                vehServices.ServiceId =
                               vehicle.VehicleId+int2str(idx);
                vehServices.insert();
                this.assertEquals(vehicle.VehicleId,
                                  vehServices.VehicleId,
                                  strFmt("Vehicle %1 inserted",
                                         vehicle.VehicleId));
                this.assertNotEqual(vehServices.RecId,
                                  0,
                                  strFmt("Vehicle %1 inserted",
                                         vehicle.VehicleId));
            }
        }
    }
  10. In this instance, we expect the cases to pass, so we first go back to each test method and change them to what we expect to fail.
  11. Close the code window and compile the class.
  12. To execute this test case, right-click on the class and go to Add-ins | Run test case.

As before, the system will provide the results of the test in the Infolog window, marking the test methods as succeeded or failed. In the following example, a test method had a compilation error:

Creating unit tests with data isolation

Setup and teardown of test data

In our example, we used company isolation of data. This creates a company for each class, runs the setup method to create the setup data, and deletes the company after the test class completes. This is sufficient in our custom code, as the test data is very simple. However, this is insufficient for many testing requirements, as the configuration data is just as important as the code. This is usually the case when we are running the tests to validate code on the test environment. In this case, we can run our test cases without isolation, use SQL database backup and restore as the setup and teardown steps. This is easily possible only when using the TFS development method, where each developer has their own instance, which is another advantage of using this method of development.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.27.251