Complex classes like ItemTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ItemTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class ItemTest extends PHPUnit_Framework_TestCase { |
||
33 | |||
34 | private function getNewEmpty() { |
||
35 | return new Item(); |
||
36 | } |
||
37 | |||
38 | public function testGetId() { |
||
39 | $item = new Item(); |
||
40 | $this->assertNull( $item->getId() ); |
||
41 | |||
42 | $item->setId( new ItemId( 'Q1' ) ); |
||
43 | $this->assertEquals( new ItemId( 'Q1' ), $item->getId() ); |
||
44 | |||
45 | $item->setId( null ); |
||
46 | $this->assertNull( $item->getId() ); |
||
47 | |||
48 | $item = new Item( new ItemId( 'Q2' ) ); |
||
49 | $this->assertEquals( new ItemId( 'Q2' ), $item->getId() ); |
||
50 | } |
||
51 | |||
52 | public function testSetIdUsingNumber() { |
||
53 | $item = new Item(); |
||
54 | $item->setId( 42 ); |
||
55 | $this->assertEquals( new ItemId( 'Q42' ), $item->getId() ); |
||
56 | } |
||
57 | |||
58 | public function testGetSiteLinkWithNonSetSiteId() { |
||
59 | $item = new Item(); |
||
60 | |||
61 | $this->setExpectedException( OutOfBoundsException::class ); |
||
62 | $item->getSiteLinkList()->getBySiteId( 'enwiki' ); |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * @dataProvider simpleSiteLinkProvider |
||
67 | */ |
||
68 | public function testAddSiteLink( SiteLink $siteLink ) { |
||
69 | $item = new Item(); |
||
70 | |||
71 | $item->getSiteLinkList()->addSiteLink( $siteLink ); |
||
72 | |||
73 | $this->assertEquals( |
||
74 | $siteLink, |
||
75 | $item->getSiteLinkList()->getBySiteId( $siteLink->getSiteId() ) |
||
76 | ); |
||
77 | } |
||
78 | |||
79 | public function simpleSiteLinkProvider() { |
||
80 | $argLists = []; |
||
81 | |||
82 | $argLists[] = [ |
||
83 | new SiteLink( |
||
84 | 'enwiki', |
||
85 | 'Wikidata', |
||
86 | [ |
||
87 | new ItemId( 'Q42' ) |
||
88 | ] |
||
89 | ) |
||
90 | ]; |
||
91 | $argLists[] = [ |
||
92 | new SiteLink( |
||
93 | 'nlwiki', |
||
94 | 'Wikidata' |
||
95 | ) |
||
96 | ]; |
||
97 | $argLists[] = [ |
||
98 | new SiteLink( |
||
99 | 'enwiki', |
||
100 | 'Nyan!', |
||
101 | [ |
||
102 | new ItemId( 'Q42' ), |
||
103 | new ItemId( 'Q149' ) |
||
104 | ] |
||
105 | ) |
||
106 | ]; |
||
107 | $argLists[] = [ |
||
108 | new SiteLink( |
||
109 | 'foo bar', |
||
110 | 'baz bah', |
||
111 | [ |
||
112 | new ItemId( 'Q3' ), |
||
113 | new ItemId( 'Q7' ) |
||
114 | ] |
||
115 | ) |
||
116 | ]; |
||
117 | |||
118 | return $argLists; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * @dataProvider simpleSiteLinksProvider |
||
123 | */ |
||
124 | public function testGetSiteLinks() { |
||
135 | |||
136 | public function simpleSiteLinksProvider() { |
||
156 | |||
157 | public function testHasLinkToSiteForFalse() { |
||
165 | |||
166 | public function testHasLinkToSiteForTrue() { |
||
176 | |||
177 | public function testEmptyItemReturnsEmptySiteLinkList() { |
||
178 | $item = new Item(); |
||
179 | $this->assertTrue( $item->getSiteLinkList()->isEmpty() ); |
||
180 | } |
||
181 | |||
182 | public function testAddSiteLinkOverridesOldLinks() { |
||
183 | $item = new Item(); |
||
184 | |||
185 | $item->getSiteLinkList()->addNewSiteLink( 'kittens', 'foo' ); |
||
186 | |||
187 | $newLink = new SiteLink( 'kittens', 'bar' ); |
||
188 | $item->addSiteLink( $newLink ); |
||
189 | |||
190 | $this->assertTrue( $item->getSiteLinkList()->getBySiteId( 'kittens' )->equals( $newLink ) ); |
||
191 | } |
||
192 | |||
193 | public function testEmptyItemIsEmpty() { |
||
194 | $item = new Item(); |
||
195 | $this->assertTrue( $item->isEmpty() ); |
||
196 | } |
||
197 | |||
198 | public function testItemWithIdIsEmpty() { |
||
202 | |||
203 | public function testItemWithStuffIsNotEmpty() { |
||
216 | |||
217 | public function testItemWithSitelinksHasSitelinks() { |
||
222 | |||
223 | public function testItemWithoutSitelinksHasNoSitelinks() { |
||
227 | |||
228 | private function newStatement() { |
||
233 | |||
234 | public function testEmptyConstructor() { |
||
235 | $item = new Item(); |
||
236 | |||
237 | $this->assertNull( $item->getId() ); |
||
238 | $this->assertTrue( $item->getFingerprint()->isEmpty() ); |
||
239 | $this->assertTrue( $item->getLabels()->isEmpty() ); |
||
240 | $this->assertTrue( $item->getDescriptions()->isEmpty() ); |
||
241 | $this->assertTrue( $item->getAliasGroups()->isEmpty() ); |
||
242 | $this->assertTrue( $item->getSiteLinkList()->isEmpty() ); |
||
243 | $this->assertTrue( $item->getStatements()->isEmpty() ); |
||
244 | } |
||
245 | |||
246 | public function testCanConstructWithStatementList() { |
||
247 | $statement = new Statement( new PropertyNoValueSnak( 42 ) ); |
||
248 | $statement->setGuid( 'meh' ); |
||
249 | |||
250 | $statements = new StatementList( $statement ); |
||
251 | |||
252 | $item = new Item( null, null, null, $statements ); |
||
253 | |||
254 | $this->assertEquals( |
||
255 | $statements, |
||
256 | $item->getStatements() |
||
257 | ); |
||
258 | } |
||
259 | |||
260 | public function testSetStatements() { |
||
267 | |||
268 | public function equalsProvider() { |
||
269 | $firstItem = new Item(); |
||
270 | $firstItem->getStatements()->addNewStatement( new PropertyNoValueSnak( 42 ) ); |
||
271 | |||
272 | $secondItem = new Item(); |
||
273 | $secondItem->getStatements()->addNewStatement( new PropertyNoValueSnak( 42 ) ); |
||
274 | |||
275 | $secondItemWithId = $secondItem->copy(); |
||
276 | $secondItemWithId->setId( 42 ); |
||
277 | |||
278 | $differentId = $secondItemWithId->copy(); |
||
279 | $differentId->setId( 43 ); |
||
280 | |||
281 | return [ |
||
282 | [ new Item(), new Item() ], |
||
283 | [ $firstItem, $secondItem ], |
||
284 | [ $secondItem, $secondItemWithId ], |
||
285 | [ $secondItemWithId, $differentId ], |
||
286 | ]; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @dataProvider equalsProvider |
||
291 | */ |
||
292 | public function testEquals( Item $firstItem, Item $secondItem ) { |
||
296 | |||
297 | /** |
||
298 | * @return Item |
||
299 | */ |
||
300 | private function getBaseItem() { |
||
301 | $item = new Item( new ItemId( 'Q42' ) ); |
||
302 | $item->setLabel( 'en', 'Same' ); |
||
303 | $item->setDescription( 'en', 'Same' ); |
||
304 | $item->setAliases( 'en', [ 'Same' ] ); |
||
305 | $item->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Same' ); |
||
306 | $item->getStatements()->addNewStatement( new PropertyNoValueSnak( 42 ) ); |
||
307 | |||
308 | return $item; |
||
309 | } |
||
310 | |||
311 | public function notEqualsProvider() { |
||
312 | $differentLabel = $this->getBaseItem(); |
||
313 | $differentLabel->setLabel( 'en', 'Different' ); |
||
314 | |||
315 | $differentDescription = $this->getBaseItem(); |
||
316 | $differentDescription->setDescription( 'en', 'Different' ); |
||
317 | |||
318 | $differentAlias = $this->getBaseItem(); |
||
319 | $differentAlias->setAliases( 'en', [ 'Different' ] ); |
||
320 | |||
321 | $differentSiteLink = $this->getBaseItem(); |
||
322 | $differentSiteLink->getSiteLinkList()->removeLinkWithSiteId( 'enwiki' ); |
||
323 | $differentSiteLink->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Different' ); |
||
324 | |||
325 | $differentStatement = $this->getBaseItem(); |
||
326 | $differentStatement->setStatements( new StatementList() ); |
||
327 | $differentStatement->getStatements()->addNewStatement( new PropertyNoValueSnak( 24 ) ); |
||
328 | |||
329 | $item = $this->getBaseItem(); |
||
330 | |||
331 | return [ |
||
332 | 'empty' => [ $item, new Item() ], |
||
333 | 'label' => [ $item, $differentLabel ], |
||
334 | 'description' => [ $item, $differentDescription ], |
||
335 | 'alias' => [ $item, $differentAlias ], |
||
336 | 'siteLink' => [ $item, $differentSiteLink ], |
||
337 | 'statement' => [ $item, $differentStatement ], |
||
338 | ]; |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * @dataProvider notEqualsProvider |
||
343 | */ |
||
344 | public function testNotEquals( Item $firstItem, Item $secondItem ) { |
||
345 | $this->assertFalse( $firstItem->equals( $secondItem ) ); |
||
346 | $this->assertFalse( $secondItem->equals( $firstItem ) ); |
||
347 | } |
||
348 | |||
349 | public function cloneProvider() { |
||
350 | $item = new Item( new ItemId( 'Q1' ) ); |
||
351 | $item->setLabel( 'en', 'original' ); |
||
352 | $item->getStatements()->addNewStatement( new PropertyNoValueSnak( 1 ) ); |
||
353 | $item->getSiteLinkList()->addNewSiteLink( 'enwiki', 'Original' ); |
||
354 | |||
355 | return [ |
||
356 | 'copy' => [ $item, $item->copy() ], |
||
357 | 'native clone' => [ $item, clone $item ], |
||
358 | ]; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * @dataProvider cloneProvider |
||
363 | */ |
||
364 | public function testCloneIsEqualButNotIdentical( Item $original, Item $clone ) { |
||
365 | $this->assertNotSame( $original, $clone ); |
||
366 | $this->assertTrue( $original->equals( $clone ) ); |
||
367 | $this->assertSame( |
||
368 | $original->getId(), |
||
369 | $clone->getId(), |
||
370 | 'id is immutable and must not be cloned' |
||
371 | ); |
||
372 | |||
373 | // The clone must not reference the same mutable objects |
||
374 | $this->assertNotSame( $original->getFingerprint(), $clone->getFingerprint() ); |
||
375 | $this->assertNotSame( $original->getStatements(), $clone->getStatements() ); |
||
376 | $this->assertNotSame( |
||
377 | $original->getStatements()->getFirstStatementWithGuid( null ), |
||
378 | $clone->getStatements()->getFirstStatementWithGuid( null ) |
||
379 | ); |
||
380 | $this->assertNotSame( $original->getSiteLinkList(), $clone->getSiteLinkList() ); |
||
381 | $this->assertSame( |
||
382 | $original->getSiteLinkList()->getBySiteId( 'enwiki' ), |
||
383 | $clone->getSiteLinkList()->getBySiteId( 'enwiki' ), |
||
384 | 'SiteLink is immutable and must not be cloned' |
||
385 | ); |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * @dataProvider cloneProvider |
||
390 | */ |
||
391 | public function testOriginalDoesNotChangeWithClone( Item $original, Item $clone ) { |
||
392 | $originalStatement = $original->getStatements()->getFirstStatementWithGuid( null ); |
||
393 | $clonedStatement = $clone->getStatements()->getFirstStatementWithGuid( null ); |
||
394 | |||
395 | $clone->setLabel( 'en', 'clone' ); |
||
396 | $clone->setDescription( 'en', 'clone' ); |
||
397 | $clone->setAliases( 'en', [ 'clone' ] ); |
||
398 | $clonedStatement->setGuid( 'clone' ); |
||
399 | $clonedStatement->setMainSnak( new PropertySomeValueSnak( 666 ) ); |
||
400 | $clonedStatement->setRank( Statement::RANK_DEPRECATED ); |
||
401 | $clonedStatement->getQualifiers()->addSnak( new PropertyNoValueSnak( 1 ) ); |
||
402 | $clonedStatement->getReferences()->addNewReference( new PropertyNoValueSnak( 1 ) ); |
||
403 | $clone->getSiteLinkList()->removeLinkWithSiteId( 'enwiki' ); |
||
404 | |||
405 | $this->assertSame( 'original', $original->getFingerprint()->getLabel( 'en' )->getText() ); |
||
406 | $this->assertFalse( $original->getFingerprint()->hasDescription( 'en' ) ); |
||
407 | $this->assertFalse( $original->getFingerprint()->hasAliasGroup( 'en' ) ); |
||
408 | $this->assertNull( $originalStatement->getGuid() ); |
||
409 | $this->assertSame( 'novalue', $originalStatement->getMainSnak()->getType() ); |
||
410 | $this->assertSame( Statement::RANK_NORMAL, $originalStatement->getRank() ); |
||
411 | $this->assertTrue( $originalStatement->getQualifiers()->isEmpty() ); |
||
412 | $this->assertTrue( $originalStatement->getReferences()->isEmpty() ); |
||
413 | $this->assertFalse( $original->getSiteLinkList()->isEmpty() ); |
||
414 | } |
||
415 | |||
416 | // Below are tests copied from EntityTest |
||
417 | |||
418 | public function labelProvider() { |
||
419 | return [ |
||
420 | [ 'en', 'spam' ], |
||
421 | [ 'en', 'spam', 'spam' ], |
||
422 | [ 'de', 'foo bar baz' ], |
||
423 | ]; |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * @dataProvider labelProvider |
||
428 | * @param string $languageCode |
||
429 | * @param string $labelText |
||
430 | * @param string $moarText |
||
431 | */ |
||
432 | public function testSetLabel( $languageCode, $labelText, $moarText = 'ohi there' ) { |
||
433 | $entity = $this->getNewEmpty(); |
||
434 | |||
435 | $entity->setLabel( $languageCode, $labelText ); |
||
436 | |||
437 | $this->assertSame( $labelText, $entity->getFingerprint()->getLabel( $languageCode )->getText() ); |
||
438 | |||
439 | $entity->setLabel( $languageCode, $moarText ); |
||
440 | |||
441 | $this->assertSame( $moarText, $entity->getFingerprint()->getLabel( $languageCode )->getText() ); |
||
442 | } |
||
443 | |||
444 | public function descriptionProvider() { |
||
445 | return [ |
||
446 | [ 'en', 'spam' ], |
||
447 | [ 'en', 'spam', 'spam' ], |
||
448 | [ 'de', 'foo bar baz' ], |
||
449 | ]; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * @dataProvider descriptionProvider |
||
454 | * @param string $languageCode |
||
455 | * @param string $description |
||
456 | * @param string $moarText |
||
457 | */ |
||
458 | public function testSetDescription( $languageCode, $description, $moarText = 'ohi there' ) { |
||
459 | $entity = $this->getNewEmpty(); |
||
460 | |||
461 | $entity->setDescription( $languageCode, $description ); |
||
462 | |||
463 | $this->assertSame( $description, $entity->getFingerprint()->getDescription( $languageCode )->getText() ); |
||
464 | |||
465 | $entity->setDescription( $languageCode, $moarText ); |
||
466 | |||
467 | $this->assertSame( $moarText, $entity->getFingerprint()->getDescription( $languageCode )->getText() ); |
||
468 | } |
||
469 | |||
470 | public function aliasesProvider() { |
||
471 | return [ |
||
472 | [ [ |
||
473 | 'en' => [ [ 'spam' ] ] |
||
474 | ] ], |
||
475 | [ [ |
||
476 | 'en' => [ [ 'foo', 'bar', 'baz' ] ] |
||
477 | ] ], |
||
478 | [ [ |
||
479 | 'en' => [ [ 'foo', 'bar' ], [ 'baz', 'spam' ] ] |
||
480 | ] ], |
||
481 | [ [ |
||
482 | 'en' => [ [ 'foo', 'bar', 'baz' ] ], |
||
483 | 'de' => [ [ 'foobar' ], [ 'baz' ] ], |
||
484 | ] ], |
||
485 | // with duplicates |
||
486 | [ [ |
||
487 | 'en' => [ [ 'spam', 'ham', 'ham' ] ] |
||
488 | ] ], |
||
489 | [ [ |
||
490 | 'en' => [ [ 'foo', 'bar' ], [ 'bar', 'spam' ] ] |
||
491 | ] ], |
||
492 | ]; |
||
493 | } |
||
494 | |||
495 | /** |
||
496 | * @dataProvider aliasesProvider |
||
497 | */ |
||
498 | public function testSetAliases( array $aliasesLists ) { |
||
499 | $entity = $this->getNewEmpty(); |
||
500 | |||
501 | foreach ( $aliasesLists as $langCode => $aliasesList ) { |
||
502 | foreach ( $aliasesList as $aliases ) { |
||
503 | $entity->setAliases( $langCode, $aliases ); |
||
504 | } |
||
505 | } |
||
506 | |||
507 | foreach ( $aliasesLists as $langCode => $aliasesList ) { |
||
508 | $expected = array_values( array_unique( array_pop( $aliasesList ) ) ); |
||
509 | $actual = $entity->getFingerprint()->getAliasGroup( $langCode )->getAliases(); |
||
510 | $this->assertSame( $expected, $actual ); |
||
511 | } |
||
512 | } |
||
513 | |||
514 | public function testSetEmptyAlias() { |
||
515 | $item = new Item(); |
||
516 | |||
517 | $item->setAliases( 'en', [ 'wind', 'air', '', 'fire' ] ); |
||
518 | $this->assertSame( |
||
519 | [ 'wind', 'air', 'fire' ], |
||
520 | $item->getAliasGroups()->getByLanguage( 'en' )->getAliases() |
||
521 | ); |
||
522 | |||
523 | $item->setAliases( 'en', [ '', '' ] ); |
||
524 | $this->assertFalse( $item->getAliasGroups()->hasGroupForLanguage( 'en' ) ); |
||
525 | } |
||
526 | |||
527 | public function instanceProvider() { |
||
562 | |||
563 | /** |
||
564 | * @dataProvider instanceProvider |
||
565 | * @param Item $entity |
||
566 | */ |
||
567 | public function testCopy( Item $entity ) { |
||
576 | |||
577 | public function testCopyRetainsLabels() { |
||
588 | |||
589 | /** |
||
590 | * @dataProvider instanceProvider |
||
591 | * @param Item $entity |
||
592 | */ |
||
593 | public function testSerialize( Item $entity ) { |
||
603 | |||
604 | public function testWhenNoStuffIsSet_getFingerprintReturnsEmptyFingerprint() { |
||
612 | |||
613 | public function testWhenLabelsAreSet_getFingerprintReturnsFingerprintWithLabels() { |
||
629 | |||
630 | public function testWhenTermsAreSet_getFingerprintReturnsFingerprintWithTerms() { |
||
652 | |||
653 | public function testGivenEmptyFingerprint_noTermsAreSet() { |
||
659 | |||
660 | public function testGivenEmptyFingerprint_existingTermsAreRemoved() { |
||
671 | |||
672 | public function testWhenSettingFingerprint_getFingerprintReturnsIt() { |
||
691 | |||
692 | public function testGetLabels() { |
||
703 | |||
704 | public function testGetDescriptions() { |
||
715 | |||
716 | public function testGetAliasGroups() { |
||
727 | |||
728 | public function testGetLabels_sameListAsFingerprint() { |
||
736 | |||
737 | public function testGetDescriptions_sameListAsFingerprint() { |
||
745 | |||
746 | public function testGetAliasGroups_sameListAsFingerprint() { |
||
754 | |||
755 | /** |
||
756 | * @dataProvider clearableProvider |
||
757 | */ |
||
758 | public function testClear( Item $item ) { |
||
759 | $clone = $item->copy(); |
||
760 | |||
761 | $item->clear(); |
||
762 | |||
763 | $this->assertEquals( $clone->getId(), $item->getId(), 'cleared Item should keep its id' ); |
||
764 | $this->assertTrue( $item->isEmpty(), 'cleared Item should be empty' ); |
||
765 | } |
||
766 | |||
767 | public function clearableProvider() { |
||
768 | return [ |
||
769 | 'empty' => [ new Item( new ItemId( 'Q23' ) ), ], |
||
770 | 'with fingerprint' => [ |
||
771 | new Item( |
||
793 | |||
794 | } |
||
795 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.