Calculating Leap Years in PHP with PHPUnit
I don’t know about you, but using my child-like-memory-of-a-brain, always thought that a leap year was one which was wholly divisible by 4. So, the year 2000….the year 1992….the year 2020…all leap years, right? Well, actually, no.
The rule is really this:
- a leap year is one which is divisible by 4 and not 100 OR,
- if it is divisble by 4 and 100, then it must also be divisible by 400.
So, in effect, the year 1992 is a leap year (divisble by 4, but not 100) but 1900 isn’t (divisble by 4 and 100, but not 400).
Who’d have guessed and what’s more, that I would be re-educated from a Microsoft Excel article!
So what’s making me talk about this? Well, I was intrigued by a piece of code in the excellent Advanced PHP Programming book by George Schlossnagle which jarred my mistaken belief about the calculation. Here is the pertinent portion which is used to calculate the number of days in February if it is and isn’t a leap year:
if((($year % 4 == 0) && ($year % 100)) || ($year % 400 == 0)) { |
Therefore, in a leap year, February sports 29 days or otherwise, the meagre 28.
Now that I have my head around the logic, the code makes sense, but what if that wasn’t the case or you hadn’t read my english explanation of how the calculation is done? One option, which we should do anyway, is to use PHPUnit. Let’s do that in this case.
I need to make some assumptions, so let’s start from the idea that you have PHP & composer installed, at least, and like me, you might be using Windows.
Create a directory named leap
F:\> mkdir leap |
Now install PHPUnit
F:\leap>composer require phpunit/phpunit |
Testing PHPUnit is Installed
You can check it is installed by running it, so do that now.
F:\leap>vendor\bin\phpunit |
OK, looking good. Next step is to have a basic PHPUnit configuration file. We won’t try and be fancy with all the options, here; just enough to make it work, TDD style ;-)
A Basic PHPUnit Configuration File
Open up your editor and save the following into a file named: phpunit.xml
in the leap
directory.
|
The basic idea here is that we point to where our vendor/autoload
file is so that PHPUnit can find any associated libraries, and tell PHPUnit where to find our tests. In this case, that’s the folder tests
.
Creating a Simple Test
First let’s create that tests
folder we just mentioned.
F:\leap> mkdir tests |
And using your text editor again, create the following file named LeapTest.php
inside the tests
folder.
|
This is just about the simplest test we could have. We’re going to test that true
is, err….true
. If you’re interested, there are a couple of features worth noticing.
- Class name
- Has the suffix Test. PHPUnit will specifically look for these files and treat them as test classes.
- testCase() function
- Again, PHPUnit is hunting for any functions preceded by the word
test
. You can also use a docblock if you like, but for now, we’ll stick with this style.
- Again, PHPUnit is hunting for any functions preceded by the word
Before we move on, let’s make sure it is working by running phpunit. Ensure you are in the leap
folder, and then run this:
F:\leap>vendor\bin\phpunit |
If the Gods were smiling upon you, you should have the above output: all is OK
since 1 test resulted in 1 assertion. If not, go back and ensure you didn’t introduce any typos in the test, or the configuration XML file, before trying again.
Testing the Code
I know we’ve taken a while to get to the purpose of this article, but I thought it was worth going through how to set up a basic PHPUnit harness from nothing for those that might find it useful. It’s time to test our function now.
To start, let’s remove that function and add our own function into the test case. Not the right way to do it in a proper project, but this is just to illustrate the point, so I get a free pass!
|
That matches what George had in his book in terms of logic. Now, borrowing from the Microsoft article, I am going to use all of their examples and answers, together with a new assert - assertEquals
- whose usage will become obvious when you see it. Here’s the whole class:
|
Can you guess what comes next? Yep: run phpunit.
F:\leap>vendor\bin\phpunit |
Woohoo! 1 test and 14 assertions, all resulting in that coveted OK
, so we can be sure George’s code works as expected.
Alright, we could end here, but all that repeated code is making my eyes water. Let’s finish off by using a feature called Data Providers which will allow us to parameterize these tests.
The first thing we need is a function which will return our inputs and outputs, so add this under our test function.
public function leapYearProvider() |
Strictly speaking, it didn’t have to be called somethingProvider but that’s a good standard to use and what others will expect. Lastly, we need to add an annotation to our test to tell PHPUnit which provider to use. Add this docblock in front of the test function.
/** |
14 more tests for good luck:
F:\leap>vendor\bin\phpunit |
One final note. Did you see how the output changed from running 1 test, with 14 assertions, to 14 tests, with 14 assertions? That’s the data provider at work.
Time to end here; I’m all tested out though much wiser about leap years.
Hi! Did you find this useful or interesting? I have an email list coming soon, but in the meantime, if you ready anything you fancy chatting about, I would love to hear from you. You can contact me here or at stephen ‘at’ logicalmoon.com