Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 16 | class EditCounterTest extends \PHPUnit_Framework_TestCase |
||
| 17 | { |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Get counts of revisions: deleted, not-deleted, and total. |
||
| 21 | */ |
||
| 22 | public function testLiveAndDeletedEdits() |
||
| 23 | { |
||
| 24 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 25 | $editCounterRepo->expects($this->once()) |
||
| 26 | ->method('getPairData') |
||
| 27 | ->willReturn([ |
||
| 28 | 'deleted' => 10, |
||
| 29 | 'live' => 100, |
||
| 30 | ]); |
||
| 31 | |||
| 32 | $project = new Project('TestProject'); |
||
| 33 | $user = new User('Testuser'); |
||
| 34 | $editCounter = new EditCounter($project, $user); |
||
| 35 | $editCounter->setRepository($editCounterRepo); |
||
| 36 | |||
| 37 | $this->assertEquals(100, $editCounter->countLiveRevisions()); |
||
| 38 | $this->assertEquals(10, $editCounter->countDeletedRevisions()); |
||
| 39 | $this->assertEquals(110, $editCounter->countAllRevisions()); |
||
| 40 | } |
||
| 41 | |||
| 42 | /** |
||
| 43 | * A first and last date, and number of days between. |
||
| 44 | */ |
||
| 45 | public function testDates() |
||
| 46 | { |
||
| 47 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 48 | $editCounterRepo->expects($this->once())->method('getPairData')->willReturn([ |
||
| 49 | 'first' => '20170510100000', |
||
| 50 | 'last' => '20170515150000', |
||
| 51 | ]); |
||
| 52 | $project = new Project('TestProject'); |
||
| 53 | $user = new User('Testuser1'); |
||
| 54 | $editCounter = new EditCounter($project, $user); |
||
| 55 | $editCounter->setRepository($editCounterRepo); |
||
| 56 | $this->assertEquals( |
||
| 57 | new \DateTime('2017-05-10 10:00'), |
||
| 58 | $editCounter->datetimeFirstRevision() |
||
| 59 | ); |
||
| 60 | $this->assertEquals( |
||
| 61 | new \DateTime('2017-05-15 15:00'), |
||
| 62 | $editCounter->datetimeLastRevision() |
||
| 63 | ); |
||
| 64 | $this->assertEquals(5, $editCounter->getDays()); |
||
| 65 | } |
||
| 66 | |||
| 67 | /** |
||
| 68 | * Only one edit means the dates will be the same. |
||
| 69 | */ |
||
| 70 | public function testDatesWithOneRevision() |
||
| 71 | { |
||
| 72 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 73 | $editCounterRepo->expects($this->once()) |
||
| 74 | ->method('getPairData') |
||
| 75 | ->willReturn([ |
||
| 76 | 'first' => '20170510110000', |
||
| 77 | 'last' => '20170510110000', |
||
| 78 | ]); |
||
| 79 | $project = new Project('TestProject'); |
||
| 80 | $user = new User('Testuser1'); |
||
| 81 | $editCounter = new EditCounter($project, $user); |
||
| 82 | $editCounter->setRepository($editCounterRepo); |
||
| 83 | $this->assertEquals( |
||
| 84 | new \DateTime('2017-05-10 11:00'), |
||
| 85 | $editCounter->datetimeFirstRevision() |
||
| 86 | ); |
||
| 87 | $this->assertEquals( |
||
| 88 | new \DateTime('2017-05-10 11:00'), |
||
| 89 | $editCounter->datetimeLastRevision() |
||
| 90 | ); |
||
| 91 | $this->assertEquals(1, $editCounter->getDays()); |
||
| 92 | } |
||
| 93 | |||
| 94 | /** |
||
| 95 | * Test that page counts are reported correctly. |
||
| 96 | */ |
||
| 97 | public function testPageCounts() |
||
| 98 | { |
||
| 99 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 100 | $editCounterRepo->expects($this->once()) |
||
| 101 | ->method('getPairData') |
||
| 102 | ->willReturn([ |
||
| 103 | 'edited-live' => '3', |
||
| 104 | 'edited-deleted' => '1', |
||
| 105 | 'created-live' => '6', |
||
| 106 | 'created-deleted' => '2', |
||
| 107 | ]); |
||
| 108 | $project = new Project('TestProject'); |
||
| 109 | $user = new User('Testuser1'); |
||
| 110 | $editCounter = new EditCounter($project, $user); |
||
| 111 | $editCounter->setRepository($editCounterRepo); |
||
| 112 | |||
| 113 | $this->assertEquals(3, $editCounter->countLivePagesEdited()); |
||
| 114 | $this->assertEquals(1, $editCounter->countDeletedPagesEdited()); |
||
| 115 | $this->assertEquals(4, $editCounter->countAllPagesEdited()); |
||
| 116 | |||
| 117 | $this->assertEquals(6, $editCounter->countCreatedPagesLive()); |
||
| 118 | $this->assertEquals(2, $editCounter->countPagesCreatedDeleted()); |
||
| 119 | $this->assertEquals(8, $editCounter->countPagesCreated()); |
||
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Test that namespace totals are reported correctly. |
||
| 124 | */ |
||
| 125 | public function testNamespaceTotals() |
||
| 126 | { |
||
| 127 | $namespaceTotals = [ |
||
| 128 | // Namespace IDs => Edit counts |
||
| 129 | '1' => '3', |
||
| 130 | '2' => '6', |
||
| 131 | '3' => '9', |
||
| 132 | '4' => '12', |
||
| 133 | ]; |
||
| 134 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 135 | $editCounterRepo->expects($this->once()) |
||
| 136 | ->method('getNamespaceTotals') |
||
| 137 | ->willReturn($namespaceTotals); |
||
| 138 | $project = new Project('TestProject'); |
||
| 139 | $user = new User('Testuser1'); |
||
| 140 | $editCounter = new EditCounter($project, $user); |
||
| 141 | $editCounter->setRepository($editCounterRepo); |
||
| 142 | |||
| 143 | $this->assertEquals($namespaceTotals, $editCounter->namespaceTotals()); |
||
| 144 | } |
||
| 145 | |||
| 146 | /** |
||
| 147 | * Get all global edit counts, or just the top N, or the overall grand total. |
||
| 148 | */ |
||
| 149 | public function testGlobalEditCounts() |
||
| 150 | { |
||
| 151 | $wiki1 = new Project('wiki1'); |
||
| 152 | $wiki2 = new Project('wiki2'); |
||
| 153 | $editCounts = [ |
||
| 154 | ['project' => new Project('wiki0'), 'total' => 30], |
||
| 155 | ['project' => $wiki1, 'total' => 50], |
||
| 156 | ['project' => $wiki2, 'total' => 40], |
||
| 157 | ['project' => new Project('wiki3'), 'total' => 20], |
||
| 158 | ['project' => new Project('wiki4'), 'total' => 10], |
||
| 159 | ['project' => new Project('wiki5'), 'total' => 35], |
||
| 160 | ]; |
||
| 161 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 162 | $editCounterRepo->expects($this->once()) |
||
| 163 | ->method('globalEditCounts') |
||
| 164 | ->willReturn($editCounts); |
||
| 165 | $user = new User('Testuser1'); |
||
| 166 | $editCounter = new EditCounter($wiki1, $user); |
||
| 167 | $editCounter->setRepository($editCounterRepo); |
||
| 168 | |||
| 169 | // Get the top 2. |
||
| 170 | $this->assertEquals( |
||
| 171 | [ |
||
| 172 | ['project' => $wiki1, 'total' => 50], |
||
| 173 | ['project' => $wiki2, 'total' => 40], |
||
| 174 | ], |
||
| 175 | $editCounter->globalEditCountsTopN(2) |
||
| 176 | ); |
||
| 177 | |||
| 178 | // And the bottom 4. |
||
| 179 | $this->assertEquals(95, $editCounter->globalEditCountWithoutTopN(2)); |
||
| 180 | |||
| 181 | // Grand total. |
||
| 182 | $this->assertEquals(185, $editCounter->globalEditCount()); |
||
| 183 | } |
||
| 184 | |||
| 185 | /** |
||
| 186 | * Ensure parsing of log_params properly works, based on known formats |
||
| 187 | */ |
||
| 188 | public function testLongestBlockDays() |
||
| 189 | { |
||
| 190 | $wiki = new Project('wiki1'); |
||
| 191 | $user = new User('Testuser1'); |
||
| 192 | |||
| 193 | // Scenario 1 |
||
| 194 | $editCounter = new EditCounter($wiki, $user); |
||
| 195 | $editCounterRepo = $this->getMock(EditCounterRepository::class); |
||
| 196 | $editCounter->setRepository($editCounterRepo); |
||
| 197 | $editCounterRepo->expects($this->once()) |
||
| 198 | ->method('getBlocksReceived') |
||
| 199 | ->with($wiki, $user) |
||
| 200 | ->willReturn([ |
||
| 201 | [ |
||
| 202 | 'log_timestamp' => '20170101000000', |
||
| 203 | 'log_params' => 'a:2:{s:11:"5::duration";s:8:"72 hours";s:8:"6::flags";s:8:"nocreate";}', |
||
| 204 | ], |
||
| 205 | [ |
||
| 206 | 'log_timestamp' => '20170301000000', |
||
| 207 | 'log_params' => 'a:2:{s:11:"5::duration";s:7:"1 month";s:8:"6::flags";s:11:"noautoblock";}', |
||
| 208 | ], |
||
| 209 | ]); |
||
| 210 | $this->assertEquals(31, $editCounter->getLongestBlockDays()); |
||
| 211 | |||
| 212 | // Scenario 2 |
||
| 213 | $editCounter2 = new EditCounter($wiki, $user); |
||
| 214 | $editCounterRepo2 = $this->getMock(EditCounterRepository::class); |
||
| 215 | $editCounter2->setRepository($editCounterRepo2); |
||
| 216 | $editCounterRepo2->expects($this->once()) |
||
| 217 | ->method('getBlocksReceived') |
||
| 218 | ->with($wiki, $user) |
||
| 219 | ->willReturn([ |
||
| 220 | [ |
||
| 221 | 'log_timestamp' => '20170201000000', |
||
| 222 | 'log_params' => 'a:2:{s:11:"5::duration";s:8:"infinite";s:8:"6::flags";s:8:"nocreate";}', |
||
| 223 | ], |
||
| 224 | [ |
||
| 225 | 'log_timestamp' => '20170701000000', |
||
| 226 | 'log_params' => 'a:2:{s:11:"5::duration";s:7:"60 days";s:8:"6::flags";s:8:"nocreate";}', |
||
| 227 | ], |
||
| 228 | ]); |
||
| 229 | $this->assertEquals(-1, $editCounter2->getLongestBlockDays()); |
||
| 230 | |||
| 231 | // Scenario 3 |
||
| 232 | $editCounter3 = new EditCounter($wiki, $user); |
||
| 233 | $editCounterRepo3 = $this->getMock(EditCounterRepository::class); |
||
| 234 | $editCounter3->setRepository($editCounterRepo3); |
||
| 235 | $editCounterRepo3->expects($this->once()) |
||
| 236 | ->method('getBlocksReceived') |
||
| 237 | ->with($wiki, $user) |
||
| 238 | ->willReturn([ |
||
| 239 | [ |
||
| 240 | 'log_timestamp' => '20170701000000', |
||
| 241 | 'log_params' => 'a:2:{s:11:"5::duration";s:7:"60 days";s:8:"6::flags";s:8:"nocreate";}', |
||
| 242 | ], |
||
| 243 | [ |
||
| 244 | 'log_timestamp' => '20170101000000', |
||
| 245 | 'log_params' => "9 weeks\nnoautoblock", |
||
| 246 | ], |
||
| 247 | ]); |
||
| 248 | $this->assertEquals(63, $editCounter3->getLongestBlockDays()); |
||
| 249 | } |
||
| 250 | } |
||
| 251 |