Tests are red:
function testCoreIdentityMappingReturnsInputAtDefaultOffset () { $rotor = new Enigma_Rotor(); 'A'=>'A','B'=>'B','C'=>'C','D'=>'D','E'=>'E','F'=>'F','G'=>'G', 'H'=>'H','I'=>'I','J'=>'J','K'=>'K','L'=>'L','M'=>'M','N'=>'N', 'O'=>'O','P'=>'P','Q'=>'Q','R'=>'R','S'=>'S','T'=>'T','U'=>'U', 'V'=>'V','W'=>'W','X'=>'X','Y'=>'Y','Z'=>'Z' ); $offset=1; //default $rotor->setRingOffset($offset); $rotor->setCoreMapping($coreMapping); $testCharacter='V'; $this->assertSame( $testCharacter, $rotor->getOutputCharacterForInputCharacter($testCharacter) ); }
Fatal error: Call to undefined method Enigma_Rotor::getOutputCharacterForInputCharacter() in Enigma/RotorTest.php on line 109This is the function we specified in our Enigma_Encryptor_Interface, but haven't yet implemented because we didn't need it.
Our first rotor's independent of offset, so we ignore that consideration in our code for now. Let's also simulate a slightly more complex rotor, with the mapping:
public function getOutputCharacterForInputCharacter($inputCharacter) { return($this->_coreMapping[$inputCharacter]); }
'A'=>'N','B'=>'O','C'=>'P','D'=>'Q','E'=>'R','F'=>'S','G'=>'T', 'H'=>'U','I'=>'V','J'=>'W','K'=>'X','L'=>'Y','M'=>'Z','N'=>'A', 'O'=>'B','P'=>'C','Q'=>'D','R'=>'E','S'=>'F','T'=>'G','U'=>'H', 'V'=>'I','W'=>'J','X'=>'K','Y'=>'L','Z'=>'M'and test that:
It's green; and as noted it's the widely-used ROT-13 cypher.
function testRot13Mapping () { $rotor = new Enigma_Rotor(); 'A'=>'N','B'=>'O','C'=>'P','D'=>'Q','E'=>'R','F'=>'S','G'=>'T', 'H'=>'U','I'=>'V','J'=>'W','K'=>'X','L'=>'Y','M'=>'Z','N'=>'A', 'O'=>'B','P'=>'C','Q'=>'D','R'=>'E','S'=>'F','T'=>'G','U'=>'H', 'V'=>'I','W'=>'J','X'=>'K','Y'=>'L','Z'=>'M' ); $offset=1; $rotor->setRingOffset($offset); $rotor->setCoreMapping($coreMapping); $testInputCharacter='V'; $testOutputCharacter='I'; $this->assertSame( $testOutputCharacter, $rotor->getOutputCharacterForInputCharacter($testInputCharacter) ); }
'A'=>'E','B'=>'K','C'=>'M','D'=>'F','E'=>'L','F'=>'G','G'=>'D', 'H'=>'Q','I'=>'V','J'=>'Z','K'=>'N','L'=>'T','M'=>'O','N'=>'W', 'O'=>'Y','P'=>'H','Q'=>'X','R'=>'U','S'=>'S','T'=>'P', 'U'=>'A','V'=>'I','W'=>'B','X'=>'R','Y'=>'C','Z'=>'J'Unlike previous rotors, this one will deliver different results depending on the Ringstellung, so we want to add that as a parameter as well as the ring input and output. We should test a few combinations of input and offset, which we'll need to work out manually to start with.



