Test Fixture Initializer

Overtime I have been advocating the use of the standard Business Central tests in various posts, webinars, conference presentations, and my book. IMHO this has been, and still, is an undervalued collection of over 22,000 automated tests that cover all functional areas. It’s a no brainer to let them run on your solution as we did on ours. In case your solution intertwines with standard code chances are big that these tests will also hit your code resulting in a number of failing tests. As discussed in this post this run resulted in over 75 % of failing tests when executed on our solution. Meaning that indeed the standard tests did hit our code and that we had a potential horizon of thousands of tests that could be running on our solution. After 6 weeks of work we had, on our NAV 2016 solution, a test collateral of more than 14,000 tests that was going to be run every night. A test run that each morning was going to show us the sanity of that part of our solution. Today, on NAV 2018, this collateral entails 18,300 tests, mainly standard. But we have meanwhile also been writing our own.

By mainly working on the shared fixture of the tests we could raise the success rate from an initial 23 % to 72 % within one week of work. And eventually even more. Based on this work and some suggestions we also got a buy-in from Microsoft in improving their test code by adding publishers that would allow us to adjust standard tests with little to none footprint. The most import improvement was implemented by Microsoft in the Initialize function that the majority of standard tests hold to take care of the shared fixture or generic fresh fixture:

local procedure Initialize()
begin
    // Generic Fresh Setup
    LibraryTestInitialize.OnTestInitialize(<codeunit id>);
    //<generic fresh data initialization>

    // Lazy Setup
    if isInitialized then
        exit();

    LibraryTestInitialize.OnBeforeTestSuiteInitialize(<codeunit id>);

    //<shared data initialization>

    isInitialized := true;
    Commit();

    LibraryTestInitialize.OnAfterTestSuiteInitialize(<codeunit id>);
end;

Using the now available publishers we built up code that I have now published on GitHub and have called the Test Fixture Initializer. To enable the creation of a shared fixture we subscribe to the OnBeforeTestSuiteInitialize publisher as it has proven to do this before any standard code does. To achieve the same for a generic fresh fixture we subscribe to the OnTestInitialize publisher. The OnAfterTestSuiteInitialize publisher will fire after standard code has done its part on he shared fixture. We have experienced that we hardly ever use this one, but of course you might if your code needs extend the shared fixture after everything else and/or needs to happen after the Commit.

The following schema shows how the code is structured:

OnTestInitialize

At this level you will be adding code for each specific standard test codeunit in SetGenericFreshSetup in Codeunit 97004 (FLX Generic Fresh Setup) to get generic fresh fixture created:

codeunit 97004 "FLX Generic Fresh Setup"
{
    // (c) fluxxus.nl - https://github.com/fluxxus-nl/TestFixtureInitializer

    procedure SetGenericFreshSetup(CallerCodeunitID: Integer)
    begin
        case CallerCodeunitID of
            // add specific codeunits and your method to set generic fresh fixture here
            134391: // = Codeunit::"ERM Sales Batch Posting" - added id as number to not
                    // need dependency please be sure to remove this example codeunit if
                    // not relevant to you
                SetGenericFreshSetupForERMSalesBatchPosting();
        end
    end;

    local procedure SetGenericFreshSetupForERMSalesBatchPosting()
    begin
    end;
}

OnBeforeTestSuiteInitialize

At this level you will be adding each specific standard test codeunit to the case statement in  DoesCodeunitNeedLazySetup function in Codeunit 97005 (FLX Check Lazy Setup) to get standard shared fixture created for it by the Initialize function in Codeunit 97000 (FLX Initialize):

codeunit 97005 "FLX Check Lazy Setup"
{
    // (c) fluxxus.nl - https://github.com/fluxxus-nl/TestFixtureInitializer

    procedure DoesCodeunitNeedLazySetup(CallerCodeunitID: Integer): Boolean
    begin
        case CallerCodeunitID of
            // add specific codeunits here that need additonal shared fixture
            132502: // = Codeunit::"Purch. Document Posting Errors" - added id as number to
                    // not need dependency please be sure to remove this example codeunit
                    // if not relevant to you
                exit(true);
            else
                exit(false);
        end;
    end;
}

