Codementor Events

[How TO]Write Complex Test Cases Using a DIY Test Case Generator.

Published Oct 03, 2017Last updated Oct 10, 2017
[How TO]Write Complex Test Cases Using a DIY Test Case Generator.

What is this?

We have to write a lot of code and tests.

Sometimes, we need to maintain huge and complex code, as well as tests, that follow changes when business situations change so fast.

It's too hard to write and keep quality test code when faced with complex business logic.

We are coders. Let's fix these issues with a generator to make tests easily and quickly.

After learning this method, you can easily scale in these situations.

For Windows users

  • GitHub clone
  • yarn
  • yarn watch_xlsx
  • Define your complex business patterns in XLSX.
  • Modify generate_test_case.rb to do what you need.
  • Copy generated test code to your project.
  • Run and modify data that you want.

For Non-Windows Users

  • Clone from GitHub
  • yarn
  • yarn watch_csv
  • Define your complex business patterns in CSV.
  • Modify generate_test_case.rb to do what you need.
  • Copy generated test code to your project.
  • Run and modify data that you want.

Prepare to Use

Clone from GitHub

Open your terminal or command prompt.

git clone https://github.com/panghea/testcasegenerator

Example

This example is workflow for Item Packaging.
Item Packaging is defined by Item Category and Item Ship Type.
There are five routes to approve Item Shipping for the package.

Code Example

public class Routes {
    // result for Package Type
    public String packageType;

    public void approveA(Item item) {
        // approve with Item Category and Ship Type

        // process data and write to the database....
        // if the final approve flow record package type to this.packageType 
    }
    public void approveB(Item item) {
        // approve with Item Category and Ship Type

        // process data and write to the database....
        // if the final approve flow record package type to this.packageType 
    }
    public void approveC(Item item) {
        // approve with Item Category and Ship Type

        // process data and write to the database....
        // if the final approve flow record package type to this.packageType 
    }
    public void approveD(Item item) {
        // approve with Item Category and Ship Type

        // process data and write to the database....
        // if the final approve flow record package type to this.packageType 
    }
    public void approveE(Item item) {
        // approve with Item Category and Ship Type

        // process data and write to the database....
        // if the final approve flow record package type to this.packageType 
    }
    public String Reject(Item item) {
        // reject workflow when somthing happend at bussiness working.

        // clean up data and record where to reject approve process to the database
        return approveProcessName;
    }
}

Here is an Excel data example (input/data_pattern.xlsx)

Item Category Code Item Ship Type Approve Route A Approve Route B Approve Route C Approve Route D Approve Route E Package Type
Category A Ship Type B skip need skip skip need Package C1
Category A Ship Type C need need skip need need Package C2
Category A Ship Type D need need need need need Package A2
Category A Ship Type C skip skip skip skip need Package A3
Category B Ship Type A need need skip need need Package C1
Category B Ship Type B skip need need need need Package A2
Category B Ship Type F need need skip need need Package B7
Category C Ship Type B need need need need need Package B4
Category C Ship Type A need need skip skip need Package A3
Category D Ship Type F skip need skip need skip Package C2
Category D Ship Type G skip need skip skip need Package D1
Category E Ship Type A need skip skip skip skip Package F2

Here is CSV data example (input/data_pattern_csv.yaml)

It writes CSV inside YAML, which is useful for reading and editing for humans and converters.

data_csv: 
  - ["Category A","Ship Type B","skip","need","skip","skip","need","Package C1"]
  - ["Category A","Ship Type C","need","need","skip","need","need","Package C2"]
  - ["Category A","Ship Type D","need","need","need","need","need","Package A2"]
  - ["Category A","Ship Type C","skip","skip","skip","skip","need","Package A3"]
  - ["Category B","Ship Type A","need","need","skip","need","need","Package C1"]
  - ["Category B","Ship Type B","skip","need","need","need","need","Package A2"]
  - ["Category B","Ship Type F","need","need","skip","need","need","Package B7"]
  - ["Category C","Ship Type B","need","need","need","need","need","Package B4"]
  - ["Category C","Ship Type A","need","need","skip","skip","need","Package A3"]
  - ["Category D","Ship Type F","skip","need","skip","need","skip","Package C2"]
  - ["Category D","Ship Type G","skip","need","skip","skip","need","Package D1"]
  - ["Category E","Ship Type A","need","skip","skip","skip","skip","Package F2"]

Generate YAML from each data file (Excel or CSV)

Generate YAML from XLSX files (Windows only)

If you are using Windows, you can generate YAML from XLSX.

cd testcasegenerator
ruby xlsx2yaml.rb

Generate YAML from CSV file

If you aren't using Windows, you can generate YAML from CSV.

cd testcasegenerator
ruby csv2yaml.rb

Output YAML file

generated/generator_input.yaml

---
- ItemCategory: Category A
  ItemShipType: Ship Type B
  ApproveRouteA: skip
  ApproveRouteB: need
  ApproveRouteC: skip
  ApproveRouteD: skip
  ApproveRouteE: need
  PackageType: Package C1
- ItemCategory: Category A
  ItemShipType: Ship Type C
  ApproveRouteA: need
  ApproveRouteB: need
  ApproveRouteC: skip
  ApproveRouteD: need
  ApproveRouteE: need
  PackageType: Package C2
...

Generate Test Case File from YAML file

ruby generate_test_case.rb

Generator Source

It takes just a few codes to generate test cases.

