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.
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.
recipe62
as well as a user with permission to access it.In these steps, we will see how to build tests that are aimed at different servers.
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)
recipe62_test.py
to store our test code.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"])
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 + ";")
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.
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);
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 + ";")
recipe62_dev.py
.from recipe62_test import Sqlite3EventCorrelatorTests if __name__ == "__main__": import unittest unittest.main()
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.
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.
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:
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.
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.
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.
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.
18.188.211.106