Of course, we haven't actually written code to handle offsets yet, so the tests are red:
/** * @dataProvider rotorIDataProvider */ function testRotorIMappingOffset ($offset,$testInputCharacter,$testOutputCharacter) { $rotor = new Enigma_Rotor(); 'A'=>'E','B'=>'K','C'=>'M','D'=>'F','E'=>'L','F'=>'G','G'=>'D', 'H'=>'Q','I'=>'V','J'=>'Z','K'=>'N','L'=>'T','M'=>'O','N'=>'W', 'O'=>'Y','P'=>'H','Q'=>'X','R'=>'U','S'=>'S','T'=>'P', 'U'=>'A','V'=>'I','W'=>'B','X'=>'R','Y'=>'C','Z'=>'J' ); $rotor->setRingOffset($offset); $rotor->setCoreMapping($coreMapping); $this->assertSame($testOutputCharacter, $rotor->getOutputCharacterForInputCharacter($testInputCharacter)); } function rotorIDataProvider() { ); }
silverbox:Enigma wechsler$ phpunit RotorTest.php PHPUnit 3.4.5 by Sebastian Bergmann. ..............FF Time: 1 second, Memory: 5.75Mb There were 2 failures: 1) Enigma_RotorTest::testRotorIMappingOffset with data set #1 (2, 'V', 'B') --- Expected +++ Actual @@ @@ -B +I /Users/wechsler/tdd-enigma/tests/Enigma/RotorTest.php:151 2) Enigma_RotorTest::testRotorIMappingOffset with data set #2 (16, 'G', 'J') --- Expected +++ Actual @@ @@ -J +D /Users/wechsler/tdd-enigma/tests/Enigma/RotorTest.php:151 FAILURES! Tests: 16, Assertions: 16, Failures: 2.We can handle the offsets by revising the getOutputCharacterForInputCharacter function in Rotor.php:
We've unwrapped this into a few subfunctions for clarity, but essentially the flow is:
public function getOutputCharacterForInputCharacter ($inputCharacter) { $coreInputCharacter = $this->_getCharacterOffsetBy( $inputCharacter, $this->_ringOffset-1 ); $coreOutputCharacter = $this->_coreMapping[$coreInputCharacter]; $outputCharacter = $this->_getCharacterOffsetBy( $coreOutputCharacter, 0 - ($this->_ringOffset-1) ); // $outputCharacter= $this->_coreMapping[$inputCharacter]; return ($outputCharacter); } private function _getCharacterOffsetBy ($character, $offset) { $charAsInt = $this->_charToAlphabetPosition($character); $newInteger = (26 + $charAsInt - $offset) % 26; if($newInteger==0) { $newInteger=26; } $newCharacter = $this-> _alphabetPositionToCharacter($newInteger); return ($newCharacter); } private function _charToAlphabetPosition ($char) { return $position; } private function _alphabetPositionToCharacter ($position) { return $char; }
silverbox:tdd-enigma wechsler$ svn status M tests/Enigma/RotorTest.php M library/Enigma/Rotor.php silverbox:tdd-enigma wechsler$ svn commit -m 'Code can now handle ring offset' Sending library/Enigma/Rotor.php Sending tests/Enigma/RotorTest.php Transmitting file data .. Committed revision 5.We’ve added more code than in previous steps here, but it’s all been in order to fix some already-existing tests. We’ve added no new functionality beyond that, and all the functions we’ve added are private, so there are no new calls that can be made into the Rotor. We don’t need to test all these functions as they’re encapsulated within the “unit” of functionality we’re already testing, and can never be used as a distinct unit themselves.
We’ve already seen one way to expect an exception in testing, but we can also use a comment notification as for the dataprovider above. As we want to check a few failure cases, we’ll also use a data provider:
throw new Exception ("Bad Character '$char'"); }
Note that we test for invalid data in the offset calculation, but have to send the input to getOutputCharacterForInputCharacter() as the closest external function. It’s not ideal but it still performs the testing we need.
/** * Try and encrypt something invalid * @dataProvider badCharacterIDataProvider * @expectedException Exception */ function testEncryptBadCharacter($characters) { $rotor = new Enigma_Rotor(); 'A'=>'E','B'=>'K','C'=>'M','D'=>'F','E'=>'L','F'=>'G','G'=>'D', 'H'=>'Q','I'=>'V','J'=>'Z','K'=>'N','L'=>'T','M'=>'O','N'=>'W', 'O'=>'Y','P'=>'H','Q'=>'X','R'=>'U','S'=>'S','T'=>'P','U'=>'A', 'V'=>'I','W'=>'B','X'=>'R','Y'=>'C','Z'=>'J' ); $offset=0; $rotor->setRingOffset($offset); $spaceTranslated= $rotor->getOutputCharacterForInputCharacter($characters); } function badCharacterIDataProvider() { }
Note that we also need to remove the last line from our ringOffsetBoundariesDataProvider;
public function setRingOffset ($offset) { $offset=$this->_charToAlphabetPosition($offset); throw new Exception("Offset must be integer in range 1..26"); } $this->_ringOffset = $offset; }
And that’s about it for the Rotor. It’s taken us a while to get here as we’ve examined our code very closely and introduced quite a few new tricks, but we’ve got some code for this system element that we can be very confident in.
function ringOffsetBoundariesDataProvider() { // array('A',false) // No longer invalid! ); }
All content copyright Richard George (richard@phase.org), 2009-2010
Sponsored links to recommended books: