Design Patterns in Python
Design Patterns in Python
Under the following conditions: Attribution You must include a link to www.testingperspective.com along with mentioning the author names Rahul Verma and Chetan Girdhar. Noncommercial You may not use this work for commercial purposes. No Derivative Works You may not alter, transform, or build upon this work. You can choose to collaborate with the authors for the next version of this work.
Cover Image
Renjith Krishnan /FreeDigitalPhotos.net http://www.freedigitalphotos.net/images/view_photog.php?photogid=721
www.testingperspective.com
Chetan Giridhar
Chetan Giridhar has 5 years experience of working in software services industry, service, product companies and research organization. He runs the website TechnoBeans technobeans.wordpress.com where he shares his knowledge w.r.t. day-to-day usage of programming for testers. Chetan has a strong background in C++, Java, Perl and Python. He has developed tools and libraries for users and community developers that could be easily downloaded from Chetan @ sourceforge. He has written on wide variety of subjects in the field of security, code reviews and agile methodologies for testing magazines including TestingExperience and AgileRecord. He has given lectures on Python Programming at Indian Institute of Astrophysics. You can reach him at cjgiridhar@gmail.com.
www.testingperspective.com
Table of Contents
COPYRIGHT INFORMATION.......................................................................................................................... 2 ABOUT THE AUTHORS .................................................................................................................................. 3 FOREWARD ................................................................................................................................................... 6 PREFACE ........................................................................................................................................................ 7 Why Write This Book ......................................................................................................................... 7 What is a design pattern? ............................................................................................................... 7 Context of Design Patterns in Python ........................................................................................ 8 Design Pattern Classifications ....................................................................................................... 8 Who This Book is For ........................................................................................................................ 8 What this book covers ...................................................................................................................... 8 Pre-Requisites ...................................................................................................................................... 9 Online Version ..................................................................................................................................... 9 Feedback................................................................................................................................................ 9 MODEL-VIEW-CONTROLLER PATTERN....................................................................................................... 10 Controller ......................................................................................................................................... 11 Model ................................................................................................................................................. 11 View ................................................................................................................................................... 11 A Sample Python Implementation ............................................................................................. 12 Example Description ................................................................................................................... 12 Database .......................................................................................................................................... 12 Python Code ................................................................................................................................... 12 Explanation ..................................................................................................................................... 14 COMMAND PATTERN ................................................................................................................................. 15 A Sample Python Implementation ............................................................................................. 16 Example description .................................................................................................................... 16 Python Code ................................................................................................................................... 16
www.testingperspective.com
www.testingperspective.com
Foreword
Vipul Kocher
Shri Ramkrishna Paramhans, one of the greatest mystics of this age and Guru of Swami Vivekananda, used to narrate a story that went like this There was a Ghost who was very lonely. It is said that when a person dies an accidental death on Tuesday or Saturday becomes a Ghost. Whenever that Ghost saw an accidental death he would rush there hoping he would find a friend. However, every time he was disappointed because all of them went to respective places without even one becoming a Ghost. While He said it referring to difficulty of finding people with spiritual bent of mind, I take the liberty of using this story for my own interests. I got drawn to Object Oriented Design world in 1996 despite my job role being that of system tester. I started my career as a developer and that probably was the reason or probably my love for abstract. Whatever be the reason, I got drawn to the world of Grady Booch,Rumbaugh, Shlaer and Mellor, Coad and Yourdon etc. Then Gang of Four happened. I got blown away by the book Design Patterns Elements of Reusable Object-Oriented Software. Since then I kept looking for testers who shared same love as me for design patterns. Alas! None was to be found. That is, until I met Rahul Verma and Chetan Giridhar. I used to wonder when automation framework developers would start talking about the framework as a designer/architect rather than just as a user. Will they ever take automation project as a development project? Will they ever apply design patterns to the automation framework? In the present book I hope to see this dream of mine fulfilled. While the book aims to talk of design patterns in Python, I hope to see the examples and explanations from the point of view of a tester who wants to create a framework utilizing sound architecture and design patterns. Patterns have begun to occupy a large portion of my thought process for almost two years now; the process having begun in 1999 with my first attempt at testing patterns by the name Q-patterns or Questioning-Patterns. I sincerely hope to see one piece of the pattern puzzle falling in place with this book. I hope you enjoy the journey that this book promises to take you through. Vipul Kocher Vipul Kocher
Co-President, PureTesting
www.testingperspective.com
Preface
Testers are poor coders. We here that more often than one would think. There is a prominent shade of truth in this statement as testers mostly code to get the work done, at times using scripting languages which are vendor specific, just tweaking a recorded script. The quality of code written is rarely a concern and most of the times, the time and effort needed to do good coding is not allocated to test automation efforts. The above scenario changes drastically when one needs to develop ones own testing framework, extend an existing one or contribute new code to an existing one. To retain the generic property of the framework and to build scalable frameworks, testers need to develop better coding skills.
www.testingperspective.com
Who This Book is For If you are a beginner to learning Python or design patterns, this book can prove to be a very easy-to-understand introductory text. If you are a tester, in addition to the above this book would also be helpful in learning contexts in which design patterns can be used in the test automation world. What this book covers
www.testingperspective.com
Because the current number of design patterns is a handful, we have not categorized them based on classifications mentioned earlier. By the time we publish next version of this book with a target of 20 patterns, the content would be organized into classification-wise grouping of chapters. Pre-Requisites
Basic Knowledge of Object-Oriented Programming Basic Knowledge of Python (see How Would Pareto Learn Python by Rahul Verma to get started)
Online Version An online version of the book is available on Testing Perspective website at the following link: http://dpip.testingperspective.com/ This version is a more updated version of this ebook at any given point of time in terms of corrections and updated content. We plan to release updated version of this offline PDF version at regular intervals to keep up with the updated content in online version. Feedback There would be mistakes. Some of them would be directly visible and some of them could be more subtle. Please write back to us so that we can correct the same. You can use the contact form at Testing Perspective website to do so or write to us at feedback@testingperspective.com.
www.testingperspective.com
DP#
Controller associates the user input to a Model and a View Model fetches the data to be presented from persistent storage
10
www.testingperspective.com
View deals with how the fetched data is presented to the user
Lets talk about all these components in detail: Controller Controller can be considered as a middle man between user and processing (Model) & formatting (View) logic. It is an entry point for all the user requests or inputs to the application. The controller accepts the user inputs, parses them and decides which type of Model and View should be invoked. Accordingly, it invokes the chosen Model and then the chosen View to provide to the user what he/she/it requested. Model Model represents the business processing logic of the application. This component would be an encapsulated version of the application logic. Model is also responsible for working with the databases and performing operations like Insertion, Update and Deletion. Every model is meant to provide a certain kind of data to the controller, when invoked. Further, a single model can return different variants of the same kind of data based on which method of the Model gets called. What exactly gets returned to the controller, could be controlled by passing arguments to a given method of the model. View View, also referred as presentation layer, is responsible for displaying the results obtained by the controller from the model component in a way that user wants
11
www.testingperspective.com
If the user queries for a particular defect, the test management system displays the summary of the defect to the user in a prescribed format. If the user searches for a component it shows list of all defects logged against that component.
Database Lets consider a SQLite DB with the name TMS and a table defects. TMS.[defects] ID Component Summary 1 2 3 XYZ XYZ ABC File doesnt get deleted Registry doesnt get created Wrong title gets displayed
12
www.testingperspective.com
class DefectView: def summary(self, summary, defectid): print "#### Defect Summary for defect# %d####\n%s" % (defectid,summary) def defectList(self, list, category): print "#### Defect List for %s ####\n" % category for defect in list: print defect class Controller: def __init__(self): pass def getDefectSummary(self, defectid): model = DefectModel() view = DefectView() summary_data = model.getSummary(defectid) return view.summary(summary_data, defectid) def getDefectList(self, component): model = DefectModel() view = DefectView() defectlist_data = model.getDefectList(component) return view.defectList(defectlist_data, component) Python Code for User import mvc controller = mvc.Controller() # Displaying Summary for defect id # 2
13
www.testingperspective.com
Explanation 1. Controller would first get the query from the user. It would know that the query is for viewing defects. Accordingly it would choose DefectModel. 2. If the query is for a particular defect, Controller calls getSummary() method of DefectModel, passing the defect id as the argument, for returning the summary of the defect. Then the Controller calls summary() method of DefectView and displays the response to the user. 3. If the user query consists of a component name, , Controller calls getDefectList() method of DefectModel, passing the component name as the argument, for returning the defect list for the given component. Then the Controller calls defectList() method of DefectView and displays the response to the user.
14
www.testingperspective.com
DP#
Command Pattern
Introduction
As per Wikipedia: In object-oriented programming, the command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
Command pattern is one of the design patterns that are categorized under Observer design pattern. In command pattern the object is encapsulated in the form of a command, i.e., the object contains all the information that is useful to invoke a method anytime user needs. To give an example, let say we have a user interface where in the background of the interface would turn into RED if the button on the user interface named RED is clicked. Now the user is unaware what classes or methods the interface would call to make the background turn RED, but the command user sends (by clicking on RED button) would ensure the background is turned RED. Thus command pattern gives the client (or the user) to use the interface without the information of the actual actions being performed, without affecting the client program. The key to implementing this pattern is that the Invoker object should be kept away from specifics of what exactly happens when its methods are executed. This way, the same Invoker object can be used to send commands to objects with similar interfaces.
15
www.testingperspective.com
Client: the Client represents the one that instantiates the encapsulated object. Invoker: the invoker is responsible for deciding when the method is to be invoked or called. Receiver: the receiver is that part of the code that contains the instructions to execute when a corresponding command is given.
A Sample Python Implementation For demonstrating this pattern, we are going to do a python implementation of an example present on Wikipedia in C++/Java/C#. Example description
It's the implementation of a switch It could be used to switch on/off It should't be hard-coded to switch on/off a particular thing (a lamp or an engine) Python Code
class Switch: """ The INVOKER class""" def __init__(self, flipUpCmd, flipDownCmd): self.__flipUpCommand = flipUpCmd
16
www.testingperspective.com
17
www.testingperspective.com
def __init__(self): self.__lamp = Light() self.__switchUp = FlipUpCommand(self.__lamp) self.__switchDown = FlipDownCommand(self.__lamp) self.__switch = Switch(self.__switchUp,self.__switchDown) def switch(self,cmd): cmd = cmd.strip().upper() try: if cmd == "ON": self.__switch.flipUp() elif cmd == "OFF": self.__switch.flipDown() else: print "Argument \"ON\" or \"OFF\" is required." except Exception, msg: print "Exception occured: %s" % msg # Execute if this file is run as a script and not imported as a module if __name__ == "__main__": lightSwitch = LightSwitch() print "Switch ON test." lightSwitch.switch("ON") print "Switch OFF test" lightSwitch.switch("OFF") print "Invalid Command test" lightSwitch.switch("****")
18
www.testingperspective.com
DP#
Observer Pattern
Introduction As per Wikipedia: The observer pattern (a subset of the publish/subscribe pattern) is a software design pattern in which an object, called the subject, maintains a list of its dependants, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems.
Typically in the Observer Pattern, we would have: 1. Publisher class that would contain methods for:
Registering other objects which would like to receive notifications Notifying any changes that occur in the main object to the registered objects (via registered object's method) Unregistering objects that do not want to receive any further notifications A method that is used by the Publisher Class, to notify the objects registered with it, of any change that occurs.
3. An event that triggers a state change that leads the Publisher to call its notification method
19
www.testingperspective.com
20
www.testingperspective.com
21
www.testingperspective.com
22
www.testingperspective.com
DP#
Facade Pattern
Introduction facade(n): the face or front of a building As per Wikipedia: The facade pattern is a software engineering design pattern commonly used with Object-oriented programming. (The name is by analogy to an architectural facade.). A facade is an object that provides a simplified interface to a larger body of code, such as a class library. Faade pattern falls under the hood of Structural Design Patterns. Faade is nothing but an interface that hides the inside details and complexities of a system and provides a simplified front end to the client. With faade pattern, client can work with the interface easily and get the job done without being worried of the complex operations being done by the system. An important point to understand about the Facade pattern is that it provides a simplified interface to a part of the system, thereby providing ease-of-use for a sub-set of the functionality rather than complete functionality. Beauty of this is that the underlying classes are still available to the client if the client wants additional features/greater control and customization that are not provided in the current context of Facade pattern implementation. Because of the above reason, Facade pattern is not about encapsulating the subsystem, rather about providing a simplified interface for a chosen functionality
23
www.testingperspective.com
1. In this block diagram, we have three classes representing the CPU, the Memory and the HardDrive of a computer. CPU Class has methods called as jump() and execute(). Memory Class has a method load() and HardDrive Class has read() method. 2. We have a Facade, the Class Computer, that is exposed to the Client with the help of the start() method. 3. When the Client intends to start the Computer System, it calls the start() method of Class Computer by calling Computer.start(). Let's see what actually happens behind the scenes! In start(), CPU, Memory and HardDrive classes are instantiated. Then the load() method of Class Memory is called where it gets the BOOT_ADDRESS and calls the read() method of HardDrive Class from it gets the BOOT_SECTOR and SECTOR_SIZE of the HardDrive. start() then calls the jump() method of CPU Class with the BOOT_ADDRESS and then calls the execute() method. Thus the client is not aware of the complex operations happening in the background when the computer is started. It only has a facade exposed to it from where it could start the computer easily without bothering about the inner details or complexities.
24
www.testingperspective.com
25
www.testingperspective.com
26
www.testingperspective.com
DP#
Mediator Pattern
Introduction As per Wikipedia: The mediator pattern provides a unified interface to a set of interfaces in a subsystem. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior..
Typically, mediator pattern is used in cases where many classes start communicating amongst each other to produce result. When the software starts getting developed, more user requests get added and in turn more functionality need to be coded. This results in increased interaction with in the existing classes and in addition of new classes to address new functionality. With the increasing complexity of the system, interaction between classes becomes tedious to handle and maintaining the code becomes difficult. Mediator pattern comes in as a solution to this problem by allowing loose-coupling between the classes, also called as Colleagues in the Mediator Design Pattern. The idea is that there would be one Mediator class that is aware of the functionality of all the classes in the system. The classes are aware of their functionality and interact with the Mediator class. Whenever there is a need of interaction between the classes, a class sends information to the Mediator and it is the responsibility of the Mediator to pass this information to the required class. Thus the complexity occurring because of interaction between the classes gets reduced.
27
www.testingperspective.com
28
www.testingperspective.com
29
www.testingperspective.com
30
In the above Python Code, the user of the framework first creates instances of Class Reporter, Class DB and Class TestManager and registers these classes with each other with the help of setReporter(), setDB() and setTM() methods.
When the test category starts execution, the TestManager Class and the TC Class register with each other. TC Class first calls the setup() method which in turn requests the TestManager Class (the Mediator) to be prepared for reporting of the test execution results using the prepareReporting() method.
prepareReporting() method of the TestManager, the Mediator Class, communicates with the other Colleagues (Class Reporter and Class DB) in the system by calling the prepare() and insert() methods.
execute() method of Class TC is responsible for running the tests. When the test execution is getting finished, tearDown() method of Class TC is called, which in turn calls the publishReport() method of the Mediator (Class TestManager) which communicates with the Colleagues (Class Reporter and Class DB) for preparing the report and getting the execution status in the database (by calling the prepare() and update() methods respectively).
Also, during insert(), if communication between Class TC (Colleague) and Class TestManager (Mediator) fails, the next step in test execution is conveyed of this failure.
By this example, it can be understood that the communication between Colleagues in the system (Class TC, Class Reporter and Class DB) can easily be achieved by the Mediator (Class TestManager) and the possibility of the complexity that could arise as a result of communication between Colleagues without the Mediator is avoided. This example also demonstrates the capability of a two way interaction between the Colleague and the Mediator with the help of communication failure case between Class TC and Class TestManager.
31
www.testingperspective.com
DP#
Factory Pattern
Introduction As per Wikipedia: The factory pattern is a creational design pattern used in software development to encapsulate the processes involved in the creation of objects.
Factory pattern involves creating a super class which provides an abstract interface to create objects of a particular type, but instead of taking a decision on which objects get created it defers this creation decision to its subclasses. To support this there is a creation class hierarchy for the objects which the factory class attempts create and return. Factory pattern is used in cases when based on a type got as an input at runtime, the corresponding object has to be created. In such situations, implementing code based on Factory pattern can result in scalable and maintainable code i.e. to add a new type, one need not modify existing classes; it involves just addition of new subclasses that correspond to this new type. In short, use Factory pattern when: A class does not know what kind of object it must create on a users request You want to build an extensible association between this creater class and classes corresponding to objects that it is supposed to create.
32
www.testingperspective.com
33
www.testingperspective.com
34
www.testingperspective.com
DP#
Proxy Pattern
Introduction As per Wikipedia: A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. A well-known example of the proxy pattern is a reference counting pointer object. Proxy Pattern is an example of Structural Design Patterns. It is also referred to as Surrogate pattern as the intention of this pattern is to create a surrogate for the real object/class. Proxy pattern has three essential elements:
Real Subject (that performs the business objectives, represented by Proxy). Proxy Class (that acts as an interface to user requests and shields the real Subject) Client (that makes the requests for getting the job done).
This design pattern is typically used in situations when: 1. Creation of object for a Real Subject Class is costly in terms of resources and a simple object creation by Proxy Class can be a cheaper option. www.testingperspective.com
35
Website where the Cache Proxy can cache certain set of frequently requested web pages to cater to clients requests. This helps in avoiding many requests getting hit on the server and improves performance.
The message box which gives the status of a file copy operation giving the progress in terms of percentage completion. Opening a large file in a word processor leads to a message saying Please wait while the software opens the document
A Sample Python Implementation Example Description Based on the example from Prof. Kiran, let us consider a case of a regular office situation where, in order to speak to a Sales Manager of a company, the Client makes a call first to the receptionist of the sales manager office and then the Receptionist passes the call to the manager. In this case, Sales Manager would be the Subject to whom the Client wants to speak to and the Receptionist would be the Proxy that shields the Subject from talking directly to the Clients. Expanding this example, we could consider 'Sales Manager' as the Real Subject and create a common Subject Class referred to as 'Managers' from which 'Sales Manager' and the 'Receptionist' can be derived. Python Code import time class Manager(object): def work(self): pass def talk(self): pass class SalesManager(Manager): def work(self): print "Sales Manager working..."
36
www.testingperspective.com
37
www.testingperspective.com
38
www.testingperspective.com