Subscribe RSS

Chris Chartier

Software Test Engineering and Management
  • Home
  • Competencies
  • Experience
  • Contact
    • Email Me
    • Contact Links
  • Test Links
Home » Software Testing, Tips, Tools » Abstract Factory Pattern For Reusable Testing

Abstract Factory Pattern For Reusable Testing

2011/05/12 Posted by CCAdmin under Software Testing, Tips, Tools
No Comments
LinkedInShare
Print Friendly

About ten years ago, when I was still a relative rookie software engineer I got moved into the Software Reuse group (SRG) within our software department. It was cutting edge…at least most people considered it cutting edge at the time. When I say Software Reuse group, I use the term “group” very loosely. The “group” was me and a guy named Dave. I took the move as new challenge though, and I embraced it as a way to make a positive difference.

 

As it turns out, this was a great move for me. First, and foremost, working with Dave was a pleasure. I learned more things from Dave than I can count. He was quite experienced and taught me about Reusable Software concepts, Software Design Patterns (which we used plentifully), UML and gave me my first taste in Object-oriented programming. I owe Dave a lot for what he taught me, and he was a great mentor.

 

Our task was to go around the various software programs that were going on within the software department and try to find common component needs. We would talk to each of the groups and learn about what components they needed in their software and then look for overlap between programs. For example, if we found three or four programs that were all going to develop a logging application, we would work with those groups to find out what their specific logging requirements were, and then we would design a reusable logger that met all the requirements of the programs so they could all use the reusable logger. Yes, this was “design for reuse”, not “ad-hoc reuse”.

 

So one of the first assignments I got was to write a reusable serial communications driver. There were programs that each had different communication devices. Also note, that most were on embedded platforms, so not only were they different serial communication devices, but each was on a different operating system (or no operating system, where my driver would be compiled in with the embedded software).

 

As you can see, this was quite a challenging task to be given. I wasn’t sure where to start, but luckily Dave already had a plan. As he was teaching me UML, he explained to me the Abstract Factory design pattern1. The basic idea is very similar to that of an Interface. Essentially the abstract factory defines interface classes for the desired functionality. The interface classes are then initialized with a “concrete factory”, that is, they are initialized with the specific class needed upon instantiation.

 

Abstract Factory Design Pattern

Abstract Factory Design Pattern

 

So in our case (I’ll recreate a simplified mock-up example for this post), we started with the concept of a “Abstract Communication Factory”. This Communication Factory has four interfaces: Read, Write, Flush and Status. As mentioned earlier, each of these interfaces have multiple “concrete factories” for each platform that is supported. So in this example, we’ll assume three devices. So each interface has three concrete functions mapped to each interface. The Read interface has a “Dev1_Read”, “Dev2_Read”, “Dev3_Read”, and so forth.

 

Side Note: If you are wondering how we got past the “cross-platform” problem, we coded all of this in standard ANSI C language using function pointers for assigning concrete functions to the abstract interfaces. ANSI C was by far the most common supported language for any and all platforms we used, so we were sure to have compiler support on all platforms. By keeping it ANSI C, we wouldn’t run into quirks that may have been unique to one compiler but not supported on another.

 

Abstract Communication Factory

Abstract Communication Factory

 

As you can see, this was a pretty big task, especially for a rookie software engineer. With the number of devices and interface support needed, this took a few months to get through. Now I had not learned much about software testing at this point in my career.  I had done some minor unit testing and ad-hoc scenario type testing along the way, but it was not thoroughly tested by any stretch of the term. I figured we’d test it when we integrated with the programs that were going to use them. Dave, says, “Ooooh, no.  We have to test this thoroughly ourselves before we give it to the programs. We need to test each interface, each concrete function, on each platform, etc…” My jaw drops to the floor when I realize I’m only half done. I’d just spent a few months on this, and I would have to write as much code to test it. Ugh….now how do I start that task.

 

As I start giving it some thought….and start looking at the big picture (the UML class diagram of the abstract factory pattern) a light bulb goes off in my head. I have a sudden realization that the abstract factory pattern (that was so perfect for reusable software because we could make concrete factories for each platform) was also the perfect architecture for the test code. What do I have to test? Four interfaces, with each interface able to go down to a different concrete function. It’s almost exactly the same architecture as the code itself.

 

The test architecture starts with a “Communication Test Factory”, instead of a Communication Factory. Instead of the Read, Write, Flush, Status interfaces, we have the Test_Read, Test_Write, Test_Flush, Test_Status interfaces. Similarly, each of the interfaces has a concrete function to back it up for each device. The only slight difference is that I show multiple unit tests defined for each device specific concrete test function. So essentially, we can use an abstract factory to test an abstract factory.

 

Abstract Test Communication Factory

Abstract Test Communication Factory

 

Maybe a little pseudo code will help understanding of how this might work in software.

 

First the Abstract Communication Factory pseudo code.

 


// Abstract Factory Definition

START AbstractCommunicationFactory()

INTERFACE Read(params)

INTERFACE Write(params)

INTERFACE Flush(params)

INTERFACE Status(params)

END AbstractCommunicationFactory()

// Concrete Functions

Dev1_Read(params)

{

// Code for Device 1 Read

}

Dev1_Write(params)

{

// Code for Device 1 Write

}

Dev1_Flush(params)

{

// Code for Device 1 Flush

}

Dev1_Status(params)

{

// Code for Device 1 Status

}

….Same for Dev2 and Dev3 concrete functions

START MAIN PROGRAM()

// Assign Concrete functions to interfaces based on startup condition

IF DEFINED DEV1