def generateTestCase(testCase) 
    outputCodes = []
    # write method header
    outputCodes.push(<<-GENERATOR.gsub(/^$/,"")
        /*
        * <b>Test Case Of [#{testCase["ItemCategory"]}] and [#{testCase["ItemShipType"]}]</b><br>
        * <pre>
        * ======================================================
        * 2017/09/15 Tadayuki Tanigawa
        * </pre>
        * @throws Throwable
        */
        @Test
        public void testCase#{"%02d" % (testCase['caseNo']+ 1)}_#{testCase['ItemCategory'].gsub(/ /,"_")}_#{testCase['ItemShipType'].gsub(/[- ]/,"_")}() throws Throwable {
            // prepare for test data by route
                    GENERATOR
                    );

    # prepare data for each route
    outputCodes.push(prepareTestData(testCase).join())

    # do test methods
    outputCodes.push(<<-GENERATOR

            // create item
            Item item = new Item();
            item.setCategory("#{testCase["ItemCategory"].gsub(/.* /,"")}");
            item.setShipType("#{testCase["ItemShipType"].gsub(/.* /,"")}");

            // call each route 
            Routes routes = new Routes();
            GENERATOR
    )
    outputCodes.push(testRouteCall(testCase).join())

    # assert test result
    outputCodes.push(<<-GENERATOR

            // assert package type
            assertThat("Package Type", routes.packageType, is("#{testCase["PackageType"].gsub(/.* /,"")}"));
            GENERATOR
    )
    outputCodes.push(<<~GENERATOR
            #{testCase["Assertions"]}
            GENERATOR
    )

    outputCodes.push(<<-GENERATOR
        }
            GENERATOR
            )

    outputCodes
end

Generated Code

This is a generated test method by generate_test_case.rb.

If you have to add routes to your business rules, it's easy to create tests using a generator.
You just add a new item route as a row into XLSX or CSV.

        /*
        * <b>Test Case Of [Category B] and [Ship Type F]</b><br>
        * <pre>
        * ======================================================
        * 2017/09/15 Tadayuki Tanigawa
        * </pre>
        * @throws Throwable
        */
        @Test
        public void testCase07_Category_B_Ship_Type_F() throws Throwable {
            // prepare for test data by route
            // For Route A insert or update data to the Database 
            dao.update("Update ItemRouteTable ... RouteName = 'ApproveRouteA' ,itemCategory = 'Category B'....");
            // For Route B insert or update data to the Database 
            dao.insert("Insert RouteTable ... SELECT 'B' AS RouteType ,'Category B' as ItemCategory....");
            dao.update("Update ItemCategoryTable ... itemCategory = 'Category B Where itemShipType = 'Ship Type F'")
            dao.update("Update ItemRouteTable ... RouteName = 'ApproveRouteB' ,itemCategory = 'Category B'....");
            // For Route D insert or update data to the Database 
            dao.insert("Insert RouteTable ... SELECT 'D' AS RouteType ,'Category B' as ItemCategory....");
            dao.update("Update ItemRouteTable ... RouteName = 'ApproveRouteD' ,itemCategory = 'Category B'....");
            // For Route E insert or update data to the Database 
            dao.insert("Insert RouteTable ... SELECT 'E' AS RouteType ,'Category B' as ItemCategory....");
            dao.update("Update ItemRouteTable ... RouteName = 'ApproveRouteE' ,itemCategory = 'Category B'....");

            // create item
            Item item = new Item();
            item.setCategory("B");
            item.setShipType("F");

            // call each route 
            Routes routes = new Routes();
            // approve by A
            route.approveA(item);
            // approve by B
            route.approveB(item);
            // approve by D
            route.approveD(item);
            // approve by E
            route.approveE(item);

            // assert package type
            assertThat("Package Type", routes.packageType, is("B7"));
        }

Automatically Generate Code When You Save XLSX or CSV

It watches file modification and executes Ruby automatically.
You just edit generate_test_case.rb or data_pattern.xlsx or data_pattern_csv.yaml!

For Windows

npm install -g yarn
yarn watch_xlsx

For Others

npm install -g yarn
yarn watch_csv

Next

  • You can add custom expressions that treat data or assertion of test code into YAML for really complex tests.

    - ItemCategory: Category A
      ItemShipType: Ship Type B
      ....
      Assertions: |1-
                   // assert Asset Type of Category A
                   assertThat("Asset Type", item.getAssetType(), is("23"));
    

    Change generate_test_case.rb

    outputCodes.push(<<~GENERATOR
            #{testCase["Assertions"]}
            GENERATOR
    )
    

    Then generate code:

            // assert Asset Type of Category A
            assertThat("Asset Type", item.getAssetType(), is("23"));
    

    You can define YAML rules and generate results easily.

  • You can write Excel documents from YAML this way! It's really useful!
    If you are interested, CALL ME!

FAQ

  1. Why use YAML for generation?

    • YAML is easier to modify than CSV and XLSX in Text Editor.
    • YAML is a text file that's easy to manage on Git.
    • If XLSX or CSV is broken, we can re-generate from YAML to each file — just make a generator for XLSX or CSV.
    • YAML reads faster then XLSX from Ruby. We sometimes deal with big size XLSX files.
  2. Why use Ruby?

    • Ruby's here document is awesome for code generation.
    • Ruby is good for Excel automation so far.
Discover and read more posts from Tadayuki Tanigawa
get started