Visit hokiecsgrad's column >>

HOKIECSGRAD

Add To Watchlist
Articles Posted: 12; Links Seeded: 65
Member Since: 2/2006Last Seen: 11/07/2009

Refactoring for Unit Testing

Code listing #1

Code listing #2

Code listing #3

Code listing #4

advertisement

One of the biggest complaints that I hear from developers making their first foray into unit testing is that a lot of their code is untestable. Upon reviewing their code, I find that they're generally correct, but not because the code is particularly complicated. More often than not, the problem lies in the design of the application. This article details a quick exercise that I like to walk these developers through in order to get them thinking about designing their software with unit testing in mind.

Please keep in mind that the code I will present throughout this article has many problems such as the lack of proper error handling, variables used and sent to various systems without proper data scrubbing, and so forth. What I really want you to concentrate on is the overall design of the system and how it applies to unit testing.

We begin with a very simple class that we need to unit test. SEE CODE LISTING #1. As we see, this is a very simple class that stores, and allows access to, preferences for a particular user in our system. This class is extremely representative of the type of design I see in web-based applications written by young developers. On the surface, this appears to be a fine design and it certainly works inside the application without any problems. It exhibits qualities of OOP that we like to see, encapsulation, separation of concerns, high cohesion, etc.

We're off to a good start, but now we need to write a few unit tests, and almost immediately we run into problems. In order to write even the most basic of unit tests for the Preferences class, we need to setup a database and create a table (prefs) with some demo data in there. First of all, that's a lot of work for some simple unit tests, and we end up testing things that unit tests don't need to test, namely the database, the connection to the database, and the library we use to access the database. Let me be clear, these things will eventually need to be tested, but these tests usually come much later in the form of functionality testing, aka application testing, aka integration testing.

So how can we go about refactoring our design to make unit testing easier? What if we remove the MySQL specific code that handles the actual communication with the database into a separate object and refactor the Preferences class to do away with the specifics of the persistence mechanism? SEE CODE LISTING #2. We're on our way to being able to unit test Preferences, but we're not there yet. Even though we've moved the persistence code into a separate class, we still have to have the database setup in order to unit test the code.

In order to have a truly meaningful unit test, we need to be able to control the inputs and outputs. By moving the code specific to our persistence mechanism to another class, we can easily create another class which will allow us to mock up the data persistence mechanism. SEE CODE LISTING #3. With a small update to the preferences class, namely we set the $dao variable to be an instance ofPrefsDAOMock instead of PrefsDAOMysql, we can now run our program without needing to setup a database first. We are now able to write unit tests that are specific to the Preferences class.

However, looking at the code shows us something interesting. Both of the PrefsDAO classes have the same set of methods, even though their implementation is different. This is a clue that we can create a higher level class (an interface or abstract class) to guide how a concrete class should be implemented. By making this change, we'll introduce polymorphism into our application which allows us to program the application to a single standard without knowing or caring what the actual implementation looks like. SEE LISTING #4. Notice that we've also moved the DAO creation code outside of the Preferences class. By doing so, we can now allow the application itself (or the testing code) to choose which persistence mechanism to use. Note here that we could take this one step further by using a Factory Pattern and allow the DAO creation to happen at runtime instead of compile time as it is now, but that's a matter for another article.

We've managed to pull all the persistence code outside of the Preferences class, and we can now write our unit tests to test the Preferences class in an extremely controlled manner. This is exactly what we want to do, but we also realize another benefit from this refactor. If we ever change our persistence mechanism, all we have to do write a new concrete class which implements the DAO interface and swap out the DAO object creation code. A real world example of why this might happen is if we moved our data access from directly accessing our SQL database to requiring the access to go through a SOAP- or REST-based web service.

  • 9 Votes
  • Enjoy this article? Help vote it up the 'Vine.

Back To Top

Published to:

What's this?
Who's leading the conversation?
This visualization below allows you to see the impact that each user has on the current conversation. The top row contains the group of users who have had the most impact, the 2nd row the group of users who have had the 2nd most impact (et cetera). Users with similar impact are grouped together, and the average score of the group is shown to the left of the group. The author of the article is also shown on the left, in their corresponding group. Each user's score is based on the number of comments the user has made plus the number of votes their comments have received. The scores are calculated relative one another, so while their absolute value is not particularly important, their relative difference does indicate a larger difference in impact on the conversation.
7.7
1.0
{"commentId":2798826,"authorDomain":"hokiecsgrad"}

Yeah, so, I have no idea how to do code listings here on the vine. I resorted to taking screen shots of my code and posting them, but it looks like each one got resized and is mostly illegible. I've posted the code on my website if you want to download it and play around with it.

Download Code Listings

{"commentId":2798826,"threadId":"351152","contentId":"1835848","authorDomain":"hokiecsgrad"}
  • 1 vote
Reply#1 - Sat Sep 6, 2008 6:09 PM EDT
{"commentId":2802521,"authorDomain":"behindmyscreen"}

you can use the code tag in articles and comments. I used that a lot in my article a few years back that discussed setting up postfix to use gmail as a smarthost.

Very nice article BTW. As the founder of the "How-To" group here on the vine, I would recommend you add the "how-to" tag to your article so it can come up in searches. I am clipping it to the How-To group as well.

Feel free to join the group. I hope you write a regular piece on development best practices. My CS degree was focused a lot on the theoretical and not much on the engineering so I did not get a lot of this information. The first thing I thought of is "factor out the functionality" (I am a Math major as well). It is good to see that my instincts are right.

Thanks again for the great article.

{"commentId":2802521,"threadId":"351152","contentId":"1835848","authorDomain":"behindmyscreen"}
  • 1 vote
#1.1 - Sun Sep 7, 2008 12:18 AM EDT
{"commentId":2804195,"authorDomain":"hokiecsgrad"}

Thanks for the kind words. Most colleges tend to focus their CS degrees on the theoretical, at least judging by the interviews that I do. Not many kids graduate with an understanding or appreciation of good, object oriented design, even at the Masters level, which really worries me. It doesn't mean that they're not smart or talented, just that they still need some work.

I've recently moved into a position managing a group of very young developers and have started working on a series of exercises that we run through every Friday to help bring them up to speed on some advanced concepts. My goal is to write articles about the really helpful ones in order to share what we learn with the rest of the company.

I tried the code tag, but I actually lost a lot of formatting when I used it. All my tabs/indentation were wiped out and there was no code coloring at all.

{"commentId":2804195,"threadId":"351152","contentId":"1835848","authorDomain":"hokiecsgrad"}
  • 2 votes
#1.2 - Sun Sep 7, 2008 6:32 AM EDT
{"commentId":2805097,"authorDomain":"hemphill"}

I have also been disappointed with the quality of CS graduates. I don't think that they need a firm grasp of OO programming, since that debate is far from over. I do think they should be able to flowchart, code in a language(at least), understand basics of database structures and normalization, and have some basic notion of how to test software. The graduates I see seem to be of the 'learn java in 30 days' cloth, which is disappointing.

{"commentId":2805097,"threadId":"351152","contentId":"1835848","authorDomain":"hemphill"}
  • 1 vote
#1.3 - Sun Sep 7, 2008 10:00 AM EDT
{"commentId":2805270,"authorDomain":"behindmyscreen"}

hokiscsgrad,

I am glad to see that you company is one of the few that is willing to teach and train Developers. Very few companies want to spend the resources on developing talent anymore.

{"commentId":2805270,"threadId":"351152","contentId":"1835848","authorDomain":"behindmyscreen"}
  • 1 vote
#1.4 - Sun Sep 7, 2008 10:25 AM EDT
{"commentId":2805323,"authorDomain":"behindmyscreen"}

hemphill,

that does not even sound like a theoretical curriculum that you are seeing in those kids. That is scary.

One thing I think most CS courses need to do is have at least 6 hours of course work about software engineering so at least they are familiar with the ideas of code testing, getting the right information out of the customer, flow charting, and working on a development team.

My friend went to University of Michigan Dearborn and they don't have a properly named CS coursework. It is software engineering. The entire 36 credit hours is meant to teach you how to work as a developer. they require database usage (though no necessarily SQL knowledge) to teach how to connect to a database, they have at least 3 projects that you work in a development team, the last of which you actually develop something for a real customer. Unit Testing, flow charting, reporting, documentation, etc are part of every class. It is really very good.

that school is known for engineering and it seems that they have developed a CS course work to be the same as there other Engineering courses.

{"commentId":2805323,"threadId":"351152","contentId":"1835848","authorDomain":"behindmyscreen"}
  • 2 votes
#1.5 - Sun Sep 7, 2008 10:32 AM EDT
{"commentId":2806369,"authorDomain":"hokiecsgrad"}

I don't want to talk too badly about the CS departments. The theoretical is absolutely essential. I need to understand how a b-tree works, I need to be able to look at an algorithm and tell you that its big O is O(n^2), and some knowledge of compiler design can certainly come in handy. The thing that gets me is not that developers don't know certain design patterns, but that they don't even know what a design pattern is. EVEN at the Master's level. That's just criminal.

Luckily, my employer has a very good reputation in the community as a place that developers really want to work. We get a lot of our kids from Virginia Tech (go Hokies!!) and spend less time worrying about whether or not they know these things, but about whether or not they have a desire to know these things. Design patterns can be taught, but the desire to learn, the desire to constantly improve cannot.

{"commentId":2806369,"threadId":"351152","contentId":"1835848","authorDomain":"hokiecsgrad"}
  • 1 vote
#1.6 - Sun Sep 7, 2008 12:31 PM EDT
{"commentId":2807393,"authorDomain":"behindmyscreen"}

I think CS grads all over the US wish every company that did software development behaved like yours.

And I did not want to characterize UM-D as something that totally forgoes theory. They certainly cover language theory, data structures, algorithm analysis, etc. but they also put a major focus on the skills needed to be an effected software developer.

