Advance Design Patterns

Desktop, API, Data warehouse

frontend

Topics in the series

1. Design patterns and concepts in test automation

2. Advanced design patterns in web test automation

3. Advanced design patterns - Desktop, API, Data warehouse

Table of contents

  1. Desktop testing considerations

  2. API testing

  3. Data warehouse testing

Desktop testing considerations

mobile

Challenges

Heavy environment and infrastructure setup.

Executing tests in parallel.

How to leverage common code as much as possible and drive the web/mobile/desktop workflow based on the configuration.

CI server desktop setup and keep-alive sessions.

Many automation tools are based off on coordinates or image recognition, but those tend to be flaky, more often than not.

Screen objects

As Design patterns are general reusable solutions to a commonly occurring problems within a given context.

It is feasible to apply PageObject model and pywinauto, since selectors and their resolution are resolved at a single place in our code.

Python example

screens/notepad_main_screen.py
						
							from pywinauto.application import Application

class NotepadBaseScreen(object):

    def __init__(self):
        self.app = Application(backend="uia").start("notepad.exe")

    def save_document(text_to_save):
	# real encapsulation happens, as element and action are one
	self.app.UntitledNotepad.menu_select("File->SaveAs")
	self.app.SaveAs.wait("exists enabled visible ready")
	self.app.SaveAs.ComboBox5.select("UTF-8")
	self.app.SaveAs.edit1.set_text(text_to_save)
	self.app.SaveAs.Save.click()
                        

Python example

API testing

wrong-design

Rest API

We focus on sending requests to the API, receive responses, and verify the system's behaviour.

As backend, it mainly concentrates on the Business (domain) logic layer of the software architecture.

Technical acumen is a must! We need to deeply understand how technology stack works.

API testing approach

Following points, aim to help us perform API Testing:

Understanding the functionality of the API program and clearly define the scope of the tests.

Apply testing techniques such as BVA, Equivalence classes, and Error guessing when designing and implementing the tests.

Input parameters for the API need to be defined appropriately.

Execute the test cases and compare expected and actual results.

DSL

“A domain-specific language (DSL) is a computer language specialized to a particular application domain.”
-- Wikipedia

DSL (2)

Is created specifically to solve problems in a particular domain and is not intended to be able to solve problems outside of it (although that may be technically possible).

The DSL could be as simple as an object that interacts with the SUT. As you implement it, you can make up the structure for it as it suits your need.

DSL (3)

    Could sets up data in the database,
  1. or in-memory versions of it

  2. or create mocks

  3. or interact with the system by executing the commands we're testing

  4. and finally do the assertions of the state of the system.

C# example

dsl/wallet.cs
						
public class Wallet
{
  private readonly AtmService _atmService;
  private readonly AccountService _accountService;
							
  public Wallet(AtmService atmService, AccountService accountService)
  {
    _atmService = atmService;
    _accountService = accountService;
  }
							
  public void Withdraw(int accountNumber, int pin, int amount)
  {  // Call the service wrapper for our account
    _accountService.SetAuthotization(accountNumber, pin);
    _atmService.Withdraw(accountNumber, amount);
  }
}
						

Command pattern

A behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time.

Encapsulate a request as an object, thereby letting you parametrize clients with different requests, queue or log requests, and support undoable operations.

Depending on the programming language, we might need to go with Servant design pattern, to pass an object, implementing an interface which declares the signature of passed method.

Command pattern (2)

A command knows about receiver and invokes its methods.

The receiver and parameter values for its methods are stored in the command.

An invoker object knows how to execute a command, and optionally does bookkeeping about the command execution.

Invoker, command and receiver objects are held by a client, which decides which receiver it should assign to the command, and which commands to assign to the invoker. The client decides which commands to execute at which points.

Command pattern (3)

Command pattern (3)

Command pattern (3)

Command pattern (3)

Command pattern (3)

Command pattern (3)

Strategy pattern

Could be used to if we need more than one implementation of the same workflow, which is done differently.

Depending on the context we could choose the implementation, at runtime.

Strategy pattern (2)

Python example

dsl/register_strategies.py
						
import types

class RegisterStrategy:
    def __init__(self, func = None):
        if func is not None:
            self.execute = types.MethodType(func, self)
							
    def execute(self):
        print('Registration via Web')
							
def execute_api(self): 
    print('Registration via API')
							
def execute_database(self):
    print('Registration via DB')							
						

Python example

dsl/register_strategies.py
						
import types

class RegisterStrategy:  # xUnit Fixture patterns
    def __init__(self, func = None):
        if func is not None:
            self.execute = types.MethodType(func, self)
							
    def execute(self):
        print('Registration via Web')  # Delegated Setup
							
def execute_api(self): 
    print('Registration via API')  # Inline setup
							
def execute_database(self):
    print('Registration via DB')  # Prebuild or Suite Setup							
						

Python example

dsl/register_strategies.py
						
							if __name__ == '__main__':
   web = RegisterStrategy()
   api = RegisterStrategy(execute_api)  # python first-class functions
   db = RegisterStrategy(execute_database)
   web.execute()  # prints "Registration via Web"
   api.execute()  # prints "Registration via API"
   db.execute()   # prints "Registration via DB"
						

