Last active
November 1, 2016 19:41
-
-
Save foozlereducer/f5bbbae2737bb96316696563c1c7d66f to your computer and use it in GitHub Desktop.
Unit testing training files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| TDD Benefits: | |
| • Studies have found that using TDD leads to greater developer productivity | |
| • Rarely debugging is required; roll back to last stable version; or directly zero in to the bug | |
| • TDD drives the design of your code. Test first focus on how code will be used. Interfaces are designed before implementation which is beneficial to design. | |
| • TDD enables small steps without exceptional cases or error handling being initially considered. Instead dedicated tests flush out this behaviour. | |
| • Even though TDD creates more code than not testing, studies support that time taken for development can shorten. | |
| • Full test coverage helps limit defects becoming expensive problems later. Eliminating defects early in the process usually avoids lengthy, costly and tedious debugging later in the project. | |
| • TDD often leads to more modular, flexible and extensible code. This effect comes about as TDD guides developers to think of small units that can be tested independently. This leads to smaller classes, looser coupling and cleaner interfaces. | |
| • Extraneous ‘maybe we need’ code is eliminated. Only code that passes the tests are implemented. | |
| • Full test coverage will demonstrate unintended effects of later code changes. | |
| • TDD encourages refactoring code. Refactoring code generally improves code quality: reduce duplication, relocate code, reduce complexity of or eliminate code. | |
| • Madeyski (Madeyski, L. "Test-Driven Development - An Empirical Evaluation of Agile Practice", Springer, 2010, ISBN 978-3-642-04287-4, pp. 1-245. DOI: 978-3-642-04288-1) provides empirical evidence that TDD produces lower coupling between code than the Test Last Approach. This suggests that TDD creates better modularity which benefits long lived software like Postmedia Plugins and Themes. | |
| Limitations: | |
| • TDD / Unit tests are not good for user interface testing; too many tests that tend to be fragile need to be created. | |
| • Management support is essential. Without it TDD and unit testing will fail. | |
| • Unit Tests that do not test the entire public interfaces (inputs) can lead to ‘blind spots’. | |
| • A high pass ratio can lead to over security; QA (user-testing) and Performance testing should not be skipped. | |
| • Tests become part of maintenance overhead. Badly written tests are expensive to maintain | |
| • Writing excessive number of tests can cost time and provide little benefit, well written initial tests may accept new requirements without the need for additional tests. | |
| • As we do not change tests, initial tests become more precious and new functionality is not easy to replicate in later TDD cycles. Fixing bad early tests / design early become ever important. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Postmedia\Web\Utilities; | |
| /** | |
| * Is Device - Adapter classes for JetPack device handling | |
| * @author Steve Browning | |
| * @version 1.0 - basic wrappers with jetpack exists checks | |
| */ | |
| class Device { | |
| /** | |
| * Jetpack User Agent class, only instatiated for unit test | |
| * | |
| * @var object | |
| */ | |
| private $juai; | |
| /** | |
| * Device Constructor | |
| */ | |
| public function __construct() {} | |
| /** | |
| * Is Mobile returns true if device is a mobile | |
| * | |
| * @return boolean | |
| */ | |
| public function is_mobile() { | |
| if ( function_exists( 'jetpack_is_mobile' ) ) { | |
| if ( $this->valid_user_agent( $this->get_user_agent() ) ) { | |
| return jetpack_is_mobile(); | |
| } | |
| } else if ( function_exists( 'wp_is_mobile' ) ) { | |
| return wp_is_mobile(); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Is Tablet runs and returns the tablet check | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @return boolean | |
| */ | |
| public function is_tablet() { | |
| if ( function_exists( 'jetpack_is_mobile' ) ) { | |
| if ( $this->valid_user_agent( $this->get_user_agent() ) ) { | |
| return $this->tablet_check(); | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Unit Test Setting of the JetPack_User_Agent_Info class | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @param object instance of the jetpack user agent class need to be set for unit testing only | |
| */ | |
| public function unit_test_set_jetpack_user_agent_info( \Jetpack_User_Agent_Info $juai ) { | |
| $this->juai = $juai; | |
| } | |
| /** | |
| * Valid User Agent check to ensure that user agent is a valid type | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @param string $ua user agent | |
| * @return boolean | |
| */ | |
| private function valid_user_agent( $ua ) { | |
| if ( is_string( $ua ) ) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Set User Agent primary used for unit testing, normally a device will provide a user agent string | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @param string $ua user agent | |
| * @return void | |
| */ | |
| public function set_user_agent( $ua ) { | |
| if ( $this->valid_user_agent( $ua ) ) { | |
| if ( ! empty( $ua ) ) { | |
| // Filter out any bad scoobies in the user agent string | |
| $_SERVER['HTTP_USER_AGENT'] = filter_var( $ua, FILTER_SANITIZE_STRING ); | |
| } | |
| } else { | |
| $_SERVER['HTTP_USER_AGENT'] = null; | |
| } | |
| } | |
| /** | |
| * Get User Agent will return the current server user agent | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @return void | |
| */ | |
| public function get_user_agent() { | |
| if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) { | |
| return filter_var( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), FILTER_SANITIZE_STRING ); | |
| } else { | |
| $_SERVER['HTTP_USER_AGENT'] = null; | |
| } | |
| } | |
| /** | |
| * Tablet Check regex logic and Jetpack_User_Agent_Info::is_tablet() check. The $juai property is only set for the integration ( unit ) tests | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @return boolean | |
| */ | |
| private function tablet_check() { | |
| // jetpack_is_mobile will return FALSE for tablets; so we first check if it is false, then we run it | |
| // against the is_tablet() method which calls internal tablet methods; if this returns tablet = true | |
| // then we return tablet. As a final fail safe there are many generic tablet user_agents that are returned | |
| // as mobiles or desktops, so the basic preg_match checks for a match of 'tablet' in the user agent string | |
| // if it finds it then it will return 'tablet' | |
| if ( ! empty( $this->juai ) ) { | |
| if ( | |
| true == $this->juai->is_tablet() || | |
| preg_match( '/tablet/i', $this->get_user_agent() ) | |
| ) { | |
| return true; | |
| } | |
| } else if ( class_exists( 'Jetpack_User_Agent_Info' ) ) { | |
| if ( | |
| true == \Jetpack_User_Agent_Info::is_tablet() || | |
| preg_match( '/tablet/i', $this->get_user_agent() ) | |
| ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Type - will return the device type ( as string ) running the current page. | |
| * | |
| * @author Steve Browning | |
| * @since 1.0 | |
| * @return string the device type; desktop, mobile or tablet | |
| */ | |
| public function type() { | |
| if ( function_exists( 'jetpack_is_mobile' ) ) { | |
| if ( $this->valid_user_agent( $this->get_user_agent() ) ) { | |
| if ( $this->is_tablet() ) { | |
| return 'tablet'; | |
| } | |
| if ( $this->is_mobile() ) { | |
| return 'mobile'; | |
| } | |
| // When reaching here, if jetpack_is_mobile returns FALSE and it is not a tablet then it is a desktop | |
| // So return 'desktop' | |
| return 'desktop'; | |
| } | |
| } | |
| return 'mobile'; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Postmedia; | |
| class Heinz57Trunk { | |
| private $path; | |
| private $url; | |
| private $taxonomy = 'postmedia-plugin-heinz-57'; | |
| /** | |
| * Constructor | |
| */ | |
| public function __construct( Paths $paths ) { | |
| $this->path = $paths->path(); | |
| $this->url = $paths->url(); | |
| } | |
| public function initialize() { | |
| add_action( 'plugins_loaded', array( $this, 'setup' ) ); | |
| add_action( 'init', array( $this, 'init' ) ); | |
| add_action( 'wp_ajax_heinz_secret_formula', array( $this, 'heinz_secret_formula') ); | |
| } | |
| public function setup() { | |
| //add_action( 'admin_menu', array( $this, 'set_main_menu' ) ); | |
| } | |
| public function init() {} | |
| /** | |
| * Ajax function for the Heinze Secret Formula tested in test-ajax.php | |
| */ | |
| public function heinz_secret_formula() { | |
| $response = array( 'Secret Forumla Revealed' ); | |
| echo wp_json_encode( $response ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // Add this class to a PHPUnitUtilities.php file inside your tests folder i.e. your_plugin/tests/PHPUnitUtilities.php | |
| // Configure this in your test like: | |
| /** | |
| require_once( dirname(__FILE__) . '/../../tests/WP-API-2.0-beta13/plugin.php'); | |
| class ApiPostTest extends WP_UnitTestCase { | |
| ... | |
| } | |
| */ | |
| class PHPUnitUtilities { | |
| /** | |
| * Helper to call private methods by making them public | |
| * @param [type] $obj [description] | |
| * @param [type] $name [description] | |
| * @param array $args [description] | |
| * @return [type] [description] | |
| */ | |
| public static function callPrivateMethod( $obj, $name, array $args ) { | |
| $class = new \ReflectionClass( $obj ); | |
| $method = $class->getMethod( $name ); | |
| $method->setAccessible( true ); | |
| return $method->invokeArgs( $obj, $args ); | |
| } | |
| } | |
| // Usage in a test: | |
| function test_private_invalid_uri_returns_empty() { | |
| $uri = 'http:/\/theprovincewpdqa5canada%<script>bad_js_func();</script>.com/'; | |
| $filtered_uri = PHPUnitUtilities::callPrivateMethod( $this->ac, 'validate_uri', array( $uri ) ); | |
| $this->assertEmpty( $filtered_uri ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| require_once('./classes/Postmedia/Heinz57Trunk.php'); | |
| require_once('./classes/Postmedia/Paths.php'); | |
| class AjaxTests extends WP_Ajax_UnitTestCase { | |
| public $paths; | |
| function setUp() { | |
| // The WP_Ajax_UnitTestCase library does not allow for mocking; therefore the AJAX tests will | |
| // always be integration tests where dependencies will need to be provided and not mocked. | |
| // This is the case for the Paths class. | |
| /* // calling this allows hooks into the base WordPress classes such as the WP_User object. | |
| parent::setUp(); | |
| $this->paths = $this->getMock( 'Postmedia\Paths', array( 'path', 'url' ) ); | |
| $this->paths->expects( $this->any() )->method('path') | |
| ->will( $this->returnValue('/my/path/postmedia-plugins/postmedia-plugin-heinz-57/')); | |
| $this->paths->expects( $this->any() )->method('url') | |
| ->will( $this->returnValue('http://localhost/postmedia-plugins/postmedia-plugin-heinz-57') );*/ | |
| } | |
| function test_reveal_secret_formula_via_ajax() { | |
| $ht = new Postmedia\Heinz57Trunk( new Postmedia\Paths() ); | |
| try { | |
| add_action( 'wp_ajax_heinz_secret_formula', array( $ht, 'heinz_secret_formula' ) ); | |
| $this->_handleAjax( 'heinz_secret_formula' ); | |
| } catch ( WPAjaxDieStopException $e ) { | |
| // We expected this, do nothing. | |
| } | |
| $response = implode( ',', json_decode( $this->_last_response ) ); | |
| $this->assertContains( 'Secret Forumla Revealed', $response ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| require_once( dirname(__FILE__) . '/../../classes/Postmedia/Web/Utilities/Device.php'); | |
| class DeviceTest extends WP_UnitTestCase { | |
| public $Device; | |
| function setUp() { | |
| #setup code | |
| parent::setUp(); | |
| require_once( dirname(__FILE__) . '/class.jetpack-user-agent.php' ); | |
| $this->Device = new Postmedia\Web\Utilities\Device(); | |
| $this->Device->unit_test_set_jetpack_user_agent_info( new Jetpack_User_Agent_Info() ); | |
| } | |
| /** | |
| * Create the data set for the data provider | |
| * Inner array pairs are user agent ( $ua ) and $expected value | |
| * @return collections array | |
| */ | |
| function mobile_user_agent_data_provider() { | |
| return array( | |
| array( 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3', 'mobile' ), | |
| array( 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4', 'mobile' ), | |
| array( 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3', 'mobile' ), | |
| array( 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36', 'mobile' ) | |
| ); | |
| } | |
| /** | |
| * Create the data set for the data provider | |
| * Inner array pairs are user agent ( $ua ) and $expected value | |
| * @return collections array | |
| */ | |
| function is_mobile_user_agent_data_provider() { | |
| return array( | |
| array( 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3', true ), | |
| array( 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4', true ), | |
| array( 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3', true ), | |
| array( 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36', true ) | |
| ); | |
| } | |
| /** | |
| * Create the data set for the data provider | |
| * Inner array pairs are user agent ( $ua ) and $expected value | |
| * @return collections array | |
| */ | |
| function is_tablet_user_agent_data_provider() { | |
| return array( | |
| array( 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3; Tablet PC 2.0)', true ), | |
| array( 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1', true ), | |
| array( 'Mozilla/5.0 (Linux; U; Android 4.2.2; nl-nl; GT-P5210 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', true ), | |
| array( 'Mozilla/5.0 (Linux; U; Android 4.2.2; en-gb; SM-T311 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', true ), | |
| array( 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10', true ), | |
| array( 'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X; en-us) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3', true ) | |
| ); | |
| } | |
| /** | |
| * Create the data set for the data provider | |
| * Inner array pairs are user agent ( $ua ) and $expected value | |
| * @return collections array | |
| */ | |
| function tablet_user_agent_data_provider() { | |
| return array( | |
| array( 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3; Tablet PC 2.0)', 'tablet' ), | |
| array( 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1', 'tablet' ), | |
| array( 'Mozilla/5.0 (Linux; U; Android 4.2.2; nl-nl; GT-P5210 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', 'tablet' ), | |
| array( 'Mozilla/5.0 (Linux; U; Android 4.2.2; en-gb; SM-T311 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', 'tablet' ), | |
| array( 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10', 'tablet' ), | |
| array( 'Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X; en-us) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3', 'tablet' ) | |
| ); | |
| } | |
| /** | |
| * @dataProvider mobile_user_agent_data_provider | |
| * pass the $ua and $expected values from the data providers data set | |
| */ | |
| function testGetDeviceTypeMobile( $ua, $expected ) { | |
| // mobile user agent | |
| $this->Device->set_user_agent( $ua ); | |
| $this->assertEquals( $expected, $this->Device->type() ); | |
| } | |
| /** | |
| * @dataProvider is_mobile_user_agent_data_provider | |
| * pass the $ua and $expected values from the data providers data set | |
| */ | |
| function testIsMobile( $ua, $expected ) { | |
| // mobile user agent | |
| $this->Device->set_user_agent( $ua ); | |
| $this->assertEquals( $expected, $this->Device->is_mobile() ); | |
| } | |
| /** | |
| * @dataProvider tablet_user_agent_data_provider | |
| * pass the $ua and $expected values from the data providers data set | |
| */ | |
| function testGetDeviceTypeTablet( $ua, $expected ) { | |
| // tablet user agent | |
| $this->Device->set_user_agent( $ua ); | |
| $this->assertEquals( $expected, $this->Device->type() ); | |
| } | |
| /** | |
| * @dataProvider is_tablet_user_agent_data_provider | |
| * pass the $ua and $expected values from the data providers data set | |
| */ | |
| function testIsTablet( $ua, $expected ) { | |
| // tablet user agent | |
| $this->Device->set_user_agent( $ua ); | |
| $this->assertEquals( $expected, $this->Device->is_tablet() ); | |
| } | |
| /** | |
| * Create the data set for the data provider | |
| * Inner array pairs are bad user agent and $expected value | |
| * @return collections array | |
| */ | |
| function bad_user_agent_data_provider() { | |
| return array( | |
| array( 12345, 'mobile' ), | |
| array( -12345, 'mobile' ), | |
| array( true, 'mobile' ), | |
| array( null, 'mobile' ), | |
| array( '', 'mobile' ), | |
| array( "<IMG SRC=jAvascript:alert('test2')>", 'mobile' ), | |
| array( "<SCRIPT type='text/javascript'>var adr = '../evil.php?cakemonster=' + escape(document.cookie);</SCRIPT>", 'mobile' ) | |
| ); | |
| } | |
| /** | |
| * @dataProvider bad_user_agent_data_provider | |
| * pass the $ua and $expected values from the data providers data set | |
| */ | |
| function testBadUserAgents( $bad_ua, $expected ) { | |
| // tablet user agent | |
| $this->Device->set_user_agent( $bad_ua ); | |
| $this->assertEquals( $expected, $this->Device->type() ); | |
| } | |
| function test_get_user_agent_return_correct_user_agent() { | |
| $this->Device->set_user_agent( 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1' ); | |
| $this->assertEquals( | |
| 'Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1', | |
| $this->Device->get_user_agent() | |
| ); | |
| } | |
| function test_get_bad_user_agent_returns_null() { | |
| $this->Device->set_user_agent( 1.567 ); | |
| $this->assertEquals( $this->Device->get_user_agent(), null ); | |
| } | |
| function tearDown() { | |
| # tear down code | |
| parent::tearDown(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Postmedia; | |
| class UnitTestsAre { | |
| public $phrases; | |
| public function __construct() { | |
| $this->phrases = array(); | |
| } | |
| // Add Phrase | |
| public function add_phrase( $phrase ) { | |
| return false; | |
| } | |
| } | |
| // Step 2 -- passes | |
| // Simpilest way to have add phrase pass | |
| class UnitTestsAre { | |
| public $phrases; | |
| public function __construct() { | |
| $this->phrases = array(); | |
| } | |
| // Add Phrase | |
| public function add_phrase( $phrase ) { | |
| return true; | |
| } | |
| } | |
| // Step 3 -- passes | |
| // Refactoring add_prase with dynamic code; the | |
| // test should still pass | |
| class UnitTestsAre { | |
| public $phrases; | |
| public function __construct() { | |
| $this->phrases = array(); | |
| } | |
| // Add Phrase | |
| public function add_phrase( $phrase ) { | |
| if ( ! is_array( $this->phrases ) ) { | |
| $this->phrases = array(); | |
| } | |
| if ( ! in_array( $phrase, $this->phrases ) ) { | |
| $this->phrases[] = $phrase; | |
| return true; | |
| } | |
| } | |
| } | |
| // Step 4 -- fails | |
| // The $phrase variable is not validated in the last step, | |
| // We started by creating tests for validate_phrase(); now | |
| // get the tests to fail. | |
| class UnitTestsAre { | |
| public $phrases; | |
| public function __construct() { | |
| $this->phrases = array(); | |
| } | |
| // Add Phrase | |
| public function add_phrase( $phrase ) { | |
| if ( ! is_array( $this->phrases ) ) { | |
| $this->phrases = array(); | |
| } | |
| if ( ! in_array( $phrase, $this->phrases ) ) { | |
| $this->phrases[] = $phrase; | |
| return true; | |
| } | |
| } | |
| public function validate_phrase( $phrase, $min_word_count = 4 ) { | |
| return ''; | |
| } | |
| } | |
| // Step 5 -- passes | |
| // The most basic code to get validate_phrase() to pass. | |
| public function validate_phrase( $phrase, $min_word_count = 4 ) { | |
| return 'Unit tests are fantastic'; | |
| } | |
| // Step 6 -- passes | |
| // Refactor validate_phrase; the pass should still pass. | |
| public function validate_phrase( $phrase, $min_word_count = 4, $default_phrase = '' ) { | |
| if ( empty( $default_phrase ) || ! is_string( $default_phrase ) ) { | |
| $default_phrase = 'Unit tests are fantastic'; | |
| } | |
| // Ensure min word count is integer | |
| if ( ! is_int( $min_word_count ) ) { | |
| $min_word_count = 4; | |
| } | |
| if ( ! is_string( $phrase ) ) { | |
| return $default_phrase; | |
| } | |
| if ( $min_word_count > str_word_count( $phrase, 0 ) ) { | |
| return $default_phrase; | |
| } | |
| return $phrase; | |
| } | |
| // Step 7 -- passes | |
| // Refactor by wrapping validate_phrase() around | |
| // the unvalidated $phrase in add_phrase. | |
| // Ensure that the add_phrase tests still work | |
| public function add_phrase( $phrase ) { | |
| if ( ! is_array( $this->phrases ) ) { | |
| $this->phrases = array(); | |
| } | |
| if ( ! in_array( $phrase, $this->phrases ) ) { | |
| $this->phrases[] = $this->validate_phrase( $phrase ); | |
| return true; | |
| } | |
| } | |
| // Step 8 -- fails | |
| // We've added a render_phrase() test that we first fail. | |
| public function render_phrase() { | |
| print(''); | |
| } | |
| // Step 9 -- passes | |
| // The most basic code to get the render_phrase tests to pass | |
| public function render_phrase() { | |
| print('Unit tests are okay'); | |
| } | |
| // Step 10 -- passes | |
| // refactored render_phrase() -- | |
| public function render_phrase() { | |
| if ( ! empty( $this->phrases ) ) { | |
| print( esc_attr( $this->phrases[0] ) ); | |
| } | |
| } | |
| // Step 11 -- fails | |
| // Fail the tests that has been created for set_seen_phrases(). | |
| public function set_seen_phrases() { | |
| $this->seen_phrases = array( 'a', 'b', 'c', ); | |
| return $this->seen_phrases; | |
| } | |
| // Step 12 -- passes | |
| // The most basic code to pass the set_seen_phrases() tests. | |
| public function set_seen_phrases() { | |
| $this->seen_phrases = array( | |
| 'Unit tests are bad', | |
| 'Unit tests are okay', | |
| 'Unit tests are fantastic', | |
| ); | |
| return $this->seen_phrases; | |
| } | |
| // Step 13 -- passes | |
| // refactor render_phrase(); ensure tests render tests still pass. | |
| // refactor set_seen_phrases. The tests should still pass | |
| public function render_phrase() { | |
| if ( ! empty( $this->phrases ) ) { | |
| print( esc_attr( $this->phrases[0] ) ); | |
| } | |
| $this->phrases = $this->set_seen_phrases(); | |
| } | |
| public function set_seen_phrases( $phrases = '' ) { | |
| // allows for unit testing | |
| if ( is_array( $phrases ) ) { | |
| $this->phrases = $phrases; | |
| } | |
| if ( ! empty( $this->phrases ) ) { | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| $this->seen_phrases[] = array_pop( $this->phrases ); | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| return $this->phrases; | |
| } else { | |
| return $this->seen_phrases; | |
| } | |
| } | |
| // Step 14 -- fails | |
| // Added the randomize_phrases() tests. | |
| // Most basic code to fail the test. | |
| public function randomize_phrases( $phrases = '' ) { | |
| return $phrases; | |
| } | |
| // Step 15 -- passes | |
| // The most basic code to pass the test | |
| public function randomize_phrases( $phrases = '' ) { | |
| return shuffle( $phrases ); | |
| } | |
| // Step 16 -- passes | |
| // Refactor the randomize_phrases code to include the phrases property | |
| // The test should still pass | |
| public function randomize_phrases( $phrases = '' ) { | |
| if ( is_array( $phrases ) ) { | |
| $this->seen_phrases = $phrases; | |
| } | |
| return shuffle( $this->seen_phrases ); | |
| } | |
| - | |
| // Step 17 -- passes | |
| // Continue passing set_seen_phrases() tests and | |
| // wrap the randomize_phrases() around $this->set_seen_phrases output | |
| public function set_seen_phrases( $phrases = '' ) { | |
| // allows for unit testing | |
| if ( is_array( $phrases ) ) { | |
| $this->phrases = $phrases; | |
| } | |
| if ( ! empty( $this->phrases ) ) { | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| $this->seen_phrases[] = array_pop( $this->phrases ); | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| return $this->phrases; | |
| } else { | |
| return $this->randomize_phrases(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // Test 1 | |
| require_once('./classes/Postmedia/UnitTestsAre.php'); | |
| class UnitTestsAreTests extends WP_UnitTestCase { | |
| function setUp() { | |
| // calling this allows hooks into the base WordPress classes such as the WP_User object. | |
| parent::setUp(); | |
| } | |
| function test_calling_add_phrase_adds_the_phrase() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrase = 'Unit tests are just okay'; | |
| $this->assertTrue( $uta->add_phrase( $phrase ) ); | |
| } | |
| function tearDown(){} | |
| } | |
| // Test 2 | |
| function test_phrases_validate_default_returned_when_too_few_words() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrase = 'Unit tests bad'; | |
| $this->assertEquals( $uta->validate_phrase( $phrase, $min_word_count = 4 ), 'Unit tests are fantastic' ); | |
| } | |
| function test_validate_non_string_phrase_returns_default_phrase() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrase = 987654321; | |
| $this->assertEquals( $uta->validate_phrase( $phrase, $min_word_count = 4 ), 'Unit tests are fantastic' ); | |
| } | |
| // Test 3 | |
| function test_render_phrase_outputs_single_phrase() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrase = 'Unit tests are okay'; | |
| $uta->add_phrase( $phrase ); | |
| ob_start(); | |
| $uta->render_phrase(); | |
| $phrase = ob_get_clean(); | |
| $this->assertEquals( $phrase, 'Unit tests are okay' ); | |
| } | |
| // Test 4 | |
| function test_set_seen_phrases_propogates_seen_phrase_array() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrases = array( | |
| 'Unit tests are bad', | |
| 'Unit tests are okay', | |
| 'Unit tests are fantastic' | |
| ); | |
| $phrases = $uta->set_seen_phrases( $phrases ); | |
| $this->assertEquals( 'Unit tests are bad', $uta->seen_phrases[0] ); | |
| $phrases = $uta->set_seen_phrases( $phrases ); | |
| $this->assertEquals( 'Unit tests are okay', $uta->seen_phrases[1] ); | |
| $phrases = $uta->set_seen_phrases( $phrases ); | |
| $this->assertEquals( 'Unit tests are fantastic', $uta->seen_phrases[2] ); | |
| } | |
| // Test 5 | |
| function test_after_all_phrases_rendered_seen_phrases_have_all_phrases() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $uta->add_phrase( 'Unit tests are bad' ); | |
| ob_start(); | |
| $uta->render_phrase(); | |
| ob_get_clean(); | |
| $this->assertEquals( 'Unit tests are bad', $uta->seen_phrases[0] ); | |
| $uta->add_phrase( 'Unit tests are okay' ); | |
| ob_start(); | |
| $uta->render_phrase(); | |
| ob_get_clean(); | |
| $this->assertEquals( 'Unit tests are okay', $uta->seen_phrases[1] ); | |
| $uta->add_phrase( 'Unit tests are fantastic' ); | |
| ob_start(); | |
| $uta->render_phrase(); | |
| ob_get_clean(); | |
| $this->assertEquals( 'Unit tests are fantastic', $uta->seen_phrases[2] ); | |
| } | |
| // Test 6 | |
| function test_randomize_phrases_randomizes_the_phrases() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrases = array( | |
| 'Unit tests are bad', | |
| 'Unit tests are okay', | |
| 'Unit tests are fantastic', | |
| 'Unit tests are good for loose coupling', | |
| 'Unit tests give confidence to deep refactor code', | |
| 'Good refactoring reduces duplication' | |
| ); | |
| $randomized_phrases = $uta->randomize_phrases( $phrases ); | |
| $this->assertNotEmpty( $randomized_phrases ); | |
| $this->assertNotEquals( $phrases, $randomized_phrases); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Postmedia; | |
| class UnitTestsAre { | |
| public $phrases; | |
| public function __construct() { | |
| $this->phrases = array(); | |
| } | |
| // Add Phrase | |
| public function add_phrase( $phrase ) { | |
| if ( ! is_array( $this->phrases ) ) { | |
| $this->phrases = array(); | |
| } | |
| if ( ! in_array( $phrase, $this->phrases ) ) { | |
| $this->phrases[] = $this->validate_phrase( $phrase ); | |
| return true; | |
| } | |
| } | |
| public function validate_phrase( $phrase, $min_word_count = 4, $default_phrase = '' ) { | |
| if ( empty( $default_phrase ) || ! is_string( $default_phrase ) ) { | |
| $default_phrase = 'Unit tests are fantastic'; | |
| } | |
| // Ensure min word count is integer | |
| if ( ! is_int( $min_word_count ) ) { | |
| $min_word_count = 4; | |
| } | |
| if ( ! is_string( $phrase ) ) { | |
| return $default_phrase; | |
| } | |
| if ( $min_word_count > str_word_count( $phrase, 0 ) ) { | |
| return $default_phrase; | |
| } | |
| return $phrase; | |
| } | |
| function test_render_phrase_outputs_single_phrase() { | |
| $uta = new Postmedia\UnitTestsAre(); | |
| $phrase = 'Unit tests are okay'; | |
| $uta->add_phrase( $phrase ); | |
| ob_start(); | |
| $uta->render_phrase(); | |
| $phrase = ob_get_clean(); | |
| $this->assertEquals( $phrase, 'Unit tests are okay' ); | |
| } | |
| public function render_phrase() { | |
| if ( ! empty( $this->phrases ) ) { | |
| print( esc_attr( $this->phrases[0] ) ); | |
| } | |
| $this->phrases = $this->set_seen_phrases(); | |
| } | |
| public function set_seen_phrases( $phrases = '' ) { | |
| // allows for unit testing | |
| if ( is_array( $phrases ) ) { | |
| $this->phrases = $phrases; | |
| } | |
| if ( ! empty( $this->phrases ) ) { | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| $this->seen_phrases[] = array_pop( $this->phrases ); | |
| if ( 1 < count( $this->phrases ) ) { | |
| $this->phrases = array_reverse( $this->phrases ); | |
| } | |
| return $this->phrases; | |
| } else { | |
| return $this->seen_phrases; | |
| } | |
| } | |
| public function randomize_phrases( $phrases = '' ) { | |
| if ( is_array( $phrases ) ) { | |
| $this->seen_phrases = $phrases; | |
| } | |
| return shuffle( $this->seen_phrases ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment