tx_t3blog_pi1 Kaktusteam: Mimi's Blog

Mimis Blog

01.01.2010
21:43

ExtBase - Tests für Controller schreiben

Heute mal ausnahmsweise auf deutsch, weil ich Angst habe, dass ich mich auf Englisch nicht klar genug ausdrücken kann. Mit diesem Post möchte ich einen Vorschlag machen, wie in ExtBase Tests für Controller geschrieben werden könnten. 

 

Nachdem die Controller Actions quasi die Herzstücke der Anwendung sind - also zumindest die Teile der Anwendung in denen es gilt dass alles andere funktionieren sollte - hätte ich gerne eine Möglichkeit nach anstehenden Refactorings etc. zu überprüfen, ob die Actions noch problemlos ausgeführt werden. Dazu möchte ich so viele Klassen wie möglich aus dem ExtBase Framework mocken - diese sollten ja bereits getestet sein - ich möchte nur wissen, ob meine Businesslogik im Controller noch so funktioniert, wie sie darin implementiert ist.

 

Die Frage ist jetzt: Warum teste ich nicht einfach meine Businesslogik und lass die Controller ungetestet. Nun - würde ich das so machen, könnte ich zwar nach jedem Refactoring in der Business Logik über Tests prüfen, ob mein Zeugs noch tut, was es soll. Spätestens wenn sich aber Schnittstellen in den Klassen ändern und ich die Tests anpassen muss, bedeuten laufende Tests der Businesslogik aber noch lange nicht, dass die Controller Actions noch funktionieren. Ich hätte zwar 100% getestete Business-Logik, die Anwendung selber würde aber nicht mehr laufen. Darum denke ich, dass es sinnvoll ist, Tests zu schreiben, die überprüfen, ob die Controller noch fehlerfrei arbeiten.

 

Da im Bereich der Controller-Verarbeitung in ExtBase eine Gewisse Abhängigkeitshölle herrscht (nicht bös gemeint :-) ), scheiterte mein erster Versuch gnadenlos, in dem ich versuchte, einfach den ExtBase Dispatcher auf eine gemockte TS-Konfiguration loszulassen. Das größte Problem dabei war, dass der Dispatcher über eine Konstante feststellt, dass er im Backend aufgerufen wird und damit sämtliche Frontendlogik den Bach hinunter geht. Kurzum: Unmöglich.

 

Sebastian Kurfürst machte dann den Vorschlag, möglichst nur die eigentliche Action zu testen und alles andere zu mocken. Immer noch nicht ganz so einfach, wie zuerst gedacht, aber hier ist ein Vorschlag, den ich zur Diskussion stellen möchte. Fangen wir mit einer Implementierung des Testcases an:

 

 

  1. <?php
  2.  
  3. class Tx_Yag_Controller_GalleryController_testcase
  4. extends Tx_Extbase_BaseTestCase {
  5.  
  6. /**
  7. * Holds TS configuration of yag extension
  8. * @var array
  9. */
  10. private $configuration;
  11.  
  12.  
  13.  
  14. /**
  15.   * @var Tx_Yag_Tests_Mocks_GalleryControllerMock
  16.   */
  17. private $galleryController;
  18.  
  19.  
  20.  
  21. /**
  22. * @var Tx_Extbase_Dispatcher
  23. */
  24. private $dispatcher;
  25.  
  26.  
  27.  
  28. /**
  29. * Sets up environment for testing gallery controller
  30. *
  31. * @return void
  32. * @author Michael Knoll <mimi@kaktusteam.de>
  33. */
  34. public function setUp() {
  35. // This is needed for some basic initialization!
  36. $this->dispatcher = new Tx_Extbase_Dispatcher();
  37. $this->configuration = Tx_Yag_Tests_Mocks_ConfigurationMocks::getBasicConfiguration();
  38. $this->galleryController = t3lib_div::makeInstance('Tx_Yag_Tests_Mocks_GalleryControllerMock');
  39. $this->galleryController->injectMockView(new Tx_Fluid_View_TemplateView());
  40. $this->galleryController->injectMockRepository(new Tx_Yag_Tests_Mocks_GalleryRepositoryMock());
  41. $this->galleryController->injectSettings($this->configuration['settings']);
  42. }
  43.  
  44.  
  45.  
  46. /**
  47. * Tests index action of gallery controller
  48. * @test
  49. */
  50. public function indexAction() {
  51. $this->galleryController->indexAction();
  52. }
  53.  
  54.  
  55.  
  56. /**
  57. * Tests show action of gallery controller
  58. * @test
  59. */
  60. public function showAction() {
  61. $this->galleryController->showAction(new Tx_Yag_Domain_Model_Gallery());
  62. }
  63.  
  64.  
  65. }
  66. ?>
  67.  

 

 

