Test Data Factory підхід

Test Data Factory - це підхід до створення тестових даних в Apex тестах. Він дозволяє створювати вхідні дані для тестів у зручному форматі, що дозволяє зменшити кількість коду, необхідного для створення цих даних і полегшити підтримку тестів в майбутньому.
Тестові дані можна створювати в Test Data Factory, використовуючи класи Apex, які відповідають моделям даних. Ці класи можуть містити методи, які створюють записи в базі даних з відповідними значеннями поля. Підхід Test Data Factory має кілька переваг для тестування Apex коду.

Ось декілька з них:

  • Зручне створення вхідних даних: Test Data Factory дозволяє створювати вхідні дані для тестів у зручному форматі, що дозволяє ефективно зменшити кількість коду, необхідного для створення цих даних.

  • Легкість підтримки тестів: Test Data Factory дозволяє створювати тестові дані в одному місці, що зменшує кількість коду, що потрібно підтримувати.

  • Зменшення ризику помилки: Test Data Factory дозволяє зменшити кількість помилок, пов’язаних зі створенням вхідних даних для тестів, тому що даний підхід дозволяє створювати дані в структурованому форматі.

  • Збільшення швидкодії виконання тестів: Test Data Factory дозволяє створювати вхідні дані для тестів в оптимальному форматі, що дозволяє зменшити час виконання тестів і покращити їхню швидкодію.

Отже, використання Test Data Factory є хорошою практикою для створення тестових даних в Apex тестах. Він дозволяє зменшити кількість коду, збільшити швидкість тестування.
Давайте розглянемо приклад. Отже, у нас є два тригери для Account та Order. Їх код наведений ниже.

Тригер для Account:

trigger AccountTrigger on Account (before update) {
    // Loop through the accounts being updated in this trigger context.
    for(Account accountItem : Trigger.new) {
        // Check if the Industry field of the account is null.
        if (accountItem.Industry == null) {
            // If Industry is null, set its value to 'Other'.
            accountItem.Industry = 'Other';
        }
    }
}

Тригер для Order:

trigger OrderTrigger on Order (before update) {
    // Get the current user's ID.
    Id userId = UserInfo.getUserId();
    
    User userDetails = [
        SELECT Id, 
               Name, 
               Email, 
               Profile.Name, 
               UserRole.Name 
          FROM User 
         WHERE Id = :userId
    ];
    
    // Extract the UserRole Name from the retrieved user details.
    String roleName = userDetails.UserRole.Name;
    
    // Loop through the orders being updated in this trigger context.
    for (Order orderItem : Trigger.new) {
        // Check if the order has a discount, total order price is greater than 2000, and the user is from the 'Marketing Team'.
        if (orderItem.hasDiscount__c == true
            && orderItem.Total_order_price__c > 2000
            && roleName == 'Marketing Team') {
                
            // Apply a discount of 150 to the total order price.
            orderItem.Total_order_price__c = orderItem.Total_order_price__c - 150;
        }
    }
}

Як виглядають тести для цих тригерів, якщо ми не використовуємо DataFactory:
Тест для Order тригеру:

@isTest
public class OrderTriggerTest {
    // Test setup method to create necessary records and user for testing.
    @testSetup
    static void testSetup() {
        // Get the IDs of System Administrator profile and Marketing Team role.
        String AdminProfileId = [
            SELECT Id
            FROM Profile
            WHERE Name = 'System Administrator'
            LIMIT 1
        ].Id;
        String MarketingTeamroleId = [
            SELECT Id
            FROM UserRole
            WHERE Name = 'Marketing Team'
            LIMIT 1
        ].Id;

        // Create a user with Marketing Team role.
        User userMarketingTeam = new User(
            Alias = 'adm',
            Country = 'United Kingdom',
            Email = 'adm.man@test.com',
            EmailEncodingKey = 'UTF-8',
            LastName = 'admin',
            LanguageLocaleKey = 'en_US',
            LocaleSidKey = 'en_US',
            ProfileId = AdminProfileId,
            TimeZoneSidKey = 'America/Los_Angeles',
            UserName = 'admin@test.tradingscreen.com',
            UserRoleId = MarketingTeamroleId
        );
        insert userMarketingTeam;

        // Run tests within the context of the Marketing Team user.
        System.runAs(userMarketingTeam) {
            // Create test records: Account, Contact, Contract, and Order.
            Account newAccount = new Account();
            newAccount.Name = 'TestAccount';
            insert newAccount;

            Contact newContact = new Contact();
            newContact.FirstName = 'Joe';
            newContact.LastName = 'Test';
            insert newContact;

            Contract newContract = new Contract();
            newContract.AccountId = newAccount.Id;
            newContract.Status = 'Draft';
            newContract.StartDate = Date.today();
            newContract.ContractTerm = 12;
            insert newContract;

            Order newOrder = new Order();
            newOrder.AccountId = newAccount.Id;
            newOrder.ContractId = newContract.Id;
            newOrder.Status = 'Draft';
            newOrder.EffectiveDate = Date.today();
            insert newOrder;
        }
    }
    // Test method to check the functionality of the OrderTrigger.
    @isTest
    static void checkLeadSourceField() {
        // Get the ID of Marketing Team role.
        String MarketingTeamroleId = [
            SELECT Id
            FROM UserRole
            WHERE Name = 'Marketing Team'
            LIMIT 1
        ].Id;

        // Query the Order record to be updated.
        Order orderToUpdate = [
            SELECT hasDiscount__c, Total_order_price__c
            FROM Order
            WHERE Status = 'Draft'
        ];

        // Get the Marketing Team user.
        User userMarketingTeam = [
            SELECT Id
            FROM User
            WHERE UserRoleId = :MarketingTeamroleId
            LIMIT 1
        ];

        // Run tests within the context of the Marketing Team user.
        System.runAs(userMarketingTeam) {
            // Update fields on the Order record and test the trigger logic.
            orderToUpdate.hasDiscount__c = true;
            orderToUpdate.Total_order_price__c = 2050;
            Test.startTest();
            update orderToUpdate;
            Test.stopTest();
        }

        // Query the Order record after the update.
        Order orderWithDiscount = [
            SELECT hasDiscount__c, Total_order_price__c
            FROM Order
            WHERE Status = 'Draft'
        ];

        // Verify that Total_order_price__c field was modified by the trigger
        System.assertNotEquals(orderToUpdate.Total_order_price__c, orderWithDiscount.Total_order_price__c);
    }
}