{"commentId":2807393,"threadId":"351152","contentId":"1835848","authorDomain":"behindmyscreen"}
  • 2 votes
#1.7 - Sun Sep 7, 2008 2:17 PM EDT
{"commentId":2817449,"authorDomain":"sgreenway"}

Hokies... pfft... Go Highlanders!! Haha.. j/k hokiecsgrad.

I'm a Radford U. IT grad, so I've gotta give you the obligatory grief from across the river.

{"commentId":2817449,"threadId":"351152","contentId":"1835848","authorDomain":"sgreenway"}
    #1.8 - Mon Sep 8, 2008 11:37 AM EDT
    {"commentId":2817642,"authorDomain":"rascal2pt0"}
    I don't think that they need a firm grasp of OO programming, since that debate is far from over.

    While the debate is far from over I think it should be addressed in design patters and SE. My biggest issue with my college education was that there was a focus on objects but never anything that told you how to get data into and out of objects without tying the object to the data source. Those objects were great for a run/read once write never but otherwise worthless.

    Even if the debate is far from over, its the direction the industry is taking and the only real disadvantage to going objects vs. procedural is performance. As computers get faster and have more memory that performance barrier becomes negligible. But it doesn't mean we should give into writing bad code.

    {"commentId":2817642,"threadId":"351152","contentId":"1835848","authorDomain":"rascal2pt0"}
    • 1 vote
    #1.9 - Mon Sep 8, 2008 11:49 AM EDT
    {"commentId":2817773,"authorDomain":"behindmyscreen"}

    OO is bad code?

    I think OO lends itself to engineering large projects more than procedural.

    {"commentId":2817773,"threadId":"351152","contentId":"1835848","authorDomain":"behindmyscreen"}
    • 1 vote
    #1.10 - Mon Sep 8, 2008 11:59 AM EDT
    {"commentId":2817960,"authorDomain":"hokiecsgrad"}

    BAjunkie: Thanks for chiming in. I'd like to add that I have a high degree of respect for Radford's IT program. Some of my best employees have come from there.

    {"commentId":2817960,"threadId":"351152","contentId":"1835848","authorDomain":"hokiecsgrad"}
      #1.11 - Mon Sep 8, 2008 12:14 PM EDT
      Reply
      {"commentId":2805637,"authorDomain":"blai"}

      I't ve got some serious problems with this approach. Where's your actual unit test and framework?

      {"commentId":2805637,"threadId":"351152","contentId":"1835848","authorDomain":"blai"}
        Reply#2 - Sun Sep 7, 2008 11:09 AM EDT
        {"commentId":2806274,"authorDomain":"hokiecsgrad"}

        First of all, other than the fact that I have no actual unit tests, what is you objection with the refactoring? The whole point of doing these exercises is to learn, and I'm not about to tell you that I know everything there is to know. If something is genuinely wrong here, speak up.

        The point of the article was to show you how to refactor your code to allow for unit testing, not necessarily how to teach you to write unit tests. But if you feel more comfortable with some unit tests, I can provide you with a few.

        Please note, I can't figure out how to use the code tag. =)

        class PrefTest extends PHPUnit_Framework_TestCase {

        private $userPrefs;

        public function setUp() {

        $mockDao = new PrefsDAOMock();

        $this->userPrefs = new Preferences(1, $mockDao);

        }

        public function testGetNonexistentPref() {

        // Assert that the object returns an empty string

        $this->assertEquals('', $this->userPrefs->getPref('I do not exist'));

        }

        public function testGetValidPref() {

        // Assert that the object returns the proper value for the preference

        $this->assertEquals('never', $this->userPrefs->getPref('remember me'));

        }

        public function testSetPref() {

        // Assert that the object returns the proper value after setting it

        $this->setPref('something new', 'present');

        $this->assertEquals('present', $this->userPrefs->getPref('something new'));

        }

        }

        I could continue writing tests to ensure data is properly scrubbed and that invalid inputs are handled properly, but again, for the sake of simplicity I put no error handling code in my examples.

        {"commentId":2806274,"threadId":"351152","contentId":"1835848","authorDomain":"hokiecsgrad"}
          #2.1 - Sun Sep 7, 2008 12:19 PM EDT
          {"commentId":2817581,"authorDomain":"rascal2pt0"}
          I't ve got some serious problems with this approach

          Thats not very descriptive. What are your serious problems. That unit testing has proven to provide higher quality (less bugs and refactoring) code that not unit testing? I think this example is great!

          {"commentId":2817581,"threadId":"351152","contentId":"1835848","authorDomain":"rascal2pt0"}
          • 2 votes
          #2.2 - Mon Sep 8, 2008 11:46 AM EDT
          Reply
          {"canLink":false,"threadId":"351152","isPrivate":false}
          Leave a Comment:
          You're in Easy Mode. If you prefer, you can use XHTML Mode instead.
          As a new user, you may notice a few temporary content restrictions. Click here for more info.
          {"threadId":"351152","contentId":"1835848"}
          Start TrackingStart Tracking
          Stop TrackingStop Tracking