INTERFACE Read(params) = *Dev1_Read(params)

INTERFACE Write(params) = *Dev1_Write(params)

INTERFACE Flush(params) = *Dev1_Flush(params)

INTERFACE Status(params) = *Dev1_Status(params)

ELSE IF DEFINED DEV2

INTERFACE Read(params) = *Dev2_Read(params)

INTERFACE Write(params) = *Dev2_Write(params)

INTERFACE Flush(params) = *Dev2_Flush(params)

INTERFACE Status(params) = *Dev2_Status(params)

ELSE

INTERFACE Read(params) = *Dev3_Read(params)

INTERFACE Write(params) = *Dev3_Write(params)

INTERFACE Flush(params) = *Dev3_Flush(params)

INTERFACE Status(params) = *Dev3_Status(params)

ENDIF

END MAIN PROGRAM()

 

See….it’s not as complicated as it sounded. First you define the abstract factory interfaces. Then you must write the code for the concrete factory functions to implement the interfaces for each concrete factory you wish to implement. Lastly, within your main program you assign the interfaces to the concrete functions you desire based on startup conditions.

 

Then the Abstract Test Communication Factory pseudo code is very similar.

 


// Abstract Test Factory Definition

START AbstractTestCommunicationFactory()

INTERFACE Test_Read(params)

INTERFACE Test_Write(params)

INTERFACE Test_Flush(params)

INTERFACE Test_Status(params)

END AbstractTestCommunicationFactory()

// Concrete Test Functions

Test_Dev1_Read(params)

{

Run Dev1_Read_UT1()

Run Dev1_Read_UT2()

}

Test_Dev1_Write(params)

{

Run Dev1_Write_UT1()

Run Dev1_Write_UT2()

}

Test_Dev1_Flush(params)

{

Run Dev1_Flush_UT1()

Run Dev1_Flush_UT2()

}

Test_Dev1_Status(params)

{

Run Dev1_Status_UT1()

Run Dev1_Status_UT2()

}

….Same for Dev2 and Dev3 concrete functions

START MAIN TEST PROGRAM()

// Assign Concrete test functions to interfaces based on startup condition

IF DEFINED DEV1

INTERFACE Test_Read(params) = *Test_Dev1_Read(params)

INTERFACE Test_Write(params) = * Test_Dev1_Write(params)

INTERFACE Test_Flush(params) = * Test_Dev1_Flush(params)

INTERFACE Test_Status(params) = * Test_Dev1_Status(params)

ELSE IF DEFINED DEV2

INTERFACE Test_Read(params) = * Test_Dev2_Read(params)

INTERFACE Test_Write(params) = * Test_Dev2_Write(params)

INTERFACE Test_Flush(params) = * Test_Dev2_Flush(params)

INTERFACE Test_Status(params) = * Test_Dev2_Status(params)

ELSE

INTERFACE Test_Read(params) = * Test_Dev3_Read(params)

INTERFACE Test_Write(params) = * Test_Dev3_Write(params)

INTERFACE Test_Flush(params) = * Test_Dev3_Flush(params)

INTERFACE Test_Status(params) = * Test_Dev3_Status(params)

ENDIF

Run Test_Read(params) // This has test code for interface, AND it runs the concrete function

Run Test_Write(params) // This has test code for interface, AND it runs the concrete function

Run Test_Flush(params) // This has test code for interface, AND it runs the concrete function

Run Test_Status(params) // This has test code for interface, AND it runs the concrete function

END MAIN TEST PROGRAM()

 

This pseudo code for the abstract test communication factory is almost the same as the previous abstract communication factory pseudo code. First you define the abstract test factory with its test interfaces. Then you define the concrete test functions. Again, when you start the main test program you assign the concrete test functions to the test interfaces based on startup conditions.

 

This time, the main program shows that you run the test interfaces in the main test program. Running these interface tests will call the appropriate concrete test function. Running the concrete test function will also run the unit tests because they are called within the concrete test function.

 

Conclusion

Reusable software is a fantastic way to improve software development cycle time because a component used by many different software programs can be developed only one time. There is an overhead when developing robustly and designing “for reuse”, but the upstream overhead is heavily made up for on downstream savings. The abstract factory pattern is a design pattern that is very helpful when designing reusable software in this way.

 

Once software is developed, it must be tested. Software that is designed using the abstract factory design pattern can also be tested with test code that is also architected as an abstract factory. This allows us to have reusable testing for our reusable components. We “double” our savings by not only getting them on the development side, but also getting the same type of savings on the test side.

 

 

1 Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helm, Johnson, Vlissides)

 

LinkedInShare
Tags: cross platform, design pattern, pattern, pseudo code, reuse, software reuse, software testing, UML

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

*

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

« Be Realistic – The Impossibility Of Complete Testing
BBST Foundations Recap »
Search
My Resume
Filter By Tag
activity diagram AST background BBST behavior model big picture bio blog blogs brainstorming CAST chris chartier class computer science conference cross platform design pattern education experience first history kanban lean learning links MBT me mind map model Model-Based Testing networking notes pattern pseudo code requirements reuse sequence diagram software reuse software testing testing tools UML use case use cases use case scenario
Copyright © 2011 | Chris Chartier
All rights reserved.


@chrismchartier on Twitter


Recent Comments
  • Phil Kirkham on Life: The Next Chapter
  • CCAdmin on Life: The Next Chapter
  • Phil Kirkham on Life: The Next Chapter
  • Joe Strazzere on Life: The Next Chapter
  • CCAdmin on Life: The Next Chapter
Archives
free counters
Free counters
Chris Chartier powered by WordPress and The Clear Line Theme