Тест для Account тригеру:

@isTest
public class AccountTriggerTest {
    // Test setup method to create a new Account record.
    @testSetup
    static void testSetup() {
        // Create a new Account record for testing.
        Account newAccount = new Account();
        newAccount.Name = 'TestAccount';
        insert newAccount;
    }

    // Test method to check the functionality of the AccountTrigger.
    @isTest
    static void checkIndustryField() {
        // Query the Account record to be updated.
        Account accountToUpdate = [
            SELECT Industry
            FROM Account
            WHERE Name = 'TestAccount'
        ];

        // Start a test context to simulate an update operation on the Account record.
        Test.startTest();
        update accountToUpdate;
        Test.stopTest();

        // Query the updated Account record.
        Account accountWithOtherIndustry = [
            SELECT Industry
            FROM Account
            WHERE Name = 'TestAccount'
        ];

        // Assert that the Industry field of the Account record was set to 'Other' after the update.
        System.assertEquals('Other', accountWithOtherIndustry.Industry);
    }
}

Як можна помітити, для тестування тригеру для Order нам потрібно створити чимало даних. А уявіть в рамках проекту, як можуть бути між собою пов’язані об’єкти та якими масштабними будуть testSetup для них, й скільки часу кожен розробник витратить на цю роботу. DataFactoty може значно скоротити й спростити все. Давайте розглянемо код нижче.

DataFactoty клас:

public class TestDataFactory {
    // Constants to store Profile Id and Marketing Team Role Id.
    private static final String ADMIN_PROFILE_ID = [
        SELECT Id
        FROM Profile
        WHERE Name = 'System Administrator'
        LIMIT 1
    ].Id;
    private static final String MARKETING_TEAM_ROLE_ID = [
        SELECT Id
        FROM UserRole
        WHERE Name = 'Marketing Team'
        LIMIT 1
    ].Id;

    // Method to create test data.
    public static void createTestData() {
        // Create a user with Marketing Team role.
        User userMarketingTeam = new User(
            Alias = 'adm',
            Country = 'United Kingdom',
            Email = 'adm.man@test.com',
            EmailEncodingKey = 'UTF-8',
            LastName = 'admin',
            LanguageLocaleKey = 'en_US',
            LocaleSidKey = 'en_US',
            ProfileId = ADMIN_PROFILE_ID,
            TimeZoneSidKey = 'America/Los_Angeles',
            UserName = 'admin@test.tradingscreen.com',
            UserRoleId = MARKETING_TEAM_ROLE_ID
        );
        insert userMarketingTeam;

        // Run tests within the context of the Marketing Team user.
        System.runAs(userMarketingTeam) {
            // Create test records: Account, Contact, Contract, and Order.
            Account newAccount = new Account();
            newAccount.Name = 'TestAccount';
            insert newAccount;

            Contact newContact = new Contact();
            newContact.FirstName = 'Joe';
            newContact.LastName = 'Test';
            insert newContact;

            Contract newContract = new Contract();
            newContract.Name = 'Test';
            newContract.AccountId = newAccount.Id;
            newContract.Status = 'Draft';
            newContract.StartDate = Date.today();
            newContract.ContractTerm = 12;
            insert newContract;

            Order newOrder = new Order();
            newOrder.Name = 'Test';
            newOrder.AccountId = newAccount.Id;
            newOrder.ContractId = newContract.Id;
            newOrder.Status = 'Draft';
            newOrder.EffectiveDate = Date.today();
            insert newOrder;
        }
    }

