Targeting the test server

Does your test server have all the parts? If not, then define an alternative set of tests.

This recipe assumes that the production server is installed with an enterprise grade MySQL database system while the developer's workstation is not. We will explore writing some tests that use the MySQL database. But when we need to run them in the development lab, we will make adjustments so they run on SQLite, which comes bundled with Python.

Tip

Are you wondering why MySQL isn't on the developer's workstation? It is true that MySQL is easy to install and not a huge performance load. But this scenario applies just the same if the production server was Oracle and management deemed it too costly for our developers to be granted an individual license. Due to the cost of setting up a commercial database, this recipe is demonstrated using MySQL and SQLite rather than Oracle and SQLite.

Getting ready

  1. Make sure the MySQL production database server is up and running.
    Getting ready
  2. Open a command line MySQL client shell as the root user.
  3. Create a database for this recipe called recipe62 as well as a user with permission to access it.
  4. Exit the shell. Contrary to what is shown in the following screenshot, never, ever, EVER create a live production database with passwords stored in the clear. This database is for demonstration purposes only.
    Getting ready

How to do it...

In these steps, we will see how to build tests that are aimed at different servers.

  1. Create an alternate version of the SQL script called recipe62_network.mysql used in earlier recipes that use MySQL conventions.
    DROP TABLE IF EXISTS SERVICE_MAPPING;
    DROP TABLE IF EXISTS SERVICE_EVENTS;
    DROP TABLE IF EXISTS ACTIVE_EVENTS;
    DROP TABLE IF EXISTS EQUIPMENT;
    DROP TABLE IF EXISTS SERVICE;
    DROP TABLE IF EXISTS EVENTS;
    
    CREATE TABLE EQUIPMENT (
        ID        SMALLINT PRIMARY KEY AUTO_INCREMENT,
        HOST_NAME TEXT,
        STATUS    SMALLINT
        );
    
    CREATE TABLE SERVICE (
        ID SMALLINT PRIMARY KEY AUTO_INCREMENT,
        NAME TEXT,
        STATUS TEXT
        );
    
    CREATE TABLE SERVICE_MAPPING (
        ID SMALLINT PRIMARY KEY AUTO_INCREMENT,
        SERVICE_FK SMALLINT,
        EQUIPMENT_FK SMALLINT
        );
    
    CREATE TABLE EVENTS (
        ID SMALLINT PRIMARY KEY AUTO_INCREMENT,
        HOST_NAME TEXT,
        SEVERITY SMALLINT,
        EVENT_CONDITION TEXT
        );
    
    CREATE TABLE SERVICE_EVENTS (
        ID SMALLINT PRIMARY KEY AUTO_INCREMENT,
        SERVICE_FK SMALLINT,
        EVENT_FK SMALLINT
        );
    
    CREATE TABLE ACTIVE_EVENTS (
        ID SMALLINT PRIMARY KEY AUTO_INCREMENT,
        EVENT_FK SMALLINT
        );
    
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (1, 'pyhost1', 1);
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (2, 'pyhost2', 1);
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (3, 'pyhost3', 1);
    
    INSERT into SERVICE (ID, NAME, STATUS) values (1, 'service-abc', 'Operational'),
    
    INSERT into SERVICE_MAPPING (SERVICE_FK, EQUIPMENT_FK) values (1,1);
    INSERT into SERVICE_MAPPING (SERVICE_FK, EQUIPMENT_FK) values (1,2)

    Tip

    You might not have noticed, but this schema definition has no foreign key constraints. In a real world SQL script, those should definitely be included. They were left out in this case to reduce complexity.

  2. Create a new module called recipe62_test.py to store our test code.
  3. Create an abstract test case that has one test method verifying event-to-service correlation.
    import logging
    from network import *
    import unittest
    from springpython.database.factory import *
    from springpython.database.core import *
    
    class AbstractEventCorrelatorTests(unittest.TestCase):
        def tearDown(self):
            self.correlator = None
    
        def test_service_failing(self):
            # This alarm maps to a service.
            evt1 = Event("pyhost1", "serverRestart", 5)
    
            stored_event, is_active, 
               updated_services, updated_equipment = 
                         self.correlator.process(evt1)
    
            self.assertEquals(len(updated_services), 1)
            self.assertEquals("service-abc",
                   updated_services[0]["service"]["NAME"])
            self.assertEquals("Outage",
                   updated_services[0]["service"]["STATUS"])
    
            evt2 = Event("pyhost1", "serverRestart", 1)
    
            stored_event, is_active, 
                updated_services, updated_equipment = 
                     self.correlator.process(evt2)
    
            self.assertEquals(len(updated_services), 1)
            self.assertEquals("service-abc",
                   updated_services[0]["service"]["NAME"])
            self.assertEquals("Operational",
                   updated_services[0]["service"]["STATUS"])
  4. Create a concrete subclass that connects to the MySQL database and uses the MySQL script.
    class MySQLEventCorrelatorTests(AbstractEventCorrelatorTests):
        def setUp(self):
            factory = MySQLConnectionFactory("user", "password",
                                         "localhost", "recipe62")
            self.correlator = EventCorrelator(factory)
    
            dt = DatabaseTemplate(factory)
            sql = open("recipe62_network.mysql").read().split(";")
            for statement in sql:
                dt.execute(statement + ";")
  5. Create a corresponding production test runner called recipe62_production.py.
    from recipe62_test import MySQLEventCorrelatorTests
    
    if __name__ == "__main__":
        import unittest
        unittest.main()

    Run it and verify if it connects with the production database.

    How to do it...
  6. Now create a SQLite version of the SQL script called recipe62_network.sql.
    DROP TABLE IF EXISTS SERVICE_MAPPING;
    DROP TABLE IF EXISTS SERVICE_EVENTS;
    DROP TABLE IF EXISTS ACTIVE_EVENTS;
    DROP TABLE IF EXISTS EQUIPMENT;
    DROP TABLE IF EXISTS SERVICE;
    DROP TABLE IF EXISTS EVENTS;
    
    CREATE TABLE EQUIPMENT (
        ID        INTEGER PRIMARY KEY,
        HOST_NAME TEXT    UNIQUE,
        STATUS    INTEGER
        );
    
    CREATE TABLE SERVICE (
        ID INTEGER PRIMARY KEY,
        NAME TEXT UNIQUE,
        STATUS TEXT
        );
    
    CREATE TABLE SERVICE_MAPPING (
        ID INTEGER PRIMARY KEY,
        SERVICE_FK,
        EQUIPMENT_FK,
        FOREIGN KEY(SERVICE_FK) REFERENCES SERVICE(ID),
        FOREIGN KEY(EQUIPMENT_FK) REFERENCES EQUIPMENT(ID)
        );
    
    CREATE TABLE EVENTS (
        ID INTEGER PRIMARY KEY,
        HOST_NAME TEXT,
        SEVERITY INTEGER,
        EVENT_CONDITION TEXT
        );
    
    CREATE TABLE SERVICE_EVENTS (
        ID INTEGER PRIMARY KEY,
        SERVICE_FK,
        EVENT_FK,
        FOREIGN KEY(SERVICE_FK) REFERENCES SERVICE(ID),
        FOREIGN KEY(EVENT_FK) REFERENCES EVENTS(ID)
        );
    
    CREATE TABLE ACTIVE_EVENTS (
        ID INTEGER PRIMARY KEY,
        EVENT_FK,
        FOREIGN KEY(EVENT_FK) REFERENCES EVENTS(ID)
        );
    
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (1, 'pyhost1', 1);
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (2, 'pyhost2', 1);
    INSERT into EQUIPMENT (ID, HOST_NAME, STATUS) values (3, 'pyhost3', 1);
    
    INSERT into SERVICE (ID, NAME, STATUS) values (1, 'service-abc', 'Operational'),
    
    INSERT into SERVICE_MAPPING (SERVICE_FK, EQUIPMENT_FK) values (1,1);
    INSERT into SERVICE_MAPPING (SERVICE_FK, EQUIPMENT_FK) values (1,2);
  7. Create another concrete subclass of the abstract test case, only have it connect as SQLite using the SQLite script and add it to recipe62_test.py.
    class Sqlite3EventCorrelatorTests(AbstractEventCorrelatorTests):
        def setUp(self):
            factory = Sqlite3ConnectionFactory("recipe62.db")
            self.correlator = EventCorrelator(factory)
    
            dt = DatabaseTemplate(factory)
            sql = open("recipe62_network.sql").read().split(";")
            for statement in sql:
                dt.execute(statement + ";")
  8. Create a corresponding development workstation test runner called recipe62_dev.py.
    from recipe62_test import Sqlite3EventCorrelatorTests
    
    if __name__ == "__main__":
        import unittest
        unittest.main()
  9. Run it and verify if it connects with the development database.
    How to do it...