Wie man sehen kann, musste ich um den eigentlichen Controller zu testen eine erbende Klasse implementieren, welche mir den Zugriff auf ein paar versteckte Eigenschaften ermöglicht. Hier meine Controller-Mock Klasse:

 

 

  1. <?php
  2.  
  3. class Tx_Yag_Tests_Mocks_GalleryControllerMock extends
  4. Tx_Yag_Controller_GalleryController {
  5.  
  6. public function injectMockView(Tx_Extbase_MVC_View_ViewInterface $view) {
  7. $this->view = $view;
  8. }
  9.  
  10. public function injectMockRepository(
  11. Tx_Extbase_Persistence_RepositoryInterface $mockRepository) {
  12. $this->galleryRepository = $mockRepository;
  13. }
  14.  
  15. public function getView() {
  16. return $this->view;
  17. }
  18.  
  19. }
  20.  
  21. ?>

 

 

Zuletzt war es noch nötig, eine eigene Autoloader Datei für die Tests hinzuzufügen, da der Extbase Autoload nur im "Classes" Verzeichnis einer Extension nach Klassendefinitionsdateien sucht:

 

 

  1. <?php
  2.  
  3. $extensionTestsPath = t3lib_extMgm::extPath('yag') . 'Tests/';
  4.  
  5. return array(
  6. 'tx_yag_tests_mocks_configurationmocks' =>
  7. $extensionTestsPath . 'Mocks/ConfigurationMocks.php',
  8. 'tx_yag_tests_mocks_gallerycontrollermock' =>
  9. $extensionTestsPath . 'Mocks/GalleryControllerMock.php',
  10. 'tx_yag_tests_mocks_galleryrepositorymock' =>
  11. $extensionTestsPath . 'Mocks/GalleryRepositoryMock.php'
  12. );
  13. ?>

 

 

Ich würde dieses Konzept des Testens gerne zur Diskussion stellen und freue mich über Rückmeldungen egal welcher Art. Für mich ist testgetriebene Entwicklung ein recht neues Spielfeld und ich bin mir nicht sicher, ob meine Ideen gut sind oder nicht.

01.01.2010
04:05

Elbrus S/W

Nachdem ich gerade eben endlich herausgefunden habe, wie man Farbbilder mit Lightroom gescheit in Schwarzweissbilder umwandelt, konnte ich nicht an mich halten und musste ein paar der Elbrus-Bilder vom Fühling 2009 in Schwarzweiss umwandeln... über Kritik und Anregungen freue ich mich wie immer!

 

 

Akklimatisierungstour
Seilbahnstation
's Lärsle
Ersatzteile für alte Seilbahn
Im Schneesturm auf 4300m
Gipfelpanorama
31.12.2009
02:48

Yag 0.0.1 available on Forge

 

 

Nach ca. 14-tägiger Entwicklungszeit habe ich heute die neue Gallery-Extension soweit fertiggestellt, dass ich das Forge SVN öffentlich gemacht habe. Damit kann eine erste Vorabversion der Galerie ausgecheckt werden.

 

Die Version soll in erster Linie als Diskussionsgrundlage dienen, um über die Art der Implementierung in ExtBase sowie das Datenmodell zu diskutieren. Anregungen und Ideen, wie man manche Dinge besser oder anders machen könnte, sind herzlich willkommen.

 

Das Repository findet sich unter http://forge.typo3.org/repositories/show/extension-yag

 

Als nächste Schritte möchte ich eine öffentliche Testumgebung einrichten in der die Gallery online getestet werden kann sowie unzählige Tests schreiben, was ich natürlich trotz des guten Vorsatzes bis jetzt noch nicht gemacht habe. Letzteres lag aber auch im Wesentlichen daran, dass ich mich erst mal an das ExtBase Framework rantesten musste, um rauszufinden, wie Tests überhaupt sinnvoll implementiert werden können.

30.12.2009
02:56

ExtBase Cookbook 7 - Form parameter handling and validation

Today, I tried to handle all form inputs via objects and use validators to check consistency. As this is a common task in web applications, I wanted to share my experiences with you.

 