If a standard codeunit needs some specific, i.e. additional, shared fixture add the codeunit to the case statement in SetAdditionalLazySetupBefore in Codeunit 97007 (FLX Additional Lazy Setup):

codeunit 97007 "FLX Additional Lazy Setup"
{
    // (c) fluxxus.nl - https://github.com/fluxxus-nl/TestFixtureInitializer

    procedure SetAdditionalLazySetupBefore(CallerCodeunitID: Integer)
    begin
        case CallerCodeunitID of
            // add specific codeunits and your method to set additonal shared fixture here
            134069: // = Codeunit::"ERM Edit Posting Groups" - added id as number to not
                    // need dependency please be sure to remove this example codeunit
                    // if not relevant to you
                SetAdditonalLazySetupBeforeForERMEditPostingGroups();
        end
    end;

    local procedure SetAdditonalLazySetupBeforeForERMEditPostingGroups()
    begin
    end;
}

Initialize

The spill of the Test Fixture Initializer is the Initialize function in Codeunit 97000 (FLX Initialize). This is were you will need to add various functions that do create the data (or update existing CRONUS data). The next code example is an excerpt from the Initialize for our solution:

procedure Initialize()
begin
	if not DoesPrebuiltFixtureExist thn
	begin
		SetPrebuiltFixtureExists(true);

		N4LibraryLabel.CreateLabels;
		N4LibrarySetup.CreateTNTSetups;
		N4LibrarySetup.SetCompanyInformation;
		N4LibrarySetup.SetWarehouseSetup;
		N4LibrarySetup.SetInventorySetup;
		N4LibraryERM.CreateVATPostingSetups;
		N4LibraryERM.UpdateVATProdPostingGroups;
		N4LibrarySetup.SetPurchaseSetup;
		N4LibrarySetup.CreateSalesSetups;
		N4LibrarySetup.SetSalesSetups;
		N4LibrarySetup.CreateVDESetups;
		N4LibrarySetup.CreateOWSSetups;
		N4LibrarySetup.CreateOWSCommunicationSetups;
		N4LibrarySetup.CreateCBSetups;
		N4LibrarySetup.CreateOWSNumbersSetups;
		N4LibrarySetup.CreateMultiple3SNoSeries;
		N4LibrarySetup.CreateTransactionModes;
		N4LibrarySetup.CreateMultiplePaymentTerms;
		N4LibrarySetup.CreateTermPeriodDefinitions;
		N4LibrarySetup.CreateOWSSelections;
		N4LibrarySetup.CreateReqWorksheetTemplates;
		N4LibrarySetup.UpdateCustomerTemplates('STS');
		N4LibrarySetup.UpdateCustomers('STS');
		N4LibrarySetup.CreateDeliverabilityCodes;
		N4LibrarySetup.UpdateSalesDocuments('STS');
		N4LibrarySetup.UpdateItems;
	end;
end;

Initialize Prebuilt Fixture

As some of you probably know I am a huge fan of not wanting to have a prebuilt fixture. I want my test code to control and create the fixture needed so it is independent of the setting it will run in. Nevertheless, as you can see the Test Fixture Initializer project has enabled Codeunit 97008 (FLX Initialize Preb. Fixture) to run the Initialize function in Codeunit 97000 (FLX Initialize). This is what we do before a newly created test suite will be run, as this shortens the total time of the test run. For that reason the Test Setup table has been added to the project, so that Prebuilt Fixture Exists field  can be check marked once the prebuilt fixture has been created. Any time the Initialize function is to be called this field will be verified whether or not the prebuilt fixture already exists.

Hope this all makes sense and will help in getting the standard test added to your test collateral. Feel free to make use of the code, and share your suggestions for improvements be means of an issue report.

Leave a Reply

Your email address will not be published. Required fields are marked *