I’m sure we all know the formula to convert Celsius to Fahrenheit, or convert Fahrenheit to Celsius.
It goes something like this:
celsius2fahrenheit = (temperature * 9/5) – 32
fahrenheit2celsius = (temperature – 32) * 5/9
I always forget and have to experiment a bit before I get the pluses, fives, and nines all in the right places — not to mention the correct spelling of f-a-h-r-e-n-h-e-i-t.
Luckily, I know 4 temperatures that I can test on scratch paper with this formula. They are:
- The boiling point of water (212F or 100C)
- The freezing point of water (32F or 0C)
- Standard temperature (59F or 17C)
- Forty below (-40F and -40C)
Today, I decided to codify it in a PHP script when I saw this request on PeoplePerHour.com
Now, I like to think I’m above doing someone’s homework for them (just barely), but I thought I’d take up the challenge. I also decided I would use PHPUnit to write the tests and came up with this:
TempConverter.php
<?php
class TempConverter {
public static function c2f($temp) {
return $temp * 1.0 * 9/5 + 32;
}
public static function f2c($temp) {
return ($temp -32) * 1.0 * 5/9;
}
}
TestTempConverter.php
<?php
require_once('TempConverter.php');
class TempConverterTest extends PHPUnit_Framework_Testcase
{
public function testInstantiation() {
$obj = new TempConverter();
$this->assertTrue($obj instanceof TempConverter);
}
public function testStandardTemp() {
$standardF = 59;
$standardC = 15;
$this->assertEquals($F, TempConverter::c2f($standardC));
$this->assertEquals($C, TempConverter::f2c($standardF));
}
public function testFreezing() {
$freezingF = 32;
$freezingC = 0;
$this->assertEquals($freezingF, TempConverter::c2f($freezingC));
$this->assertEquals($freezingC, TempConverter::f2c($freezingF));
}
public function testBoiling() {
$boilingF = 212;
$boilingC = 100;
$this->assertEquals($boilingF, TempConverter::c2f($boilingC));
$this->assertEquals($boilingC, TempConverter::f2c($boilingF));
}
public function testFortyBelow() {
$fortyBelow = -40;
$this->assertEquals($fortyBelow, TempConverter::c2f($fortyBelow));
$this->assertEquals($fortyBelow, TempConverter::f2c($fortyBelow));
}
}
After a bit of shuffling I figured out the righrt formulas, and got all my tests passing.
As luck would have it, I had pretty good coverage with my test data. Not just a wide range of temperatures, but a wide variety of inputs as well. I’ve got positive numbers, negative numbers, zero, and even a result with identical numbers. But what could I do to improve?
My first thought was that I knew one other number for comparison, absolute zero, the temperature at which atoms stop moving: zero Kelvin or -273C. A quick google search gave the Fahrenheit number -459.67F So I added this test case:
public function testAbsoluteZero() {
$absoluteZeroF = -459.67;
$absoluteZeroC = -273;
$this->assertEquals($absoluteZeroF, TempConverter::c2f($absoluteZeroC));
$this->assertEquals($absoluteZeroC, TempConverter::c2f($absoluteZeroF));
}
When I ran again, I got this message:
There was 1 failure:
1) TempConverterTest::testAbsoluteZero
Failed asserting that <double:-459.4> matches expected <double:-459.67>.
After a little head scratching (and a bit more googling) I learned that they’ve changed the bar, and 0 Kelvin is now precisely -273.15C. I fixed the test and ran it again, only to get this puzzling answer:
There was 1 failure:
1) TempConverterTest::testAbsoluteZero
Failed asserting that <double:-459.67> matches expected <double:-459.67>.
Now that was some odd behavior. Obviously my code was working, and I was getting the right result, but something was amiss. I tried this, which passed:
$this->assertEquals($absoluteZeroF, round(TempConverter::c2f($absoluteZeroC), 2));
Clearly it was either a rounding in PHP or something wrong with float comparisons in PHPUnit.
But that’s not what I wanted to talk about. I wanted to discuss how to structure tests. Let’s comment that out for now.
A common way of writing unit tests is to test each method in the system under test with one test method. I could have written something like this:
public function testC2F() {
$standardF = 59;
$standardC = 15;
$freezingF = 32;
$freezingC = 0;
$boilingF = 212;
$boilingC = 100;
$fortyBelow = -40;
$absoluteZeroF = -459.67;
$absoluteZeroC = -273.15;
$this->assertEquals($standardF, TempConverter::c2f($standardC));
$this->assertEquals($freezingF, TempConverter::c2f($freezingC));
$this->assertEquals($boilingF, TempConverter::c2f($boilingC));
$this->assertEquals($fortyBelow, TempConverter::c2f($fortyBelow));
}
public function testF2C() {
$standardF = 59;
$standardC = 15;
$freezingF = 32;
$freezingC = 0;
$boilingF = 212;
$boilingC = 100;
$fortyBelow = -40;
$absoluteZeroF = -459.67;
$absoluteZeroC = -273.15;
$this->assertEquals($standardC, TempConverter::f2c($standardF));
$this->assertEquals($freezingC, TempConverter::f2c($freezingF));
$this->assertEquals($boilingC, TempConverter::f2c($boilingF));
$this->assertEquals($fortyBelow, TempConverter::f2c($fortyBelow));
}
This works reasonably well besides the duplication of test data (which could be solved with class constants), but the point I’m trying to make is that you can test a scenario better the other way. Then your test function describes your test scenario.
It could easily be refactored into a data driven test with a comment denoting the scenario, and an allowance for rounding errors:
/**
* @dataProvider knownTemperatures
*/
public function testDataDrivenConversion($f, $c, $scenario) {
$digits = 7;
$this->assertEquals(round($c, $digits), round(TempConverter::f2c($f), $digits), $scenario);
$this->assertEquals(round($f, $digits), round(TempConverter::c2f($c), $digits), $scenario);
}
public function knownTemperatures() {
$tempuratures = array(
array(59, 15, 'compare standard temperatures at standard pressure'),
array(32, 0, 'compare the freezing point of water'),
array(212, 100, 'compare the boiling point of water'),
array(-40, -40, 'compare forty below zero (should be the same for both)'),
array(-459.67, -273.15, 'compare absolute zero'),
array(6, -14.44444444, 'compare a random number'),
);
return $tempuratures;
}
The source code is available at:
http://one-shore.com/aaron/TempConverter.zip
Like this:
Like Loading...