Proxy

In its most general form, is a class functioning as an interface to something else. The proxy could be interface to anything: a network connection, a large object in memory, or some other resource that is expensive or impossible to duplicate.

Is useful when we want to have control over some additional recourses. The most popular method of using it in tests, is to set up the HTTP proxy. It allows dynamically to enable and disable host blacklists, excluding or stubbing the third-party services, like Facebook or Google in our tests.

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

Proxy pattern (2)

JS example

core/media_caching_proxy.js
						
// networkFetch(url) by defaul will download resources every time
const cache = [];
const proxiedNetworkFetch = new Proxy(networkFetch, {
  apply(target, thisArg, args) {
    const urlParam = args[0];
    if (cache.includes(urlParam)) {
      return `${urlParam} - Response from cache`;
    } else {
    cache.push(urlParam);
    return Reflect.apply(target, thisArg, args);
   }
  },
});
						

Attack Proxy pattern

Attack Proxy pattern

Attack Proxy pattern

Data warehouse testing

dwh

Data warehouse

Central repositories of integrated data from one or more disparate sources. They store current and historical data in one single place that are used for creating analytical reports.

Is considered a core component of business intelligence.

Data warehouse testing

Data-centric testing is needed, to check if the data in a warehouse has integrity and is reliable, accurate, and consistent within the organization.

Should cover the complete data pipeline, during extract, transform, and load (ETL) operations, validating data at all intermediate stages.

Data warehouse testing (2)

The testing also covers business intelligence (BI) reports and dashboards that run using the consolidated data as its source.

Although the primary focus here is on the data itself, application components such as ETL tools, reporting engines, or GUI applications need to be included in the testing framework.

Pipes and Filters pattern

Decompose a task that performs complex processing into a series of separate elements that can be reused. This can improve performance, scalability, and reusability by allowing task elements that perform the processing to be deployed and scaled independently.

pf

Facade design pattern

An object that provides a simplified interface to a large system. It makes it easier to use and understand, is more readable, and reduces dependencies on external or other code.

Dependency Inversion Principle suggest that our high-level components should not depend on our low-level ones. Both should depend on abstractions.

Facade pattern (2)

Facade pattern (2)

Facade pattern (2)

Facade pattern (2)

Java example

core/DataWarehouseFacade.java
						
						public class DataWarehouseFacade {
  public static void generateReport(DBTypes dbType, 
                                   ReportTypes reportType){
    Connection con = null;
    switch (dbType){
      case MYSQL: 
        con = MySqlHelper.getMySqlDBConnection();
        MySqlHelper mySqlHelper = new MySqlHelper();
        switch(reportType){
        case HTML:
          mySqlHelper.generateMySqlHTMLReport(tableName, con);
          break;
        case PDF:
          mySqlHelper.generateMySqlPDFReport(tableName, con);
          break;
        }
        break;
      case ORACLE: 
        con = OracleHelper.getOracleDBConnection();
        OracleHelper oracleHelper = new OracleHelper();
        switch(reportType){
          case HTML:
            oracleHelper.generateOracleHTMLReport(tableName, con);
            break;
          case PDF:
            oracleHelper.generateOraclePDFReport(tableName, con);
            break;
        }
        break;
    }								
  }
}		
                        

Immutable Shared Fixture

To avoid Erratic tests, that modify a Shared Fixture, we partition the setup needed by tests into two logical parts.

The first part is the stuff that every test needs to have present, but never modified.

The second part is the objects which any test needs to modify.

Java example

test/SharedFixtureFlightManagementFacadeTest.java
						
protected void setUp() throws Exception { // Shared Fixture
  facade = new FlightMgmtFacadeImpl();
  airport = setupNationalAirportsAndFlights();
}

public void testCancel_proposed() throws Exception {
  BigDecimal mutableFlightId = airport.createFlight("Tokyo", "Osaka");
  facade.cancelFlight(mutableFlightId);
  assertEquals("CANCELLED", facade.findFlightById(mutableFlightId));
}
                        

Multiton design pattern

Helps us to manage a map of named instances as key-value pairs. Ensuring a class only has limited number of instances, and provide a global point of access to them.

Also simplifies retrieval of shared objects (fixtures).

C# example

core/SharedFixtureFlightManagementFacadeTest.java
						
							public sealed class SqlDatabase
{
  static Dictionary<string, SqlDatabase> _instances = 
    new Dictionary<string, SqlDatabase>();
  static object _lock = new object();
  private SqlDatabase() { }

  public static SqlDatabase GetMultiton(string sqlInstanceId)
  {
    lock (_lock)
    {
      if (!_instances.ContainsKey(sqlInstanceId)) _instances.Add(sqlInstanceId, new SqlDatabase());
    }
    return _instances[sqlInstanceId];
  }
}
                        

Conclusion

Understanding of how technology stack works is a must!

Acknowledge challenges and build the most fit for purpose solutions.

Use design patterns and concepts to allow evolution of scalable, maintainable and extendible testware.

THANK YOU!

Contact me at:

/ekostadinov evgenikostadinov /in/ekostadinov