An election has a set of races, each of which have candidates. So a Candidate class is a natural starting point for data modeling.
What basic characteristics does a candidate have in the context of the source data?
Name, party and county election results jump out.
A candidate also seems like a natural place for data transforms and computations that now live in lib/parser.py and lib/summary.py:
Before we migrate the hard stuff, let’s start with the basics.
We’ll store new election classes in a lib/models.py (Django users, this should be familiar). We’ll store tests for our new classes in test_models.py module.
Now let’s start writing some test-driven code!
The Candidate class should be responsible for parsing a full name into first and last names (remember, candidate names in our source data are in the form (Lastname, Firstname).
Create elex4/tests/test_models.py and add test for Candidate name parts
Run test; see it fail
Write a Candidate class with a private method to parse the full name
Note: You can cheat here. Recall that the name parsing code was already written in lib/parser.py.
Run test; see it pass
In the refactoring above, notice that we’re not directly testing the name_parse method but simply checking for the correct value of the first and last names on candidate instances. The name_parse code has been nicely tucked out of sight. In fact, we emphasize that this method is an implementation detail – part of the Candidate class’s internal housekeeping – by prefixing it with two underscores.
This syntax denotes a private method that is not intended for use by code outside the Candidate class. We’re restricting (though not completely preventing) the outside world from using it, since it’s quite possible this code wil change or be removed in the future.
More frequently, you’ll see a single underscore prefix used to denote private methods and variables. This is fine, though note that only the double underscores trigger the name-mangling intended to limit usage of the method.
In addition to a name and party, each Candidate has county-level results. As part of our summary report, county-level results need to be rolled up into a racewide total for each candidate. At a high level, it seems natural for each candidate to track his or her own vote totals.
Below are a few basic assumptions, or requirements, that will help us flesh out vote-handling on the Candidate class:
With this basic list of requirements in hand, we’re ready to start coding. For each requirement, we’ll start by writing a (failing) test that captures this assumption; then we’ll write code to make the test pass (i.e. meet our assumption).
Add test to ensure Candidate‘s initial vote count is zero
Note: We created a new TestCandidateVotes class with a setUp method that lets us re-use the same candidate instance across all test methods. This makes our tests less brittle – e.g., if we add a parameter to the Candidate class, we only have to update the candidate instance in the setUp method, rather than in every test method (as we will have to do in the TestCandidate class)
Now let’s add a method to update the candidate’s total vote totals for each county result.
Finally, let’s stash the county-level results for each candidate. Although we’re not using these lower-level numbers in our summary report, it’s easy enough to add in case we need them for down the road.