    // Method to retrieve test data and organize it in a map for testing purposes.
    public static Map<String, Map<String, SObject>> getTestData() {
        Map<String, Map<String, SObject>> testData = new Map<String, Map<String, SObject>>();
        // Retrieve and organize User, Account, Contact, Contract, and Order records.
        testData.put('users', new Map<String, SObject>([
            SELECT LastName, UserRoleId, Name
            FROM User
        ]));
        testData.put('accounts', new Map<String, SObject>([
            SELECT Name, Industry
            FROM Account
        ]));
        testData.put('contacts', new Map<String, SObject>([
            SELECT LastName, LeadSource
            FROM Contact
        ]));
        testData.put('contracts', new Map<String, SObject>([
            SELECT Name
            FROM Contract
        ]));
        testData.put('orders', new Map<String, SObject>([
            SELECT Name, hasDiscount__c, Total_order_price__c
            FROM Order
        ]));
        return testData;
    }
}

І тепер тести можна переписати наступним чином. У наступних прикладах тестові дані будуть формуватися у класі TestDataFactory.
Тест для Order тригеру з використанням Data Factory:

@isTest
public class OrderTriggerTestDataFactory {
    // Retrieve test data from TestDataFactory class.
    private static final Map<String, Map<String, SObject>> TEST_DATA = TestDataFactory.getTestData();

    // Test setup method to create necessary test data.
    @testSetup
    static void testSetup() {
        // Call the createTestData method from TestDataFactory to create required test data.
        TestDataFactory.createTestData();
    }

    // Test method to check the functionality of the OrderTrigger.
    @isTest
    static void checkLeadSourceField() {
        // Retrieve the Order record to be updated from the test data.
        Order orderToUpdate = (Order)TEST_DATA.get('orders').get('Test');

        // Retrieve the user with Marketing Team role from the test data.
        User userMarketingTeam = (User)TEST_DATA.get('users').get('admin');

        // Run tests within the context of the Marketing Team user.
        System.runAs(userMarketingTeam) {
            // Update fields on the Order record.
            orderToUpdate.hasDiscount__c = true;
            orderToUpdate.Total_order_price__c = 2050;

            // Start a test context to simulate an update operation on the Order record.
            Test.startTest();
            update orderToUpdate;
            Test.stopTest();
        }

        // Query the Order record after the update.
        Order orderWithDiscount = [
            SELECT hasDiscount__c, Total_order_price__c
            FROM Order
            WHERE Status = 'Draft'
        ];

        // Verify that Total_order_price__c field was modified by the trigger.
        System.assertNotEquals(orderToUpdate.Total_order_price__c, orderWithDiscount.Total_order_price__c);
    }
}

Та ми можемо використовувати ці дані у всіх тестах та розширювати Data Factory відповідно до наших потреб.
Тест для Account тригеру з використанням Data Factory:

@isTest
public class AccountTriggerTestDataFactory {
    // Retrieve test data from TestDataFactory class.
    private static final Map<String, Map<String, SObject>> TEST_DATA = TestDataFactory.getTestData();

    // Test setup method to create necessary test data.
    @testSetup
    static void testSetup() {
        // Call the createTestData method from TestDataFactory to create required test data.
        TestDataFactory.createTestData();
    }

    // Test method to check the functionality of the AccountTrigger.
    @isTest
    static void checkIndustryField() {
        // Retrieve the Account record to be updated from the test data.
        Account accountToUpdate = (Account)TEST_DATA.get('accounts').get('TestAccount');

        // Start a test context to simulate an update operation on the Account record.
        Test.startTest();
        update accountToUpdate;
        Test.stopTest();

        // Query the Account record after the update.
        Account accountWithOtherIndustry = [
            SELECT Industry
            FROM Account
            WHERE Name = 'TestAccount'
        ];

        // Verify that the Industry field of the Account record was set to 'Other' after the update.
        System.assertEquals('Other', accountWithOtherIndustry.Industry);
    }
}

Отже, у цій статті було розглянуто важливий аспект тестування програмного забезпечення - генерацію тестових даних за допомогою підходу, відомого як Test Data Factory. На прикладі конкретної ситуації було продемонстровано, як він може бути використаний для створення реалістичних тестових сценаріїв.

Цей підхід не лише поліпшує якість тестування, але й заощаджує час та ресурси, які можуть бути витрачені на ручне створення тестових даних. Крім того, Test Data Factory може бути особливо корисний при тестуванні великих та складних систем, де необхідно проводити тестування великої кількості варіантів вхідних даних.

6 Вподобань