How it works...

It is not uncommon to have a production environment with full-fledged servers and software installed while at the same time having a smaller development environment. Some shops even have a test bed that is somewhere in between these configurations.

Our network application handles this situation by allowing database connection information to get injected into it. In each test case, we used the exact same application, but with different database systems.

We wrote a test case that used the production MySQL database, and we wrote a test case that used the development SQLite database. Of course MySQL, even though used in many production environments, doesn't sound like something which is unavailable to developers. But it provides an easy-to-see example of having to switch database systems.

There's more...

In this recipe, we showed the need to switch database systems. This isn't the only type of external system that may require alternate configurations for test purposes. Other things like LDAP servers, third-party web services, and separate subsystems may have totally different configurations.

Tip

I have worked on several contracts and often seen management cut development lab resources to save costs. They seem to conclude that the cost of maintaining multiple configurations and handling non-reproducible bugs is less than the cost of having the exact same complement of equipment and software. I feel this conclusion is faulty, because at some time in the future, they end up buying more hardware and upgrade things due to increasing issues involving platform variance.

This means we can't always write tests that target the production environment. Writing our software so that it has maximum flexibility, like injecting database configuration as we did earlier, is a minimum.

It's important that we write as many tests as possible that work on the developer's platform. When developers have to start sharing server-side resources, then we run into resource collisions. For example, two developers sharing a single database server will have to do one of these:

  • Have separate schemas so they can empty and load test data
  • Coordinate times when they each have access to the same schema
  • Have different servers set up for each developer

