<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Danilo Gurovich &#187; Django TDD</title>
	<atom:link href="http://gurovich.com/site/tag/django-tdd/feed/" rel="self" type="application/rss+xml" />
	<link>http://gurovich.com/site</link>
	<description>Strategic eCommerce Technology and Architecture</description>
	<lastBuildDate>Mon, 25 Sep 2023 14:32:51 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Using TDD to create a Python Django SSN validation</title>
		<link>http://gurovich.com/site/2010/03/30/ssn_validation_with_django/</link>
		<comments>http://gurovich.com/site/2010/03/30/ssn_validation_with_django/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 19:44:59 +0000</pubDate>
		<dc:creator>Danilo Gurovich</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[Enterprise Development]]></category>
		<category><![CDATA[django advanced validation]]></category>
		<category><![CDATA[Django SSN Validation]]></category>
		<category><![CDATA[Django TDD]]></category>
		<category><![CDATA[frozensete]]></category>
		<category><![CDATA[Python TDD]]></category>
		<category><![CDATA[SSN Validation]]></category>

		<guid isPermaLink="false">http://gurovich.com/site/?p=1299</guid>
		<description><![CDATA[The Problem to Solve Many web applications collect financial information to submit as leads for loans, credit card applications, bank information or other purposes. To ensure that the submitted SSN is valid, a validation algorithm must be implemented against the submitted value. Many websites in their infancy check only for length and null, but there [...]]]></description>
			<content:encoded><![CDATA[<h2>The Problem to Solve</h2>
<p>Many web applications collect financial information to submit as leads for loans, credit card applications, bank information or other purposes.  To ensure that the submitted SSN is valid, a validation algorithm must be implemented against the submitted value.  Many websites in their infancy check only for length and null, but there is actually a formula that is published by the government to further qualify the SSN in the form field.  By validating this SSN further, the quality of the generated lead is increased, which will have direct impact on the value of the data.</p>
<p>The algorithm we&#8217;ll construct is based on the following formulaic instruction:</p>
<blockquote><p><em>&#8220;A valid SSN is determined by the following rules according to the website </em><a title="SSA validation utilities" href="http://www.ssa.gov/employer/ssnweb.htm" target="_blank"><em>http://www.ssa.gov/employer/ssnweb.htm</em></a></p>
<p><em>Area-Group-Serial (AAA-GG-SSSS) The Social Security number consists of nine (9) digits. The first three (3) digits denote the area (or State) where the application for an original Social Security number was filed.</em></p>
<p><em>Within each area, the group number (middle two (2) digits) range from 01 to 99 but are not assigned in consecutive order. For administrative reasons, group numbers issued first consist of the ODD numbers from 01 through 09 and then EVEN numbers from 10 through 98, within each area number allocated to a State. After all numbers in group 98 of a particular area have been issued, the EVEN Groups 02 through 08 are used, followed by ODD Groups 11 through 99.</em></p>
<p><em>Within each group, the serial numbers (last four (4) digits) run consecutively from 0001 through 9999.&#8221;</em></p></blockquote>
<p>So, for reference, just in case:  The first three digits are the &#8220;Area&#8221; reference.  The second two digits are the &#8220;Group&#8221; reference, and the final four digits are the &#8220;Serial Number&#8221; value.</p>
<p>We&#8217;ll create a validator that checks to ensure that the social security number is the right length (stripped of dashes and white spaces), is all numbers, and finally implements the Social Security Administration&#8217;s formula for the Area/Group validation and their list of invalid SSNs.<span id="more-1299"></span></p>
<h2>What is needed</h2>
<p>The data for Area/Group validation and invalid SSN&#8217;s is quite static. ?For performance reasons, it is not necessary or reasonable to create a database to manage this data when a simple list can be used. ?In this particular implementation, we&#8217;ll <em>hard code</em> these values into a <code>dictionary</code> constant and create a <code>frozenset</code> to make the lookup and validation of the SSN Area even faster.  The list of invalid SSNs will also be a frozenset.</p>
<h3>Why frozenset</h3>
<p>I use a Python <code>set</code> because I&#8217;m just testing &#8220;membership&#8221; in the group.  This gives me optimization.  Further I decided to use <code>frozenset</code> because it&#8217;s API is limited further, not allowing updates, ordering or indexing.  I want to use these values as a constant that is loaded into the VM at run-time and does not depend on any database lookup for performance reasons.</p>
<h3>Project Structure</h3>
<p>The file structure is a typical Django Project with an &#8220;application&#8221; subdirectory.  The application subdirectory is called <code>finvs</code>, short for &#8220;financial validators&#8221;.  By using the <code>manage.py startapp finvs</code>, you&#8217;ll get the basic structure of the <code>finvs</code> automatically, and then you can add the <code>utils</code> subdirectory to abstract your algorithms and validation objects away from the actual application.</p>
<p>Note that a <code>tests.py</code> file has been created automatically.  When running <code>manage.py test {app}</code> the tests that are located in the tests.py of each {app} directory &#8212; in this case <code>finvs</code>, will be run.  Once everything is set up, we will explain how to use the tests to develop the actual implementation used in the <code>validation_formulas.py</code> file without ever having to start the Django server.  This means that you won&#8217;t have to develop a lot of front end code or do anything that you don&#8217;t need to do.  You can develop in an independent manner and work on the code that is a priority.</p>
<pre>project_root
        |-finvs
                |-utils
                        |-__init__.py
                        |-ssn_constants.py
                        |-validation_formulas.py
                |-__init__.py
                |-models.py
                |-tests.py
                |-views.py
        |-__init__.py
        |-manage.py
        |-settings.py
        |-urls.py</pre>
<p>For the purposes of this demonstration, the only files we&#8217;ll be using in this structure are <code>finvs/utils/ssn_constants.py</code> to hold the data in a &#8220;persistence layer&#8221;, <code>finvs/utils/validation_formulas.py</code> to contain the actual implementation method(s) of the validation, and <code>finvs/tests.py</code> to test and develop the code before I actually implement it. </p>
<h2>The &#8220;Persistence Layer&#8221;</h2>
<p>As described above, having all the information in a database requires too much overhead and is a drag on performance of the validation.  Having to suffer through a lookup to a database and the maintenance of this data involves more work and runtime cost than is necessary for what is very static data that rarely changes. </p>
<p>Since there are only a few hundred records to deal with, there is very little memory overhead taken up holding these values as static members.  I therefore created a file in the <code>utils</code> directory that contains the data I will use to validate SSNs against (<code>ssn_constants.py</code>).  The code I inserted is enumerated in its entirety below:</p>
<pre>finvs/utils/ssn_constants.py</pre>
<pre># a dictionary of area/group field values.
#The values represent the "high value" to be
# used with the SSN validation algorithm.
SSN_HIGH_VALUES ={
"001":"04","002":"02","001":"04","002":"02","003":"02","004":"06","005":"06","006":"06",
"007":"06","008":"90","009":"88","010":"90","011":"88","012":"88","013":"88","014":"88",
"015":"88","016":"88","017":"88","018":"88","019":"88","020":"88","021":"88","022":"88",
"023":"88","024":"88","025":"88","026":"88","001":"04","002":"02","003":"02","004":"06",
"005":"06","006":"06","007":"06","008":"90","009":"88","010":"90","011":"88","012":"88",
"013":"88","014":"88","015":"88","016":"88","017":"88","018":"88","019":"88","020":"88",
"021":"88","022":"88","023":"88","024":"88","025":"88","026":"88","027":"88","028":"88",
"029":"88","030":"88","031":"88","032":"88","033":"88","034":"88","035":"72","036":"70",
"037":"70","038":"70","039":"70","040":"08","041":"08","042":"08","043":"08","044":"08",
"045":"08","046":"08","047":"08","048":"08","049":"08","050":"96","051":"96","052":"96",
"053":"96","054":"96","055":"94","056":"94","057":"94","058":"94","059":"94","060":"94",
"061":"94","062":"94","063":"94","064":"94","065":"94","066":"94","067":"94","068":"94",
"069":"94","070":"94","071":"94","072":"94","073":"94","074":"94","075":"94","076":"94",
"077":"94","078":"94","079":"94","080":"94","081":"94","082":"94","083":"94","084":"94",
"085":"94","086":"94","087":"94","088":"94","089":"94","090":"94","091":"94","092":"94",
"093":"94","094":"94","095":"94","096":"94","097":"94","098":"94","099":"94","100":"94",
"101":"94","102":"94","103":"94","104":"94","105":"94","106":"94","107":"94","108":"94",
"109":"94","110":"94","111":"94","112":"94","113":"94","114":"94","115":"94","116":"94",
"117":"94","118":"94","119":"94","120":"94","121":"94","122":"94","123":"94","124":"94",
"125":"94","126":"94","127":"94","128":"94","129":"94","130":"94","131":"94","132":"94",
"133":"94","134":"94","135":"17","136":"17","137":"17","138":"17","139":"17","140":"17",
"141":"15","142":"15","143":"15","144":"15","145":"15","146":"15","147":"15","148":"15",
"149":"15","150":"15","151":"15","152":"15","153":"15","154":"15","155":"15","156":"15",
"157":"15","158":"15","159":"82","160":"82","161":"82","162":"82","163":"82","164":"82",
"165":"82","166":"82","167":"82","168":"82","169":"82","170":"82","171":"82","172":"82",
"173":"82","174":"82","175":"82","176":"82","177":"82","178":"82","179":"82","180":"82",
"181":"82","182":"82","183":"82","184":"82","185":"82","186":"82","187":"82","188":"82",
"189":"82","190":"82","191":"82","192":"82","193":"82","194":"82","195":"82","196":"82",
"197":"82","198":"82","199":"82","200":"82","201":"82","202":"82","203":"82","204":"82",
"205":"82","206":"82","207":"80","208":"80","209":"80","210":"80","211":"80","212":"75",
"213":"75","214":"75","215":"75","216":"73","217":"73","218":"73","219":"73","220":"73",
"221":"04","222":"02","223":"99","224":"99","225":"99","226":"99","227":"99","228":"99",
"229":"99","230":"99","231":"99","232":"53","233":"51","234":"51","235":"51","236":"51",
"237":"99","238":"99","239":"99","240":"99","241":"99","242":"99","243":"99","244":"99",
"245":"99","246":"99","247":"99","248":"99","249":"99","250":"99","251":"99","252":"99",
"253":"99","254":"99","255":"99","256":"99","257":"99","258":"99","259":"99","260":"99",
"261":"99","262":"99","263":"99","264":"99","265":"99","266":"99","267":"99","268":"11",
"269":"11","270":"11","271":"11","272":"11","273":"11","274":"11","275":"11","276":"11",
"277":"11","278":"11","279":"11","280":"11","281":"11","282":"11","283":"11","284":"11",
"285":"11","286":"11","287":"11","288":"11","289":"11","290":"11","291":"11","292":"11",
"293":"11","294":"11","295":"11","296":"08","297":"08","298":"08","299":"08","300":"08",
"301":"08","302":"08","303":"31","304":"29","305":"29","306":"29","307":"29","308":"29",
"309":"29","310":"29","311":"29","312":"29","313":"29","314":"29","315":"29","316":"29",
"317":"29","318":"04","319":"04","320":"04","321":"04","322":"04","323":"04","324":"04",
"325":"04","326":"04","327":"04","328":"04","329":"04","330":"04","331":"04","332":"04",
"333":"04","334":"04","335":"04","336":"04","337":"04","338":"04","339":"04","340":"04",
"341":"04","342":"04","343":"04","344":"04","345":"04","346":"04","347":"04","348":"04",
"349":"04","350":"04","351":"04","352":"04","353":"04","354":"04","355":"04","356":"04",
"357":"04","358":"04","359":"04","360":"04","361":"04","362":"33","363":"33","364":"33",
"365":"33","366":"33","367":"33","368":"31","369":"31","370":"31","371":"31","372":"31",
"373":"31","374":"31","375":"31","376":"31","377":"31","378":"31","379":"31","380":"31",
"381":"31","382":"31","383":"31","384":"31","385":"31","386":"31","387":"27","388":"27",
"389":"27","390":"27","391":"27","392":"27","393":"27","394":"27","395":"25","396":"25",
"397":"25","398":"25","399":"25","400":"65","401":"65","402":"65","403":"65","404":"65",
"405":"63","406":"63","407":"63","408":"99","409":"99","410":"99","411":"99","412":"99",
"413":"99","414":"99","415":"99","416":"59","417":"59","418":"59","419":"59","420":"59",
"421":"59","422":"59","423":"59","424":"57","425":"99","426":"99","427":"97","428":"97",
"429":"99","430":"99","431":"99","432":"99","433":"99","434":"99","435":"99","436":"99",
"437":"99","438":"99","439":"99","440":"21","441":"21","442":"21","443":"21","444":"21",
"445":"21","446":"21","447":"19","448":"19","449":"99","450":"99","451":"99","452":"99",
"453":"99","454":"99","455":"99","456":"99","457":"99","458":"99","459":"99","460":"99",
"461":"99","462":"99","463":"99","464":"99","465":"99","466":"99","467":"99","468":"47",
"469":"47","470":"47","471":"47","472":"47","473":"47","474":"47","475":"47","476":"47",
"477":"45","478":"37","479":"35","480":"35","481":"35","482":"35","483":"35","484":"35",
"485":"35","486":"23","487":"23","488":"23","489":"23","490":"23","491":"23","492":"23",
"493":"23","494":"23","495":"23","496":"23","497":"23","498":"21","499":"21","500":"21",
"501":"31","502":"31","503":"39","504":"37","505":"51","506":"49","507":"49","508":"49",
"509":"25","510":"25","511":"25","512":"25","513":"25","514":"25","515":"25","516":"43",
"517":"41","518":"73","519":"73","520":"51","521":"99","522":"99","523":"99","524":"99",
"525":"99","526":"99","527":"99","528":"99","529":"99","530":"99","531":"59","532":"59",
"533":"57","534":"57","535":"57","536":"57","537":"57","538":"57","539":"57","540":"71",
"541":"71","542":"69","543":"69","544":"69","545":"99","546":"99","547":"99","548":"99",
"549":"99","550":"99","551":"99","552":"99","553":"99","554":"99","555":"99","556":"99",
"557":"99","558":"99","559":"99","560":"99","561":"99","562":"99","563":"99","564":"99",
"565":"99","566":"99","567":"99","568":"99","569":"99","570":"99","571":"99","572":"99",
"573":"99","574":"45","575":"99","576":"99","577":"41","578":"41","579":"41","580":"37",
"581":"99","582":"99","583":"99","584":"99","585":"99","586":"57","587":"97","589":"99",
"590":"99","591":"99","592":"99","593":"99","594":"99","595":"99","596":"80","597":"80",
"598":"80","599":"80","600":"99","601":"99","602":"57","603":"57","604":"55","605":"55",
"606":"55","607":"55","608":"55","609":"55","610":"55","611":"55","612":"55","613":"55",
"614":"55","615":"55","616":"55","617":"55","618":"55","619":"55","620":"55","621":"55",
"622":"55","623":"55","624":"55","625":"55","626":"55","627":"02","628":"02","629":"02",
"630":"02","631":"02","632":"02","633":"02","634":"02","635":"02","636":"02","637":"02",
"638":"02","639":"02","640":"02","641":"02","642":"98","643":"98","644":"98","645":"98",
"646":"86","647":"86","648":"40","649":"38","650":"40","651":"38","652":"38","653":"38",
"654":"22","655":"22","656":"22","657":"22","658":"20","659":"12","660":"12","661":"12",
"662":"12","663":"10","664":"10","665":"10","667":"30","668":"28","669":"28","670":"28",
"671":"28","672":"28","673":"28","674":"28","675":"28","676":"10","677":"10","678":"10",
"679":"09","680":"76","681":"10","682":"09","683":"09","684":"09","685":"09","686":"09",
"687":"09","688":"09","689":"09","690":"09","691":"03","692":"03","693":"03","694":"03",
"695":"03","696":"03","697":"03","698":"03","699":"01","700":"18","701":"18","702":"18",
"703":"18","704":"18","705":"18","706":"18","707":"18","708":"18","709":"18","710":"18",
"711":"18","712":"18","713":"18","714":"18","715":"18","716":"18","717":"18","718":"18",
"719":"18","720":"18","721":"18","722":"18","723":"18","724":"28","725":"18","726":"18",
"727":"10","728":"14","729":"07","730":"05","731":"05","732":"05","733":"05","750":"05",
"751":"03","756":"01","757":"01","758":"01","759":"01","760":"01","761":"01","762":"01",
"764":"62","765":"62","766":"48","767":"48","768":"48","769":"48","770":"48","771":"46",
"772":"46"
}

# frozenset makes for quick access to the key fields of the SSN high values
# the key fields are the 'areas'
VALID_SSN_AREAS = frozenset(SSN_HIGH_VALUES.keys())

# a frozenset of invalid ssn numbers
INVALID_SSN_NUMBERS = frozenset([
"002281852","042103580","062360749","078051120",
"095073645","128036045","135016629","141186941",
"165167999","165187999","165207999","165227999",
"165247999","189092294","212097694","212099999",
"306302348","308125070","468288779","549241889",
"987654320","987654321","987654322","987654323",
"987654324","987654325","987654326","987654327",
"987654328","987654329"])</pre>
<h2>The Test Harness</h2>
<p>Description of the Test</p>
<p>There are a few parts to the test.  I built it in a few steps.  first, the <code>setUp</code> method creates a &#8220;Good&#8221; SSN, &#8220;Good&#8221; Group and &#8220;Good&#8221; High Group values.  The &#8220;High Group&#8221; is that value that is highest allowed for the particular &#8220;Area&#8221; value.  The first method, <code>testSSNValidation(self)</code> is a test of the implementation with a known good serial number.  This is the first method that I wrote, because this is where I &#8220;worked out&#8221; the algorithm that I wanted to use in the implementation. </p>
<p>Once I got the &#8220;right answer&#8221;, I could create another test method to throw a whole bunch of numbers at it to ensure that it is working right under all circumstances and border conditions.  Note the extensive documentation &#8212; you may be passing this code off to another team in the future, and it&#8217;s important to leave a good trail.  Validation this complicated is not <i>self documenting</i>.  Walking through the method, I first created the return value that I wanted to use and made it <code>False</code> so that if any validation failed it would fall through and just return.  Once the default was set, I stripped out any hyphens and whitespace, I checked the value for length, then check to ensure that the Area (first three digits) is valid.  If the number passes all these tests, we can then go into the <I>private method</i> that I created to implement the &#8220;high values&#8221; formula <code>__isGroupValid(self,group,groupHighestValue)</code>:</p>
<p><sup><em>Note: this is a pretty complex formula so I&#8217;m not going to get into the gritty details &#8212; see the SSA site for more info&#8230;</em></sup></p>
<pre>finvs/tests.py</pre>
<pre>"""
This file tests the finvs application
"""
import unittest
from django.test import TestCase
from finvs.utils.validation_formulas import ValidationFormulas
from finvs.utils.ssn_constants import SSN_HIGH_VALUES
from finvs.utils.ssn_constants import INVALID_SSN_NUMBERS
from finvs.utils.ssn_constants import VALID_SSN_AREAS

class ValidatorsTestCase(unittest.TestCase):

    GOOD_SSN = ' '
    GOOD_GRP = 0
    GOOD_HIGH_GRP_VAL = 0

    def setUp(self):
        self.GOOD_SSN = '526-33-4563'
        self.GOOD_GRP = 33
        self.GOOD_HIGH_GRP_VAL = 99

   def testSSNValidation(self):
        """Tests the algorithm in the validator against
            known high values for the first three digits

           Validate a Social Security Number (SSN). A valid SSN
            is determined by the following rules according to the
            website http://www.ssa.gov/employer/ssnweb.htm

           Area-Group-Serial (AAA-GG-SSSS) The Social Security
            number consists of nine (9) digits. The first three
            (3) digits denote the area (or State) where the application
            for an original Social Security number was filed.

           Within each area, the group number (middle two (2) digits)
            range from 01 to 99 but are not assigned in consecutive
            order. For administrative reasons, group numbers issued
            first consist of the ODD numbers from 01 through 09 and
            then EVEN numbers from 10 through 98, within each area
            number allocated to a State. After all numbers in group
            98 of a particular area have been issued, the EVEN Groups
            02 through 08 are used, followed by ODD Groups 11 through
            99.

           Within each group, the serial numbers (last four (4) digits)
            run consecutively from 0001 through 9999.
           """
    def testSSNAlgorithm(self):
        bool=False
        ssn_v=self.GOOD_SSN.translate(None,'-')
        ssn_v=ssn_v.strip()
        #print("SSN: %s" % (ssn_v))
        if (len(ssn_v)==9 and ssn_v.isdigit()):
            #print("SSN passed for length and digits: %s" % (ssn_v))
            key_v = ssn_v[3:5]
            if ssn_v[0:3] in VALID_SSN_AREAS:
                hg_v = SSN_HIGH_VALUES[ssn_v[0:3]]
                #check for valid group for Area ID in private method
                bool = self.__isGroupValid(int(key_v), int(hg_v))
            #print("VALID_GROUP: %s" % (bool))
        self.assertTrue(bool)

    def testIsValidSSN(self):
        validator = ValidationFormulas()
        self.assertTrue(validator.isValidSSN(self.GOOD_SSN))
        long_ssn = "   %s   " % (self.GOOD_SSN)
        self.assertTrue(validator.isValidSSN(long_ssn))
        stripped_ssn = self.GOOD_SSN.translate(None,'-')
        self.assertTrue(validator.isValidSSN(stripped_ssn))
        for bad_ssn in INVALID_SSN_NUMBERS:
            self.assertFalse(validator.isValidSSN(bad_ssn))
        bad_ssn = "742-51-1234"
        self.assertFalse(validator.isValidSSN(bad_ssn))

    def testGroupValidAlgorithm(self):
        self.assertTrue(self.__isGroupValid(self.GOOD_GRP,self.GOOD_HIGH_GRP_VAL))

    def testShowSSNAreas(self):
        self.assertEqual(len(SSN_HIGH_VALUES.keys()), len(VALID_SSN_AREAS))

    #-----------------Private Functions-------------------------------------------

def __isSSNGroupValid(self,group,groupHighestValue):

        """Within each area, the group number (middle two (2) digits)
            range from 01 to 99 but are not assigned in consecutive
            order. For administrative reasons, group numbers issued
            first consist of the ODD numbers from 01 through 09 and
            then EVEN numbers from 10 through 98, within each area
            number allocated to a State. After all numbers in group
            98 of a particular area have been issued, the EVEN Groups
            02 through 08 are used, followed by ODD Groups 11 through
            99.
            """
        validGroup = False
        moduloGroupNumber = group % 2
        moduloGroupHighest = groupHighestValue % 2

        if ((moduloGroupHighest == 1) &#038; (groupHighestValue >= 1) &#038; (groupHighestValue <= 9)):             if ((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= groupHighestValue)):                 validGroup = True          elif ((moduloGroupHighest == 0) &#038; (groupHighestValue >= 10) &#038; (groupHighestValue <= 98)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10) &#038; (group <= groupHighestValue))):                 validGroup = True          elif ((moduloGroupHighest == 0) &#038; (groupHighestValue >= 2) &#038; (groupHighestValue <= 8)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10)
            &#038; (group <= 98))             | ((moduloGroupNumber == 0) &#038; (group >= 2)
            &#038; (group <= groupHighestValue))):                 validGroup = True          elif ((moduloGroupHighest == 1) &#038; (groupHighestValue >= 11) &#038; (groupHighestValue <= 99)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10)
            &#038; (group <= 98))             | ((moduloGroupNumber == 0) &#038; (group >= 2)
            &#038; (group <= 8))             | ((moduloGroupNumber == 1) &#038; (group >= 11)
            &#038; (group <= groupHighestValue))):                 validGroup = True          return validGroup</pre>
<p>The next methods test and retest the algorithms, ensuring that bad numbers, numbers with white space and invalid numbers also return <code>False</code> for their tests.  More comprehensive tests can and should be written before this code goes into production, but there is now the basis for an implementation and transfer of our "test" code to <code>validation_formulas.py</code> </p>
<h2>The Implementation</h2>
<p>The implementation is almost "cut and paste" from the test at this point.  The private method is a complete clone, and the public validation method just changes a few things around to get the right information.  This particular implementation is somewhat basic, since it only returns <code>True</code> or <code>False<code> with the number submitted -- it doesn't tell the requestor exactly what happened, just that the number submitted is good or bad.  More sophistication can be added by returning pointers towards messages, then assigning a pointer for each test that fails.  A test with no failure could return a "zero", and after that messages could pile up.  </p>
<p>This is a good strategy for any sophisticated validation system, since there are few hard-core validations that need to have a binary response -- it is more important to have some "why" answers with a failed validation, and in some cases, validations may not "fail" in a particular manner.  One example of a "gray area" in validation would be in profanity filters.  Some lexicon may be quite profane to a particular user, where another may not.  In these particular cases, a "score" might be returned for verification instead of a binary.  Another case would be in Address fields -- ones that don't have a number followed by string of letters may not be a real address, but there are outliers that need to be covered...</p>
<pre>validation_formulas.py</pre>
<pre>#!/usr/bin/env python
# encoding: utf-8
"""
validation_formulas.py
Created by Danilo Gurovich on 2010-03-19.
"""
from finvs.utils.ssn_constants import *

class ValidationFormulas:

    #Validates Social Security Number
    def isValidSSN(self,ssn_str):
        """Validate a Social Security Number (SSN). A valid SSN
            is determined by the following rules according to the
            website http://www.ssa.gov/employer/ssnweb.htm

           Area-Group-Serial (AAA-GG-SSSS) The Social Security
            number consists of nine (9) digits. The first three
            (3) digits denote the area (or State) where the application
            for an original Social Security number was filed.

           Within each area, the group number (middle two (2) digits)
            range from 01 to 99 but are not assigned in consecutive
            order. For administrative reasons, group numbers issued
            first consist of the ODD numbers from 01 through 09 and
            then EVEN numbers from 10 through 98, within each area
            number allocated to a State. After all numbers in group
            98 of a particular area have been issued, the EVEN Groups
            02 through 08 are used, followed by ODD Groups 11 through
            99.

           Within each group, the serial numbers (last four (4) digits)
            run consecutively from 0001 through 9999.
           """
        isValid=False
        #strip all the dashes and whitespace
        ssn_v=ssn_str.translate(None,'-')
        ssn_v=ssn_v.strip()
        #Check length and ensure that it's a digit
        if (len(ssn_v)==9 and ssn_v.isdigit()):
            #print("SSN passed for length and digits: %s" % (ssn_v))
            #Check to ensure that it's not an invalid number
            if(ssn_v not in INVALID_SSN_NUMBERS):
                key_v = ssn_v[3:5]
                if ssn_v[0:3] in VALID_SSN_AREAS:
                    hg_v = SSN_HIGH_VALUES[ssn_v[0:3]]
                    #check for valid group for Area ID in private method
                    isValid = self.__isSSNGroupValid(int(key_v), int(hg_v))

        return isValid

    #---------- Private Functions -------------------------------------------------------

def __isSSNGroupValid(self,group,groupHighestValue):

        """Within each area, the group number (middle two (2) digits)
            range from 01 to 99 but are not assigned in consecutive
            order. For administrative reasons, group numbers issued
            first consist of the ODD numbers from 01 through 09 and
            then EVEN numbers from 10 through 98, within each area
            number allocated to a State. After all numbers in group
            98 of a particular area have been issued, the EVEN Groups
            02 through 08 are used, followed by ODD Groups 11 through
            99.
            """
        validGroup = False
        moduloGroupNumber = group % 2
        moduloGroupHighest = groupHighestValue % 2

        if ((moduloGroupHighest == 1) &#038; (groupHighestValue >= 1) &#038; (groupHighestValue <= 9)):             if ((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= groupHighestValue)):                 validGroup = True          elif ((moduloGroupHighest == 0) &#038; (groupHighestValue >= 10) &#038; (groupHighestValue <= 98)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10) &#038; (group <= groupHighestValue))):                 validGroup = True          elif ((moduloGroupHighest == 0) &#038; (groupHighestValue >= 2) &#038; (groupHighestValue <= 8)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10)
            &#038; (group <= 98))             | ((moduloGroupNumber == 0) &#038; (group >= 2)
            &#038; (group <= groupHighestValue))):                 validGroup = True          elif ((moduloGroupHighest == 1) &#038; (groupHighestValue >= 11) &#038; (groupHighestValue <= 99)):             if (((moduloGroupNumber == 1) &#038; (group >= 1) &#038; (group <= 9))             | ((moduloGroupNumber == 0) &#038; (group >= 10)
            &#038; (group <= 98))             | ((moduloGroupNumber == 0) &#038; (group >= 2)
            &#038; (group <= 8))             | ((moduloGroupNumber == 1) &#038; (group >= 11)
            &#038; (group <= groupHighestValue))):                 validGroup = True          return validGroup</pre>
<h2>Why is this Cool?</h2>
<p>This whole way of developing code is cool because I don't need to fire up the server and drill through a bunch of pages to find out if I had "done it right".  In fact, I haven't even wrote the service that I want to connect to, and I haven't even filled out the model in the application that I'm working in.  I'm just doing one piece at a time, then building the model and service once I have everything ironed out.  I'll have super-duper test coverage without writing my code twice, and really have confidence in the entire back-end as I build the view and controller modules out, buying plenty of time to optimize, refactor and work everything into some nice design patterns (<em>Strategy</em>, anyone?)</p>
]]></content:encoded>
			<wfw:commentRss>http://gurovich.com/site/2010/03/30/ssn_validation_with_django/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Test Driven Development with Django Unit Testing</title>
		<link>http://gurovich.com/site/2010/03/20/test-driven-development-with-django-unit-testing/</link>
		<comments>http://gurovich.com/site/2010/03/20/test-driven-development-with-django-unit-testing/#comments</comments>
		<pubDate>Sat, 20 Mar 2010 16:56:20 +0000</pubDate>
		<dc:creator>Danilo Gurovich</dc:creator>
				<category><![CDATA[Best Practices]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[Enterprise Development]]></category>
		<category><![CDATA[Django TDD]]></category>
		<category><![CDATA[Django Test Driven Development]]></category>
		<category><![CDATA[Django Unit Testing]]></category>
		<category><![CDATA[Django unittest]]></category>
		<category><![CDATA[python unit testing]]></category>
		<category><![CDATA[TDD python]]></category>
		<category><![CDATA[unittest python]]></category>

		<guid isPermaLink="false">http://gurovich.com/site/?p=1266</guid>
		<description><![CDATA[Django has wonderful unit testing functionality built into the framework. It is often ignored because Django gives so much functionality &#8220;out of the box&#8221;, but this doesn&#8217;t mean that one shouldn&#8217;t do &#8220;their part&#8221; when extending the framework to build your own applications. Tests are a great way to &#8220;figure out&#8221; what needs to be [...]]]></description>
			<content:encoded><![CDATA[<p>Django has wonderful unit testing functionality built into the framework. It is often ignored because Django gives so much functionality &#8220;out of the box&#8221;, but this doesn&#8217;t mean that one shouldn&#8217;t do &#8220;their part&#8221; when extending the framework to build your own applications.  Tests are a great way to &#8220;figure out&#8221; what needs to be done, then actually taking what you&#8217;ve learned to implement in the application proper.</p>
<p>My attempt here is to show how I used TDD in an Django application.  We&#8217;ll create a basic project with a single application and use unit testing to figure out an algorithm an implement it in the application&#8217;s class.<span id="more-1266"></span></p>
<h3>Why Test at All?</h3>
<p>I&#8217;ve spoken to many groups about Test Driven Development (TDD).  Many experienced engineers don&#8217;t do it because they haven&#8217;t been indoctrinated into the practice early, and they are established in an organization where they maintain and extend code that they might have been writing for months or even years.  The code has been fully tested and it is solid.  Why write unit tests?</p>
<p>Newer developers don&#8217;t know how to start with tests.  They write code, then write tests, then get frustrated because they are continuously writing code twice.  Corners get cut, and at best you get the &#8220;bare minimum&#8221; of coverage, which is basically what the boss sees and reviews.</p>
<p>Both of these scenarios are unfortunate, because there are a few things that TDD gives gives all engineers and the organizations that they work for:</p>
<ul>
<li>More efficient QA. QA engineers and testers don&#8217;t have to test for functionality, and they get better quality code.?What does this mean? It means that they get to do the part of their job that is more meaningful to the company as a whole. If the basic code and functionality are automatically tested, QA can then find the border conditions and those evil underlying non-obvious bugs that can kill an application, and if large enough, kill a company&#8217;s profits.</li>
<li>Better and more cohesive organizations. Test Driven Development builds confidence throughout the entire organization. Engineers working often in geographically dispersed groups need to &#8220;trust&#8221; each other, their skills and abilities. If everyone in the organization is using TDD, these groups will know that the quality coming from others will work. This not only creates trust amongst engineers, but throughout the organization, from Marketing and Business resources all the way through QA.</li>
<li>Professionalism. Being a &#8220;Pro&#8221; doesn&#8217;t mean generating thousands of lines of code.?It means generating efficient, simple to maintain, easily configurable code that &#8220;just works&#8221; every time. It not only has to work, it has to be tested and documented.</li>
<li>Peer pressure to &#8220;do the right thing&#8221;. If the senior members and thought leaders of an engineering group are all using TDD, documenting their code and communicating effectively, this will spread throughout the organization and foment a best practices environment.</li>
</ul>
<p>Test Driven Development with high unit test coverage creates efficient, spirited and profitable software. The balance of this post will explain just how I use TDD to implement a complex Bank Routing Number (ABN) validation class. I am assuming that the reader is familiar with Django, and has a comfortable understanding with basic Django and Python development principles:</p>
<h3>Create the Project and Application</h3>
<p>My application&#8217;s name is</p>
<pre>valid_lib</pre>
<p>It is destined to be a Service Platform that allows all kinds of complex financial form data to be validated through a ReSTful interface. At the moment I&#8217;m not worried about the Service, the Interfaces or even the Business Model. Currently, I want to get all the validation algorithms and calculations in place. The rest of the stuff is pretty easy.  At the command line, in my Django development directory, I type:</p>
<pre>django-admin.py startproject valid_lib</pre>
<p>To start my project.  I <code>cd</code> into the <code>valid_lib</code> directory and then type:</p>
<pre>django-admin.py startapp finvs</pre>
<p>To create my application and it&#8217;s directory.  At this point I add the <code>valid_lib.finvs</code> to my <code>INSTALLED_APPS</code> in the the <code>settings.py</code> file, and while I&#8217;m there I add my database information, even though I really won&#8217;t be using it yet. I also add another folder in the <code>finvs</code> directory that I name <code>util</code>.  Run a <code>manage.py syncdb</code> against the application&#8217;s root and you should be good to go.  Everything should now look like fig 1:</p>
<div id="attachment_1268" class="wp-caption aligncenter" style="width: 204px"><a href="http://gurovich.com/site/wp-content/uploads/2010/03/fig1.png"><img class="size-full wp-image-1268 " title="Initial TDD validation app structure" src="http://gurovich.com/site/wp-content/uploads/2010/03/fig1.png" alt="" width="194" height="222" /></a><p class="wp-caption-text">fig 1: Initial TDD validation app structure</p></div>
<p><em>(I&#8217;m writing this on my MacBook Pro and using Textmate, but you should be getting similar results however you&#8217;re doing it)</em></p>
<p>Notice that there is a <code>test.py </code> file in the<code> finvs </code>directory.  This is where we&#8217;re going to start.  Django&#8217;s default unit testing fixture looks for this file and will execute tests within it.  We&#8217;ll open that file up and begin writing our code there.</p>
<h3>ABN Validation</h3>
<p>ABA (American Banker&#8217;s Association) routing numbers (ABNs) are used to identify financial institutions when making transactions. This number is usually required when setting up a bank account for direct deposits, automated payments, etc. Or when making payments by &#8220;paperless&#8221; check over the phone or online.</p>
<p>These routing numbers include a checksum digit which can be used to help verify if a given number is valid or not.</p>
<p>The algorithm checks the first second and third number and adds them together, then iterates through every three numbers (total of 9) to return a value. If the resulting sum is an even multiple (think <code>modulus</code> here&#8230;) of ten (but not zero), the ABA routing number is good. ?We&#8217;ll now write a test with a valid ABN to see if we can get things to work correctly.  In the <code>tests.py</code> file, write:</p>
<pre>
"""
This file tests the finvs application
"""
import unittest
from django.test import TestCase

#Here are some bank numbers that we can run tests against...
SHORT_BAD_ABN_NUM = 1000012
LONG_BAD_ABN_NUM = 255073345999
BAD_ABN_NUM = 255073545
GOOD_ABN_NUM = 255073345

class ValidatorsTestCase(unittest.TestCase):

    def setUp(self):
        self.value = CITY_CODE_FRAUD

    def testAbnAlgorythm(self):
        """Tests the algorythm with a known good bank number.
           This test is here to create the algorythm to be used
             in the validator implementation.  When it's right,
             cut and paste it into the validator class.

           The algorithm checks the first second and third number
            and adds them together, then iterates through every
            three numbers (total of 9) to return a value.

           If the resulting sum is an even multiple of ten
            (but not zero), the ABA routing number is good.
           """
        n=0
        bank_str = str(GOOD_ABN_NUM)
        num_length = len(bank_str)
        if (num_length ==9):
            for j in range(0,num_length,3):
                t = int(bank_str[j]) #this is the first digit
                ti = int(bank_str[j + 1]) #this is the second digit
                tii = int(bank_str[j + 2]) #this is the third digit
                n += (t * 3)+ (ti * 7)+ tii #add them up
            #check for zero and modulus of 10
            print((n != 0) &#038; ((n % 10) == 0))  #we'll take this line out when we get it right.
            self.assertTrue((n != 0) &#038; ((n % 10) == 0))
</pre>
<p>Now, at the application root, run:</p>
<pre>python manage.py test finvs.ValidatorsTestCase</pre>
<p>You should get something very similar to this:</p>
<pre>
$ python manage.py test finvs.ValidatorsTestCase
Creating test database...
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Installing index for auth.Permission model
Installing index for auth.Message model
True
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Destroying test database...
</pre>
<p>The <b><code>True</code></b> that you see in the output is your print statement. Notice that the system also creates and destroys a test database.  This is extremely cool functionality that can be used with Django&#8217;s Fixtures, but we&#8217;re not going to use it here for this code set.  We haven&#8217;t written a line of production code, but our test passes, and if you look at the test, <i>it is the basis for the actual implementation</i>!</p>
<h3>Implement It!</h3>
<p>We&#8217;re really close, but we&#8217;ll need to make a few changes before we implement it in our &#8220;production&#8221; code.  Seriously, we&#8217;re really close!</p>
<p>In the <code>utils</code> directory, create the file <code>validation_formulas.py</code>  This is where we&#8217;ll add our ABN validation class.  In my test code I created a check to ensure that the number sent in had exactly 9 digits.  I did this because I didn&#8217;t want to have a <code>try / except</code> block, and to keep it clean. Feel free to add this if needed.  The other change is that we actually need to return something &#8212; True or False in this case.  The test code&#8217;s <code>def</code> doesn&#8217;t return anything, it just makes an assertion.  To validate something, we need to insert a parameter into the method and then return a true/false.  We also need to give it a name.  In the <code>validation_formulas.py</code> file, you can now insert:</p>
<pre>
class ValidationFormulas:

    def isBankRoutingNumber(self,bankNumber):
        """Parses the bank routing number for purposes of finding a fraudulent entry.
            the algorithm check the first second and third number and adds them together,
            then iterates through every three numbers (total of 9) to return a value.

           If the resulting sum is an even multiple of ten (but not zero), the ABA
            routing number is good.

           param -- bankNumber the 9 digit bank number;

           return boolean true if number is valid, false if not. false is the default
           """
        n=0
        retbool=False
        bank_str = str(bankNumber)
        num_length = len(bank_str)
        if (num_length ==9):
            for j in range(0,num_length,3):
                t = int(bank_str[j])
                ti = int(bank_str[j + 1])
                tii = int(bank_str[j + 2])
                n += (t * 3)+ (ti * 7)+ tii
            retbool = (n != 0) &#038; ((n % 10) == 0)
        #return the value
        return retbool
</pre>
<p>Notice the added documentation.  Some of it is cut and paste from the Test, the rest is information that you&#8217;ll want to convey to anyone that is using this code in the future, because hopefully at some point the company you work for will realize just how brilliant you are and promote you to CTO, where you won&#8217;t get to write code anymore.  You sure as heck don&#8217;t want to leave the poor schlep that is hired to take your place in the dark!</p>
<p>Also notice that I have a default return of <code>False</code>  some applications may need a different default return, or this return may be handled in a manager class that registers the default returns in some kind of persistence layer that is more dynamic.  Your mileage may vary.</p>
<h3>Wrapping it Up</h3>
<p>There it is.  You&#8217;ve written a test first.  You&#8217;ve used the implementation of the test and created a sensible implementation.  If there are other ways of refining your code or cleaning things up, you can always go back and make more changes.  But your&#8217;e not done.  You need to write more tests to actually <b>verify</b> that this algorithm works, that it shows false for bad routing numbers, and that it an spot when the length of the number is incorrect without throwing any index out of bounds exceptions or the like.  you can then add more test methods to your <code>ValidatorsTestCase</code> class.  I&#8217;ve also leveraged the <code>setUp</code> method that is provided with the framework to generate my constants for me. The code will then look like this:</p>
<pre>
"""
This file tests the finvs application
"""
import unittest
from django.test import TestCase
from valid_lib.finvs.utils.validation_formulas import ValidationFormulas

class ValidatorsTestCase(unittest.TestCase):

    def setUp(self):
        self.SHORT_BAD_ABN_NUM = 1000012
        self.LONG_BAD_ABN_NUM = 255073345999
        self.BAD_ABN_NUM = 255073545
        self.GOOD_ABN_NUM = 255073345

    def testAbnAlgorythm(self):
        """Tests the algorythm with a known good bank number.
           This test is here to create the algorythm to be used
             in the validator implementation.  When it's right,
             cut and paste it into the validator class.

           The algorithm checks the first second and third number
            and adds them together, then iterates through every
            three numbers (total of 9) to return a value.

           If the resulting sum is an even multiple of ten
            (but not zero), the ABA routing number is good.
           """
        n=0
        bank_str = str(self.GOOD_ABN_NUM)
        num_length = len(bank_str)
        if (num_length ==9):
            for j in range(0,num_length,3):
                t = int(bank_str[j])
                ti = int(bank_str[j + 1])
                tii = int(bank_str[j + 2])
                n += (t * 3)+ (ti * 7)+ tii
            self.assertTrue((n != 0) &#038; ((n % 10) == 0))

    def testBankRouter(self):
        """Tests the algorythm in the validator against
              known good and bad bank numbers.
           """
        bank_num = self.SHORT_BAD_ABN_NUM
        formt = ValidationFormulas()
        bool = formt.isBankRoutingNumber(bank_num)
        self.assertFalse(bool)

        bank_num = self.LONG_BAD_ABN_NUM
        formt = ValidationFormulas()
        bool = formt.isBankRoutingNumber(bank_num)
        self.assertFalse(bool)

        bank_num = self.BAD_ABN_NUM
        formt = ValidationFormulas()
        bool = formt.isBankRoutingNumber(bank_num)
        self.assertFalse(bool)

        bank_num = self.GOOD_ABN_NUM
        formt = ValidationFormulas()
        bool = formt.isBankRoutingNumber(bank_num)
        self.assertTrue(bool)</b>
</pre>
<p>I&#8217;ve added an import for the &#8220;production&#8221; class that i&#8217;m testing (<code>ValidationFormulas</code>).  Finally, I&#8217;ve added a new method that implements new tests against the actual methods in this class to check my work.  Running the tests now reveal:</p>
<pre>
Creating test database...
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Installing index for auth.Permission model
Installing index for auth.Message model
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
Destroying test database...
</pre>
<p> If someone in the future messes with the &#8220;production&#8221; code implementation and things get out of whack, It will break here instead of in front of the user.  By combining this with a continuous-build environments, a lot of mistakes can be caught extremely fast, sometimes within seconds of a source control check in!</p>
<p>Enjoy.</p>
]]></content:encoded>
			<wfw:commentRss>http://gurovich.com/site/2010/03/20/test-driven-development-with-django-unit-testing/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
