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 FormTest 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 FormTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class FormTest extends \PHPUnit_Framework_TestCase |
||
12 | { |
||
13 | /** |
||
14 | * @var Form |
||
15 | */ |
||
16 | protected $form; |
||
17 | |||
18 | /** |
||
19 | * Sets up the fixture, for example, opens a network connection. |
||
20 | * This method is called before a test is executed. |
||
21 | */ |
||
22 | public function setUp() |
||
26 | |||
27 | /** |
||
28 | * Tears down the fixture, for example, closes a network connection. |
||
29 | * This method is called after a test is executed. |
||
30 | */ |
||
31 | public function tearDown() |
||
35 | |||
36 | /** |
||
37 | * @covers Koch\Form\Form::__construct |
||
38 | * @expectedException InvalidArgumentException |
||
39 | * @expectedExceptionMessage |
||
40 | */ |
||
41 | public function testConstructorThrowsExceptionWhenFirstArgumentMissing() |
||
45 | |||
46 | /** |
||
47 | * @covers Koch\Form\Form::__construct |
||
48 | */ |
||
49 | public function testConstructorArgsAreSet() |
||
61 | |||
62 | /** |
||
63 | * @expectedException InvalidArgumentException |
||
64 | * @expectedExceptionMessage The method parameter is "abc", but has to be GET or POST. |
||
65 | */ |
||
66 | public function testSetMethodThrowsInvalidArgumentException() |
||
70 | |||
71 | public function testSetMethod() |
||
81 | |||
82 | public function testGetMethod() |
||
91 | |||
92 | public function testSetAction() |
||
111 | |||
112 | public function testGetAction() |
||
119 | |||
120 | /** |
||
121 | * @expectedException InvalidArgumentException |
||
122 | * @expectedExceptionMessage The target parameter is "abc", but has to be one of _blank, _self, _parent, _top. |
||
123 | */ |
||
124 | public function testMethodsetTargetThrowsException() |
||
128 | |||
129 | /** |
||
130 | * @covers Koch\Form\Form::getTarget |
||
131 | * @covers Koch\Form\Form::setTarget |
||
132 | */ |
||
133 | public function testMethodsetTarget() |
||
139 | |||
140 | public function testGetAutocomplete() |
||
145 | |||
146 | public function testSetAutocomplete() |
||
154 | |||
155 | public function testGetNoValidation() |
||
160 | |||
161 | public function testSetNoValidation() |
||
173 | |||
174 | public function testGetAttribute() |
||
181 | |||
182 | public function testSetAttribute() |
||
189 | |||
190 | public function testSetAttributes() |
||
202 | |||
203 | public function testSetAttributesContainFormKey() |
||
204 | { |
||
205 | $this->markTestSkipped('Depends on Form\Generator\PHPArray'); |
||
206 | |||
207 | $attributes = [ |
||
208 | 'attr1' => 'val1', |
||
209 | 'attr2' => true, |
||
210 | 'form' => [ |
||
211 | 'name' => 'formname', |
||
212 | 'action' => 'someAction', |
||
213 | 'method' => 'POST', |
||
214 | 'key-a' => 'value-a', |
||
215 | 'key-b' => 'value-b', |
||
216 | ], |
||
217 | ]; |
||
218 | |||
219 | $this->form->setAttributes($attributes); |
||
220 | } |
||
221 | |||
222 | public function testCopyObjectProperties() |
||
242 | |||
243 | /** |
||
244 | * @covers Koch\Form\Form::setID() |
||
245 | * @covers Koch\Form\Form::getID() |
||
246 | */ |
||
247 | public function testSetID() |
||
252 | |||
253 | /** |
||
254 | * @covers Koch\Form\Form::setName() |
||
255 | * @covers Koch\Form\Form::getName() |
||
256 | */ |
||
257 | public function testSetName() |
||
262 | |||
263 | /** |
||
264 | * @covers Koch\Form\Form::setAcceptCharset() |
||
265 | * @covers Koch\Form\Form::getAcceptCharset() |
||
266 | */ |
||
267 | public function testGetAcceptCharset() |
||
277 | |||
278 | /** |
||
279 | * @covers Koch\Form\Form::setClass() |
||
280 | * @covers Koch\Form\Form::getClass() |
||
281 | */ |
||
282 | public function testSetClass() |
||
289 | |||
290 | /** |
||
291 | * @covers Koch\Form\Form::setDescription() |
||
292 | * @covers Koch\Form\Form::getDescription() |
||
293 | */ |
||
294 | public function testSetDescription() |
||
301 | |||
302 | /** |
||
303 | * @covers Koch\Form\Form::setHeading() |
||
304 | * @covers Koch\Form\Form::getHeading() |
||
305 | */ |
||
306 | public function testSetHeading() |
||
313 | |||
314 | /** |
||
315 | * @covers Koch\Form\Form::setEncoding() |
||
316 | * @covers Koch\Form\Form::getEncoding() |
||
317 | */ |
||
318 | public function testGetEncoding() |
||
328 | |||
329 | /** |
||
330 | * @covers Koch\Form\Form::setLegend() |
||
331 | * @covers Koch\Form\Form::getLegend() |
||
332 | */ |
||
333 | public function testSetLegend() |
||
343 | |||
344 | public function testSetLegend_allowsMethodChaining() |
||
350 | |||
351 | /** |
||
352 | * @covers Koch\Form\Form::setFormelements() |
||
353 | * @covers Koch\Form\Form::getFormelements() |
||
354 | */ |
||
355 | public function testSetFormelements() |
||
356 | { |
||
357 | // via getter - returns inital empty array |
||
358 | $this->assertEquals([], $this->form->getFormelements()); |
||
359 | |||
360 | $formelements = ['formelements']; |
||
361 | $this->form->setFormelements($formelements); |
||
362 | $this->assertEquals($formelements, $this->form->getFormelements()); |
||
363 | } |
||
364 | |||
365 | public function testFormHasErrors() |
||
369 | |||
370 | public function testregisterDefaultFormelementDecorators() |
||
385 | |||
386 | public function testRenderAllFormelements() |
||
396 | |||
397 | /** |
||
398 | * @expectedException Koch\Exception\Exception |
||
399 | * @expectedExceptionMessage Error rendering formelements. No formelements on form object. Consider adding some formelements using addElement(). |
||
400 | */ |
||
401 | public function testRenderAllFormelementsThrowsException() |
||
405 | |||
406 | public function testuseDefaultFormDecoratorsDisableViaConstructor() |
||
407 | { |
||
408 | $form = new Form(['useDefaultFormDecorators' => true]); |
||
409 | $decorators = $form->getDecorators(); |
||
410 | $this->assertEquals([], $decorators); |
||
411 | unset($form); |
||
412 | } |
||
413 | |||
414 | View Code Duplication | public function testuseDefaultFormDecoratorsMethodTrue() |
|
424 | |||
425 | View Code Duplication | public function testregisterDefaultFormDecorators() |
|
433 | |||
434 | public function testremoveDecorator() |
||
441 | |||
442 | public function testgetDecorator() |
||
449 | |||
450 | /* |
||
451 | * expectedException InvalidArgumentException |
||
452 | * expectedExceptionMessage The Form does not have a Decorator called "not-existing-formdecorator". |
||
453 | */ |
||
454 | |||
455 | public function testgetDecoratorNotFoundException() |
||
460 | |||
461 | View Code Duplication | public function testRender() |
|
471 | |||
472 | public function testRenderWithDecorator() |
||
493 | |||
494 | View Code Duplication | public function testRenderViaToString() |
|
505 | |||
506 | public function testAddElement() |
||
517 | |||
518 | public function testAddElementAddingFileElementSetsEncoding() |
||
523 | |||
524 | public function testAddElement_toSpecificPositions() |
||
525 | { |
||
526 | $this->form->addElement('textarea', [], 'Position1'); |
||
527 | $this->form->addElement('checkbox', [], 'Position2'); |
||
528 | $this->form->addElement('submitbutton', [], 'Position3'); |
||
529 | |||
530 | $formelements = $this->form->getFormelements(); |
||
531 | $this->assertArrayHasKey('Position1', $formelements); |
||
532 | $this->assertArrayHasKey('Position2', $formelements); |
||
533 | $this->assertArrayHasKey('Position3', $formelements); |
||
534 | } |
||
535 | |||
536 | public function testAddElementWithMultipleElements() |
||
548 | |||
549 | public function testAddElementWithSettingAttributes() |
||
550 | { |
||
551 | // test element |
||
552 | $attributes = [ |
||
553 | 'class' => 'myFormelementClass', |
||
554 | 'maxlength' => '20', |
||
555 | 'label' => 'myFormelementLabel', |
||
556 | 'id' => 'text-formelement-0', |
||
557 | ]; |
||
558 | |||
559 | $this->form->addElement('Text', $attributes); |
||
560 | $formelement = $this->form->getElementByPosition('0'); |
||
561 | |||
562 | $this->assertEquals($attributes['class'], $formelement->class); |
||
563 | $this->assertEquals($attributes['maxlength'], $formelement->maxlength); |
||
564 | $this->assertEquals($attributes['label'], $formelement->label); |
||
565 | $this->assertEquals($attributes['id'], $formelement->id); |
||
566 | } |
||
567 | |||
568 | View Code Duplication | public function testAddElementToCertainPosition() |
|
569 | { |
||
570 | // PREPARE: |
||
571 | // this will take position 0 |
||
572 | $this->form->addElement('File'); |
||
573 | // this will take position 1 |
||
574 | $this->form->addElement('Captcha'); |
||
575 | |||
576 | // TEST: |
||
577 | // this will take position 0 + reorders the array |
||
578 | $this->form->addElement('Text', null, 0); |
||
579 | |||
580 | $array = []; |
||
581 | $array[] = new Elements\Text(); // 0 - Text |
||
582 | $array[] = new Elements\File(); // 1 - File |
||
583 | $array[] = new Elements\Captcha(); // 2 - Captcha |
||
584 | // manually reapply formelement identifiers |
||
585 | $array['0']->setID('text-formelement-0'); |
||
586 | $array['1']->setID('file-formelement-1'); |
||
587 | $array['2']->setID('captcha-formelement-2'); |
||
588 | |||
589 | $this->assertEquals($array, $this->form->getFormelements()); |
||
590 | } |
||
591 | |||
592 | public function testAddElementSwitchEncodingWhenUsingFormelementFile() |
||
598 | |||
599 | View Code Duplication | public function testregenerateFormelementIdentifiers() |
|
600 | { |
||
601 | // PREPARE: |
||
602 | // this will take position 0 |
||
603 | $this->form->addElement('File'); |
||
604 | // this will take position 1 |
||
605 | $this->form->addElement('Captcha'); |
||
606 | |||
607 | // TEST: |
||
608 | // this will take position 0 and reorder the array |
||
609 | $this->form->addElement('Text', null, 0); |
||
610 | |||
611 | $array = []; |
||
612 | $array[] = new \Koch\Form\Elements\Text(); // 0 - Text |
||
613 | $array[] = new \Koch\Form\Elements\File(); // 1 - File |
||
614 | $array[] = new \Koch\Form\Elements\Captcha(); // 2 - Captcha |
||
615 | // manually reapply formelement identifiers |
||
616 | $array['0']->setID('text-formelement-0'); |
||
617 | $array['1']->setID('file-formelement-1'); |
||
618 | $array['2']->setID('captcha-formelement-2'); |
||
619 | |||
620 | $this->assertEquals($array, $this->form->getFormelements()); |
||
621 | } |
||
622 | |||
623 | public function testDelElementByName() |
||
633 | |||
634 | public function testGetElementByPosition() |
||
645 | |||
646 | public function testGetElementByName() |
||
653 | |||
654 | public function testGetElement_ByNameOrByPositionOrLastElement() |
||
670 | |||
671 | public function testFormelementFactory() |
||
677 | |||
678 | public function testMethodprocessForm() |
||
689 | |||
690 | public function testMethodprocessForm_withIncommingData() |
||
705 | |||
706 | public function testsetValuesDataArrayPassedToMethod() |
||
707 | { |
||
708 | // create multiselect "Snacks" with three options |
||
709 | $this->form->addElement('MultiSelect')->setName('Snacks')->setOptions( |
||
710 | ['cola' => 'Cola', 'popcorn' => 'Popcorn', 'peanuts' => 'Peanuts'] |
||
711 | ); |
||
712 | |||
713 | // two options were selected (array is incomming via post) |
||
714 | $data = ['snacks' => ['cola', 'popcorn']]; |
||
715 | |||
716 | $this->form->setValues($data); |
||
717 | |||
718 | $snacks_array = $this->form->getElementByName('Snacks')->getValue(); |
||
719 | $this->assertSame(count($snacks_array), 2); |
||
720 | $this->assertSame($snacks_array[0], 'cola'); |
||
721 | $this->assertSame($snacks_array[1], 'popcorn'); |
||
722 | } |
||
723 | |||
724 | public function testgetValues() |
||
725 | { |
||
726 | $this->form->addElement('Textarea', ['value' => 'Some Text Inside The First Textarea']); |
||
727 | $this->form->addElement('Textarea', ['value' => 'More Text Inside The Second Textarea']); |
||
728 | |||
729 | $values = $this->form->getValues(); |
||
730 | |||
731 | $this->assertTrue(is_array($values)); |
||
732 | $this->assertSame(count($values), 2); |
||
733 | |||
734 | $expected_values = [ |
||
735 | 'textarea-formelement-0' => 'Some Text Inside The First Textarea', |
||
736 | 'textarea-formelement-1' => 'More Text Inside The Second Textarea', |
||
737 | ]; |
||
738 | |||
739 | $this->assertSame($values, $expected_values); |
||
740 | } |
||
741 | |||
742 | View Code Duplication | public function testSetFormelementDecoratorFormelementPositionNull() |
|
755 | |||
756 | View Code Duplication | public function testAddFormelementDecorator() |
|
770 | |||
771 | /** |
||
772 | * @expectedException RuntimeException |
||
773 | * @expectedExceptionMessage No Formelements found. Add the formelement(s) first, then decorate! |
||
774 | */ |
||
775 | public function testAddFormelementDecorator_ThrowsExceptionWhenNoFormelementsFound() |
||
779 | |||
780 | View Code Duplication | public function testRemoveFormelementDecorator() |
|
795 | |||
796 | View Code Duplication | public function testSetDecorator() |
|
806 | |||
807 | View Code Duplication | public function testAddDecorator() |
|
817 | |||
818 | View Code Duplication | public function testGetDecorators() |
|
826 | |||
827 | public function testDecoratorFactory() |
||
833 | |||
834 | /** |
||
835 | * @covers Koch\Form\Form::setDecoratorAttributesArray() |
||
836 | * @covers Koch\Form\Form::getDecoratorAttributesArray() |
||
837 | */ |
||
838 | public function testsetDecoratorAttributesArray() |
||
845 | |||
846 | public function testapplyDecoratorAttributes() |
||
847 | { |
||
848 | // decorator type will be form |
||
849 | // this is just another way of setting attributes to the form itself |
||
850 | $attributes = ['form' => ['form' => // this is Koch\Form\Decorator\Form |
||
851 | ['heading' => 'This is the Heading of the form.', |
||
852 | 'description' => 'This is a form description text.', ], |
||
853 | ]]; |
||
854 | |||
855 | $this->form->setDecoratorAttributesArray($attributes); |
||
856 | |||
857 | $this->form->registerDefaultFormDecorators(); |
||
858 | |||
859 | $this->form->applyDecoratorAttributes(); |
||
860 | |||
861 | $form_decorator_form = $this->form->getDecorator('form'); |
||
862 | |||
863 | $this->assertSame('This is the Heading of the form.', $form_decorator_form->heading); |
||
864 | $this->assertSame('This is a form description text.', $form_decorator_form->description); |
||
865 | } |
||
866 | |||
867 | public function testAddValidator() |
||
873 | |||
874 | public function testValidateFormFalse() |
||
890 | |||
891 | View Code Duplication | public function testValidateForm_true() |
|
904 | |||
905 | View Code Duplication | public function testsetRequired() |
|
913 | |||
914 | View Code Duplication | public function testIsRequired() |
|
921 | |||
922 | public function testhasErrors() |
||
932 | |||
933 | public function testaddErrorMessage() |
||
940 | |||
941 | public function testaddErrorMessages() |
||
947 | |||
948 | public function testaddErrorMessagesOverwriteMessages() |
||
949 | { |
||
950 | $set1 = ['aaa', 'bbb', 'ccc']; |
||
951 | $set2 = ['ddd', 'eee']; |
||
952 | $this->form->addErrorMessages($set1); |
||
953 | $this->assertSame($set1, $this->form->getErrorMessages()); |
||
954 | $this->form->addErrorMessages($set2); |
||
955 | $this->assertSame($set2, $this->form->getErrorMessages()); |
||
956 | } |
||
957 | |||
958 | public function testresetErrorMessages() |
||
966 | |||
967 | public function testgetErrorMessages() |
||
973 | |||
974 | public function testMagicSet() |
||
981 | |||
982 | public function testMagicGet() |
||
990 | } |
||
991 |
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.