The third option is highly unlikely given that we are talking about a development lab with a smaller footprint than the production one.

A positive note is that developers are getting faster and more powerful machines. Compared to 10 years ago, a commonly seen workstation far exceeds old server machines. But even though we may each be able to run the entire software stack on our machine, it doesn't mean management will pay for all the necessary licensing.

Unfortunately, this limitation may never change. Hence, we have to be ready to write tests for alternate configurations and manage the discrepancies with the production environment.

How likely is it that a dev versus production environment would use two different database systems?

Admittedly, it is unlikely to have something as big as switching between SQLite and MySQL. That alone required slightly different dialects of SQL in order to define the schema. Some would immediately consider this too difficult to manage. But there are smaller differences in environments that can still yield the same need for reduced testing.

Tip

I worked on a system for many years where the production system used Oracle 9i RAC while the development lab just had Oracle 9i. RAC required extra hardware and we were never allocated the resources for it. To top it off, Oracle 9i was too big to install on the relatively lightweight PCs we developed with. While everything spoke Oracle's dialect of SQL, the uptime differences between RAC and non-RAC generated a fair number of bugs that we couldn't reproduce in the dev lab. It really did qualify as two different database systems. Given that we couldn't work in the production environment, we tested as much as we could in the dev lab and then scheduled time in the test lab where an RAC instance existed. Since many people needed access to that lab, we confined our usage to RAC-specific issues to avoid schedule delays.

This isn't just confined to database systems

As stated earlier, this isn't just about database systems. We have discussed MySQL, SQLite, and Oracle, but this also involves any sort of system we work with or depend on that varies between production and development environments.

Being able to code subsets of tests to achieve confidence can help cut down on the actual issues we will inevitably have to deal with.

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

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