After reading all parameters from my form using the request property of my controller, I remembered the way shown in the blog example, using objects to pass form parameters. So what I did was writing objects that had all parameters passed by a form as properties, added getters and setters and used the "object" attribute of the form view helper to make my form use this object for passing parameters. Here's an example of a form object which I saved under "EXT:Classes/Domain/Model/FormObject/AddImagesByPath.php":

 

 

  1. class Tx_Yag_Domain_Model_FormObject_AddImagesByPath {
  2.  
  3. protected $basePath;
  4.  
  5. protected $singlesPath;
  6.  
  7. protected $thumbsPath;
  8.  
  9. protected $origsPath;
  10.  
  11. public function getBasePath() {
  12. return $this->basePath;
  13. }
  14.  
  15. public function getOrigsPath() {
  16. return $this->origsPath;
  17. }
  18.  
  19. public function getSinglesPath() {
  20. return $this->singlesPath;
  21. }
  22.  
  23. public function getThumbsPath() {
  24. return $this->thumbsPath;
  25. }
  26.  
  27. public function setBasePath($basePath) {
  28. $this->basePath = $basePath;
  29. }
  30.  
  31. public function setOrigsPath($origsPath) {
  32. $this->origsPath = $origsPath;
  33. }
  34.  
  35. public function setSinglesPath($singlesPath) {
  36. $this->singlesPath = $singlesPath;
  37. }
  38.  
  39. public function setThumbsPath($thumbsPath) {
  40. $this->thumbsPath = $thumbsPath;
  41. }
  42.  
  43. }

 

 

Here is the snippet of the template where I use the object for passing form-values:

 

 

  1. <f:render partial="formErrors" arguments="{for: 'addImagesByPath'}" />
  2. <f:form method="post" controller="AlbumContent" action="addImagesByPath"
  3. object="{AddImagesByPath}" name="addImagesByPath"
  4. arguments="{gallery : gallery, album : album}">
  5. <label for="basePath">Path (from fileadmin/)</label><br />
  6. <f:form.textbox property="basePath" size="30" /><br />
  7. <label for="thumbsPath">Directory for thumbnails</label><br/>
  8. <f:form.textbox property="thumbsPath" size="20" /><br />
  9. <label for="singlesPath">Directory for singles:</label><br/>
  10. <f:form.textbox property="singlesPath" size="20" /><br />
  11. <label for="origsPath">Direcotry for original images:</label><br />
  12. <f:form.textbox property="origsPath" size="20" /><br /><br />
  13. <f:form.submit class="submit" value="Submit"/>
  14. </f:form>

 

 

The controller action looks like that:

 

 

  1. public function addImagesByPathAction(
  2. Tx_Yag_Domain_Model_FormObject_AddImagesByPath $addImagesByPath = NULL,
  3. Tx_Yag_Domain_Model_Gallery $gallery = NULL,
  4. Tx_Yag_Domain_Model_Album $album) {
  5.  
  6. $albumPathConfiguration =
  7. Tx_Yag_Lib_AlbumPathConfiguration::
  8. getInstanceByAlbumPathObject($addImagesByPath);
  9.  
  10. $addImagesToAlbumHandler =
  11. Tx_Yag_Lib_AddImagesToAlbumHandler::
  12. getInstanceByAlbumAndPathConfiguration(
  13. $album, $albumPathConfiguration);
  14. $images = $addImagesToAlbumHandler->addImagesFromPathConfiguration();
  15.  
  16. $this->view->assign('addImagesByPath', $addImagesByPath);
  17. $this->view->assign('images', $images);
  18. $this->view->assign('gallery', $gallery);
  19. $this->view->assign('album', $album);
  20.  
  21. return $this->view->render();
  22.  
  23. }

 

 

For validating my form object, I added a validator in "Classes/Domain/Validator/FormObject/" and named it "AddImagesByPathValidator.php".

 

Make sure you use the right class name with PEAR convention "path encoding" and you have your validator put into a subdirectory with the same name as the subdirectory in the model directory.

 

