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:
Complex classes like ArraysTest 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 ArraysTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | final class ArraysTest extends \PHPUnit_Framework_TestCase |
||
14 | { |
||
15 | /** |
||
16 | * @test |
||
17 | * @covers ::get |
||
18 | */ |
||
19 | public function get() |
||
26 | |||
27 | /** |
||
28 | * @test |
||
29 | * @covers ::getIfSet |
||
30 | */ |
||
31 | public function getIfSet() |
||
39 | |||
40 | /** |
||
41 | * @test |
||
42 | * @covers ::copyIfKeysExist |
||
43 | */ |
||
44 | public function copyIfKeysExist() |
||
53 | |||
54 | /** |
||
55 | * Verify behavior with numeric array $keyMap |
||
56 | * |
||
57 | * @test |
||
58 | * @covers ::copyIfKeysExist |
||
59 | */ |
||
60 | View Code Duplication | public function copyIfKeysExistNumericKeyMap() |
|
67 | |||
68 | /** |
||
69 | * Verify basic behavior of copyIfSet() |
||
70 | * |
||
71 | * @test |
||
72 | * @covers ::copyIfSet |
||
73 | */ |
||
74 | public function copyIfSet() |
||
81 | |||
82 | /** |
||
83 | * Verify behavior of copyIfSet() with numeric array $keyMap |
||
84 | * |
||
85 | * @test |
||
86 | * @covers ::copyIfSet |
||
87 | */ |
||
88 | View Code Duplication | public function copyIfSetNumericKeyMap() |
|
95 | |||
96 | /** |
||
97 | * @test |
||
98 | * @covers ::tryGet |
||
99 | */ |
||
100 | public function tryGetNullKey() |
||
106 | |||
107 | /** |
||
108 | * @test |
||
109 | * @covers ::tryGet |
||
110 | */ |
||
111 | public function tryGetClassForKey() |
||
117 | |||
118 | /** |
||
119 | * @test |
||
120 | * @covers ::tryGet |
||
121 | */ |
||
122 | public function tryGetValueStringKey() |
||
128 | |||
129 | /** |
||
130 | * @test |
||
131 | * @covers ::tryGet |
||
132 | */ |
||
133 | public function tryGetValueIntegerKey() |
||
139 | |||
140 | /** |
||
141 | * @test |
||
142 | * @covers ::project |
||
143 | */ |
||
144 | public function projectBasicUse() |
||
151 | |||
152 | /** |
||
153 | * @test |
||
154 | * @covers ::project |
||
155 | * @expectedException \InvalidArgumentException |
||
156 | */ |
||
157 | public function projectStrictKeyFail() |
||
161 | |||
162 | /** |
||
163 | * @test |
||
164 | * @covers ::project |
||
165 | */ |
||
166 | public function projectStrictKeyFalse() |
||
173 | |||
174 | /** |
||
175 | * @test |
||
176 | * @covers ::project |
||
177 | * @expectedException \InvalidArgumentException |
||
178 | * @expectedExceptionMessage $strictKeyCheck was not a bool |
||
179 | */ |
||
180 | public function projectStrictKeyNotBool() |
||
184 | |||
185 | /** |
||
186 | * @test |
||
187 | * @covers ::project |
||
188 | * @expectedException \InvalidArgumentException |
||
189 | * @expectedExceptionMessage a value in $input was not an array |
||
190 | */ |
||
191 | public function projectInputValueNotArray() |
||
195 | |||
196 | /** |
||
197 | * Verifies basic usage for where() with exact matching |
||
198 | * |
||
199 | * @test |
||
200 | * @covers ::where |
||
201 | */ |
||
202 | public function whereBasicUsage() |
||
214 | |||
215 | /** |
||
216 | * Verifies that where() returns empty array when nothing matches |
||
217 | * |
||
218 | * @test |
||
219 | * @covers ::where |
||
220 | */ |
||
221 | public function whereReturnsEmptyArray() |
||
232 | |||
233 | /** |
||
234 | * Verifies use of multiple conditions |
||
235 | * |
||
236 | * @test |
||
237 | * @covers ::where |
||
238 | */ |
||
239 | public function whereWithMultipleConditions() |
||
251 | |||
252 | /** |
||
253 | * Verifies use of multiple conditions |
||
254 | * |
||
255 | * @test |
||
256 | * @covers ::where |
||
257 | */ |
||
258 | public function whereReturnsMultipleResults() |
||
274 | |||
275 | /** |
||
276 | * @test |
||
277 | * @covers ::where |
||
278 | * @expectedException \InvalidArgumentException |
||
279 | * @expectedExceptionMessage a value in $array was not an array |
||
280 | */ |
||
281 | public function whereInputValueNotArray() |
||
285 | |||
286 | /** |
||
287 | * Verifies that embedInto works well with adding new items into an existing array. |
||
288 | * |
||
289 | * @test |
||
290 | * @covers ::embedInto |
||
291 | */ |
||
292 | public function embedIntoBasicUse() |
||
306 | |||
307 | /** |
||
308 | * Verifies that embedInto works well with creating new records. |
||
309 | * |
||
310 | * @test |
||
311 | * @covers ::embedInto |
||
312 | */ |
||
313 | public function embedIntoEmptyDestination() |
||
320 | |||
321 | /** |
||
322 | * Verifies that embedInto requires string for fieldname |
||
323 | * |
||
324 | * @test |
||
325 | * @covers ::embedInto |
||
326 | * @expectedException InvalidArgumentException |
||
327 | * @expectedExceptionMessage $fieldName was not a string |
||
328 | */ |
||
329 | public function embedIntoNumericFieldName() |
||
333 | |||
334 | /** |
||
335 | * Verifies that embedInto requires destination entries to be arrays. |
||
336 | * |
||
337 | * @test |
||
338 | * @covers ::embedInto |
||
339 | * @expectedException InvalidArgumentException |
||
340 | * @expectedExceptionMessage a value in $destination was not an array |
||
341 | */ |
||
342 | public function embedIntoNonArrayDestinationItems() |
||
346 | |||
347 | /** |
||
348 | * Verifies that embedInto refuses to overwrite field names. |
||
349 | * |
||
350 | * @test |
||
351 | * @covers ::embedInto |
||
352 | * @expectedException Exception |
||
353 | */ |
||
354 | public function embedIntoExistingFieldName() |
||
358 | |||
359 | /** |
||
360 | * Verifies that embedInto does nothing with 0 items to embed. |
||
361 | * |
||
362 | * @test |
||
363 | * @covers ::embedInto |
||
364 | */ |
||
365 | public function embedIntoNoItems() |
||
369 | |||
370 | /** |
||
371 | * @test |
||
372 | * @covers ::embedInto |
||
373 | */ |
||
374 | public function embedIntoOverwrite() |
||
378 | |||
379 | /** |
||
380 | * @test |
||
381 | * @covers ::embedInto |
||
382 | * @expectedException \InvalidArgumentException |
||
383 | * @expectedExceptionMessage $overwrite was not a bool |
||
384 | */ |
||
385 | public function embedIntoOverwriteNotBool() |
||
389 | |||
390 | /** |
||
391 | * Basic usage of fillIfKeysExist() |
||
392 | * |
||
393 | * @test |
||
394 | * @covers ::fillIfKeysExist |
||
395 | */ |
||
396 | public function fillIfKeysExist() |
||
406 | |||
407 | /** |
||
408 | * Basic usage of extract() |
||
409 | * |
||
410 | * @test |
||
411 | * @covers ::extract |
||
412 | * @uses \DominionEnterprises\Util\Arrays::get |
||
413 | */ |
||
414 | View Code Duplication | public function extract() |
|
428 | |||
429 | /** |
||
430 | * Basic usage of extract() with 'takeFirst' option |
||
431 | * |
||
432 | * @test |
||
433 | * @covers ::extract |
||
434 | * @uses \DominionEnterprises\Util\Arrays::get |
||
435 | */ |
||
436 | View Code Duplication | public function extractTakeFirst() |
|
450 | |||
451 | /** |
||
452 | * Basic usage of extract() with 'throw' option |
||
453 | * |
||
454 | * @test |
||
455 | * @covers ::extract |
||
456 | * @uses \DominionEnterprises\Util\Arrays::get |
||
457 | * @expectedException \Exception |
||
458 | * @expectedExceptionMessage Duplicate entry for 'boo' found. |
||
459 | */ |
||
460 | public function extractThrowOnDuplicate() |
||
472 | |||
473 | /** |
||
474 | * Verify behavior when a single dimensional array is given to extract(). |
||
475 | * |
||
476 | * @test |
||
477 | * @covers ::extract |
||
478 | * @expectedException \InvalidArgumentException |
||
479 | * @expectedExceptionMessage $arrays was not a multi-dimensional array |
||
480 | */ |
||
481 | public function extractWithSingleDimensionalArray() |
||
485 | |||
486 | /** |
||
487 | * Verify behavior when $arrays contain a invalid key value in the supplied $keyIndex field. |
||
488 | * |
||
489 | * @test |
||
490 | * @covers ::extract |
||
491 | * @uses \DominionEnterprises\Util\Arrays::get |
||
492 | * @expectedException \UnexpectedValueException |
||
493 | * @expectedExceptionMessage Value for $arrays[1][key] was not a string or integer |
||
494 | */ |
||
495 | public function extractWithInvalidKeyValue() |
||
504 | |||
505 | /** |
||
506 | * Verify behavior when $keyIndex is not a string or integer |
||
507 | * |
||
508 | * @test |
||
509 | * @covers ::extract |
||
510 | * @expectedException \InvalidArgumentException |
||
511 | * @expectedExceptionMessage $keyIndex was not a string or integer |
||
512 | */ |
||
513 | public function extractWithInvalidKeyIndex() |
||
517 | |||
518 | /** |
||
519 | * Verify behavior when $valueIndex is not a string or integer |
||
520 | * |
||
521 | * @test |
||
522 | * @covers ::extract |
||
523 | * @expectedException \InvalidArgumentException |
||
524 | * @expectedExceptionMessage $valueIndex was not a string or integer |
||
525 | */ |
||
526 | public function extractWithInvalidValueIndex() |
||
530 | |||
531 | /** |
||
532 | * Verify behavior when $duplicateBehavior is not valid |
||
533 | * |
||
534 | * @test |
||
535 | * @covers ::extract |
||
536 | * @expectedException \InvalidArgumentException |
||
537 | * @expectedExceptionMessage $duplicateBehavior was not 'takeFirst', 'takeLast', or 'throw' |
||
538 | */ |
||
539 | public function extractWithInvalidDuplicateBehavior() |
||
543 | |||
544 | /** |
||
545 | * Verify basic behavior of getFirstSet() |
||
546 | * |
||
547 | * @test |
||
548 | * @covers ::getFirstSet |
||
549 | */ |
||
550 | public function getFirstSet() |
||
554 | |||
555 | /** |
||
556 | * Verify getFirstSet() returns default value |
||
557 | * |
||
558 | * @test |
||
559 | * @covers ::getFirstSet |
||
560 | */ |
||
561 | public function getFirstSetWithDefault() |
||
565 | |||
566 | /** |
||
567 | * Verifiy basic behavior of partition() |
||
568 | * |
||
569 | * @test |
||
570 | * @covers ::partition |
||
571 | */ |
||
572 | public function partition() |
||
576 | |||
577 | /** |
||
578 | * Verify partition() behavior when $input array contains less items than than $partitionCount. |
||
579 | * |
||
580 | * @test |
||
581 | * @covers ::partition |
||
582 | */ |
||
583 | public function partitionInputLessThanPartitionCount() |
||
587 | |||
588 | /** |
||
589 | * Verify remainder of $input array is front-loaded in partition(). |
||
590 | * |
||
591 | * @test |
||
592 | * @covers ::partition |
||
593 | */ |
||
594 | public function partitionWithRemainder() |
||
598 | |||
599 | /** |
||
600 | * Verify remainder of $input array is front-loaded in partition(). |
||
601 | * |
||
602 | * @test |
||
603 | * @covers ::partition |
||
604 | */ |
||
605 | public function partitionWithMultipleRemainder() |
||
609 | |||
610 | /** |
||
611 | * Verify partition() handles empty $input array. |
||
612 | * |
||
613 | * @test |
||
614 | * @covers ::partition |
||
615 | */ |
||
616 | public function partitionEmptyInput() |
||
620 | |||
621 | /** |
||
622 | * Verifiy behavior of partition() with $partitionCount of 1. |
||
623 | * |
||
624 | * @test |
||
625 | * @covers ::partition |
||
626 | */ |
||
627 | public function partitionOnePartition() |
||
631 | |||
632 | /** |
||
633 | * Verifiy partition() throws with negative $partitionCount. |
||
634 | * |
||
635 | * @test |
||
636 | * @covers ::partition |
||
637 | * @expectedException \InvalidArgumentException |
||
638 | * @expectedExceptionMessage $partitionCount must be a positive integer |
||
639 | */ |
||
640 | public function partitionNegativePartitionCount() |
||
644 | |||
645 | /** |
||
646 | * Verifiy partition() throws with 0 $partitionCount. |
||
647 | * |
||
648 | * @test |
||
649 | * @covers ::partition |
||
650 | * @expectedException \InvalidArgumentException |
||
651 | * @expectedExceptionMessage $partitionCount must be a positive integer |
||
652 | */ |
||
653 | public function partitionZeroPartitionCount() |
||
657 | |||
658 | /** |
||
659 | * Verifiy partition() throws with non-integer $partitionCount. |
||
660 | * |
||
661 | * @test |
||
662 | * @covers ::partition |
||
663 | * @expectedException \InvalidArgumentException |
||
664 | * @expectedExceptionMessage $partitionCount must be a positive integer |
||
665 | */ |
||
666 | public function partitionNonIntegerPartitionCount() |
||
670 | |||
671 | /** |
||
672 | * Verifiy partition() preserves numeric keys. |
||
673 | * |
||
674 | * @test |
||
675 | * @covers ::partition |
||
676 | */ |
||
677 | public function partitionPreserveNumericKeys() |
||
684 | |||
685 | /** |
||
686 | * Verifiy partition() preserves associative keys. |
||
687 | * |
||
688 | * @test |
||
689 | * @covers ::partition |
||
690 | */ |
||
691 | public function partitionPreserveAssociativeKeys() |
||
698 | |||
699 | /** |
||
700 | * Verifiy partition() throws with non-boolean $preserveKeys. |
||
701 | * |
||
702 | * @test |
||
703 | * @covers ::partition |
||
704 | * @expectedException \InvalidArgumentException |
||
705 | * @expectedExceptionMessage $preserveKeys must be a boolean value |
||
706 | */ |
||
707 | public function partitionNonBoolPreserveKeys() |
||
711 | |||
712 | /** |
||
713 | * Verify basic behavior of unsetAll(). |
||
714 | * |
||
715 | * @test |
||
716 | * @covers ::unsetAll |
||
717 | */ |
||
718 | public function unsetAll() |
||
724 | |||
725 | /** |
||
726 | * Verify behavior of unsetAll() with empty array. |
||
727 | * |
||
728 | * @test |
||
729 | * @covers ::unsetAll |
||
730 | */ |
||
731 | public function unsetAllEmptyArray() |
||
738 | |||
739 | /** |
||
740 | * Verify behavior of unsetAll() with empty keys. |
||
741 | * |
||
742 | * @test |
||
743 | * @covers ::unsetAll |
||
744 | */ |
||
745 | View Code Duplication | public function unsetAllEmptyKeys() |
|
752 | |||
753 | /** |
||
754 | * Verify behavior of unsetAll() with keys that don't exist |
||
755 | * |
||
756 | * @test |
||
757 | * @covers ::unsetAll |
||
758 | */ |
||
759 | View Code Duplication | public function unsetAllKeyNotFound() |
|
766 | |||
767 | /** |
||
768 | * Verify basic behavior of nullifyEmptyStrings(). |
||
769 | * @test |
||
770 | * @covers ::nullifyEmptyStrings |
||
771 | */ |
||
772 | public function nullifyEmptyStrings() |
||
778 | |||
779 | /** |
||
780 | * Verify behavior of nullifyEmptyStrings() with empty input. |
||
781 | * @test |
||
782 | * @covers ::nullifyEmptyStrings |
||
783 | */ |
||
784 | public function nullifyEmptyStringsEmptyArray() |
||
790 | |||
791 | /** |
||
792 | * Verify basic functionality of getNested. |
||
793 | * |
||
794 | * @test |
||
795 | * @covers ::getNested |
||
796 | * |
||
797 | * @return void |
||
798 | */ |
||
799 | public function getNested() |
||
804 | |||
805 | /** |
||
806 | * Verify behavior when the given delimitedKey does not exist in the given array. |
||
807 | * |
||
808 | * @test |
||
809 | * @covers ::getNested |
||
810 | * |
||
811 | * @return void |
||
812 | */ |
||
813 | public function getNestedPathNotFound() |
||
818 | |||
819 | /** |
||
820 | * Verify functionality of changeKeyCase(). |
||
821 | * |
||
822 | * @test |
||
823 | * @covers ::changeKeyCase |
||
824 | * @dataProvider changeKeyCaseData |
||
825 | * |
||
826 | * @return void |
||
827 | */ |
||
828 | public function changeKeyCase($input, $case, $expected) |
||
832 | |||
833 | /** |
||
834 | * Dataprovider for changeKeyCase test. |
||
835 | * |
||
836 | * @return array |
||
837 | */ |
||
838 | public function changeKeyCaseData() |
||
886 | |||
887 | /** |
||
888 | * Verify basic behavior of flatten(). |
||
889 | * |
||
890 | * @test |
||
891 | * @covers ::flatten |
||
892 | * |
||
893 | * @return void |
||
894 | */ |
||
895 | View Code Duplication | public function flatten() |
|
915 | |||
916 | /** |
||
917 | * Verify behavior of flatten() with custom delimiter. |
||
918 | * |
||
919 | * @test |
||
920 | * @covers ::flatten |
||
921 | * |
||
922 | * @return void |
||
923 | */ |
||
924 | View Code Duplication | public function flattenWithCustomDelimiter() |
|
944 | |||
945 | /** |
||
946 | * @test |
||
947 | * @covers ::getAllWhereKeyExists |
||
948 | * |
||
949 | * @return void |
||
950 | */ |
||
951 | public function getAllWhereKeyExists() |
||
986 | } |
||
987 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.