The content of this validator looks like that:

 

 

  1. <?php
  2. class Tx_Yag_Domain_Validator_FormObject_AddImagesByPathValidator extends
  3. Tx_Extbase_Validation_Validator_AbstractValidator {
  4.  
  5. /**
  6.   * Returns true, if the given AddImagesByPath form object is valid
  7.   *
  8.   * @param Tx_Yag_Lib_FormObject_AlbumContent_AddImagesByPath $addImagesByPath The addImagesByPath form object
  9.   * @return boolean true
  10.   */
  11. public function isValid($addImagesByPath) {
  12. if (!is_dir($addImagesByPath->getBasePath())) {
  13. $this->addError(
  14. 'The given base path is not a valid path on this system!', 1);
  15. return FALSE;
  16. }
  17. if (!is_dir($addImagesByPath->getBasePath() . '/' .
  18. $addImagesByPath->getOrigsPath())) {
  19. $this->addError(
  20. 'The given origs path is not a valid path inside the given base path!', 2);
  21. return FALSE;
  22. }
  23. if (!is_dir($addImagesByPath->getBasePath() . '/' .
  24. $addImagesByPath->getSinglesPath())) {
  25. $this->addError(
  26. 'The given singles path is not a valid path inside the given base path!', 3);
  27. return FALSE;
  28. }
  29. if (!is_dir($addImagesByPath->getBasePath() . '/' .
  30. $addImagesByPath->getThumbsPath())) {
  31. $this->addError(
  32. 'The given thumbs path is not a valid path inside the given base path!', 4);
  33. return FALSE;
  34. }
  35. return TRUE;
  36. }
  37.  
  38. }
  39. ?>

 

 

Last but not least you can output the error messages with the following two snippets of fluid code. The first part is the inclusion of the errors in your form template, the second is a copied partial for error handling from the blog example.

 

 

  1. <f:render partial="formErrors" arguments="{for: 'addImagesByPath'}" />

 

 

 

  1. <f:form.errors for="{for}">
  2. <div class="tx-yag-form-errors">
  3. {error.message}
  4. <f:if condition="{error.propertyName}">
  5. <strong>{error.propertyName}</strong>:
  6. <f:for each="{error.errors}" as="errorDetail">
  7. {errorDetail.message}
  8. </f:for>
  9. </p>
  10. </f:if>
  11. </div>
  12. </f:form.errors>
29.12.2009
02:43

Erste - letzte Skitour 2009 / 2010

Um nach den anstrengenden Weihnachtsfutterorgien wieder einigermaßen in Form zu kommen, ging's gleich nach den beiden Feiertagen aufs Riedbergerhorn - mit Skiern versteht sich. Schnee, Wetter und Laune waren weit besser als erwartet und ganz nebenbei entstand dieses Panorama aufm Gipfel (da war allerdings aller Schnee weggeblasen...)

 

 

Panorama Riedbergerhorn - 27.12.2009
< < September 2010 > >
S M T W T F S
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Kategorien

  • [-](J)DAV(28)
  • [-]Bergsteigen(30)
  • [-]Computer & Informatik(63)
    • PHP(2)
    • [-]Typo3(54)
  • [-]Fotografie(47)
  • Kaktusteam(3)
  • [-]Laufen(4)
  • Musik & Filme(3)
  • [-]Radfahren(0)
  • [-]Reisen(11)
  • Sonstiges...(4)
  • Studium...(6)

Letzte Kommentare

Using Extbase Controller and Action in TemplaVoila TS Lib
26.08.2010 22:02
insert in TS
26.08.2010 11:27
Gut geschildert
23.08.2010 22:05
Good Timing
21.08.2010 01:59

Archiv

Kopieren Sie diesen Link in Ihren RSS Reader

RSS 0.91Posts
RSS 2.0Posts

Social Bookmarking

Bookmark bei: Mr. Wong Bookmark bei: Webnews Bookmark bei: Icio Bookmark bei: Oneview Bookmark bei: Linkarena Bookmark bei: Favoriten Bookmark bei: Seekxl Bookmark bei: Favit Bookmark bei: Social Bookmarking Tool Bookmark bei: Power Oldie Bookmark bei: Bookmarks.cc Bookmark bei: Newskick Bookmark bei: Newsider Bookmark bei: Linksilo Bookmark bei: Readster Bookmark bei: Folkd Bookmark bei: Yigg Bookmark bei: Digg Bookmark bei: Del.icio.us Bookmark bei: Reddit Bookmark bei: Simpy Bookmark bei: StumbleUpon Bookmark bei: Slashdot Bookmark bei: Netscape Bookmark bei: Furl Bookmark bei: Yahoo Bookmark bei: Spurl Bookmark bei: Google Bookmark bei: Blinklist Bookmark bei: Blogmarks Bookmark bei: Diigo Bookmark bei: Technorati Bookmark bei: Newsvine Bookmark bei: Blinkbits Bookmark bei: Ma.Gnolia Bookmark bei: Smarking Bookmark bei: Netvouz Information