Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #31 from rogeriopradoj/pt_BR-jobeet-chapter9

Pt br jobeet chapter9
  • Loading branch information...
commit 432872fdb1790d0162ef228a4022dbc8aa765944 2 parents 5ba8f92 + c0a27a6
Rogerio Prado de Jesus authored January 31, 2012

Showing 1 changed file with 737 additions and 0 deletions. Show diff stats Hide diff stats

  1. 737  jobeet/pt_BR/09.markdown
737  jobeet/pt_BR/09.markdown
Source Rendered
... ...
@@ -0,0 +1,737 @@
  1
+Dia 9: Os Testes Funcionais
  2
+===========================
  3
+
  4
+Ontem nós vimos como testar unitariamente nossas classes do Jobeet usando a
  5
+biblioteca lime que vem embutida no symfony. Hoje, iremos escrever testes
  6
+funcionais para as funcionalidades que já implementamos nos módulos `job` e
  7
+`category`.
  8
+
  9
+Testes Funcionais
  10
+-----------------
  11
+
  12
+Os testes funcionais são uma excelente ferramenta para testar sua aplicação de
  13
+ponta a ponta: desde a requisição feita pelo navegador até a resposta enviada
  14
+pelo servidor. Eles testam todas as camadas da aplicação: as rotas, os models,
  15
+as actions e os templates. Eles se parecem bastante com o que você
  16
+provavelmente já faz manualmente: toda vez que você adiciona ou modifica uma
  17
+action, você precisa ir no navegador e verificar se tudo funciona como o
  18
+esperado clicando nos links e verificando os elementos na página renderizada.
  19
+Em outras palavras, você roda um cenário correspondente ao caso de uso que
  20
+você acabou de implementar.
  21
+
  22
+Como o processo é manual, ele é tedioso e sujeito a erros. Cada vez que você
  23
+muda algo no código, você precisa percorrer todos os cenários para garantir
  24
+que o que você fez não quebrou nada. Isso é insano. Os testes funcionais no
  25
+symfony fornecem uma maneira fácil de descrever cenários. Cada um dos
  26
+cenários pode ser rodado automaticamente várias vezes simulando a experiência
  27
+de um usuário no navegador. Assim como os testes unitários, eles te dão
  28
+confiança para codificar em paz.
  29
+
  30
+>**NOTE**
  31
+>O framework de testes funcionais não substitui ferramentas como o
  32
+>"[~Selenium~](http://selenium.seleniumhq.org/)". O Selenium roda diretamente
  33
+>no navegador para automatizar testes através de várias plataformas e
  34
+>navegadores, além de poder testar o JavaScript da aplicação.
  35
+
  36
+A classe `sfBrowser`
  37
+--------------------
  38
+
  39
+No symfony, os testes funcionais são executados através de um navegador
  40
+especial implementado pela classe
  41
+[~`sfBrowser`|Browser~](http://www.symfony-project.org/api/1_4/sfBrowser).
  42
+Ele age como um navegador adaptado para sua aplicação que fica conectado
  43
+diretamente nele, sem a necessidade de um servidor web. Ele te dá acesso a
  44
+todos os objetos do symfony antes e depois de cada requisição, dando a
  45
+oportunidade de analisá-los e verificá-los programaticamente.
  46
+
  47
+`sfBrowser` fornece métodos que simulam a navegação feita em um navegador
  48
+clássico:
  49
+
  50
+ | Método       | Descrição
  51
+ | ------------ | ------------------------------------------------------
  52
+ | `get()`      | Faz uma requisição GET em uma URL
  53
+ | `post()`     | Faz uma requisição POST em uma URL
  54
+ | `call()`     | Chama uma URL (usado para os métodos `PUT` e `DELETE`)
  55
+ | `back()`     | Volta uma página no histórico
  56
+ | `forward()`  | Avança uma página no histórico
  57
+ | `reload()`   | Recarrega a página atual
  58
+ | `click()`    | Clica em um link ou botão
  59
+ | `select()`   | Seleciona um radiobutton ou checkbox
  60
+ | `deselect()` | Desmarca um radiobutton ou checkbox
  61
+ | `restart()`  | Reinicia o navegador
  62
+
  63
+Aqui estão alguns exemplos de uso dos métodos de `sfBrowser`:
  64
+
  65
+    [php]
  66
+    $browser = new sfBrowser();
  67
+
  68
+    $browser->
  69
+      get('/')->
  70
+      click('Design')->
  71
+      get('/category/programming?page=2')->
  72
+      get('/category/programming', array('page' => 2))->
  73
+      post('search', array('keywords' => 'php'))
  74
+    ;
  75
+
  76
+`sfBrowser` contém métodos adicionais para configurar o comportamento do
  77
+navegador:
  78
+
  79
+ | Método             | Descrição
  80
+ | ------------------ | -------------------------------------------------
  81
+ | `setHttpHeader()`  | Define um cabeçalho HTTP
  82
+ | `setAuth()`        | Define credenciais de autenticação básica
  83
+ | `setCookie()`      | Define um cookie
  84
+ | `removeCookie()`   | Remove um cookie
  85
+ | `clearCookies()`   | Limpa todos os cookies atuais
  86
+ | `followRedirect()` | Segue um redirecionamento
  87
+
  88
+A classe `sfTestFunctional`
  89
+---------------------------
  90
+
  91
+Nós temos um navegador, mas precisamos de uma maneira de verificar
  92
+internamente os objetos do symfony para fazer os testes de verdade. Isso poderia
  93
+ser feito com o lime e alguns métodos de `sfBrowser` como `getResponse()` e
  94
+`getRequest()`, mas o symfony fornece uma maneira melhor.
  95
+
  96
+Os métodos de teste são fornecidos por outra classe, 
  97
+[`sfTestFunctional`](http://www.symfony-project.org/api/1_4/sfTestFunctional),
  98
+que recebe uma instância de `sfBrowser` em seu construtor. A classe
  99
+`sfTestFunctional` delega os os testes para objetos testadores. Muitos
  100
+testadores são embutidos no symfony, e você também pode criar os seus
  101
+próprios.
  102
+
  103
+Como vimos no dia 8, os testes funcionais são guardados dentro do diretório
  104
+`test/functional/`. Para o Jobeet, os testes serão colocados no sub-diretório
  105
+`test/functional/frontend/` pois cada aplicação tem o seu próprio
  106
+sub-diretório. Esse diretório já contém dois arquivos: `categoryActionsTest.php`
  107
+e `jobActionsTest.php`, isso porque todos os comandos que geram módulos criam
  108
+um arquivo básico de teste funcional:
  109
+
  110
+    [php]
  111
+    // test/functional/frontend/categoryActionsTest.php
  112
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  113
+
  114
+    $browser = new sfTestFunctional(new sfBrowser());
  115
+
  116
+    $browser->
  117
+      get('/category/index')->
  118
+
  119
+      with('request')->begin()->
  120
+        isParameter('module', 'category')->
  121
+        isParameter('action', 'index')->
  122
+      end()->
  123
+
  124
+      with('response')->begin()->
  125
+        isStatusCode(200)->
  126
+        checkElement('body', '!/This is a temporary page/')->
  127
+      end()
  128
+    ;
  129
+
  130
+À primeira vista, o script acima pode parecer um pouco estranho. Isso acontece
  131
+porque os métodos de `sfBrowser` e `sfTestFunctional` implementam uma
  132
+[interface fluente](http://en.wikipedia.org/wiki/Fluent_interface) que sempre
  133
+retorna `$this`. Isso permite que você encadeie chamadas de métodos melhorando
  134
+a legibilidade. O trecho acima é equivalente a:
  135
+
  136
+    [php]
  137
+    // test/functional/frontend/categoryActionsTest.php
  138
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  139
+
  140
+    $browser = new sfTestFunctional(new sfBrowser());
  141
+
  142
+    $browser->get('/category/index');
  143
+    $browser->with('request')->begin();
  144
+    $browser->isParameter('module', 'category');
  145
+    $browser->isParameter('action', 'index');
  146
+    $browser->end();
  147
+
  148
+    $browser->with('response')->begin();
  149
+    $browser->isStatusCode(200);
  150
+    $browser->checkElement('body', '!/This is a temporary page/');
  151
+    $browser->end();
  152
+
  153
+Os testes são rodados dentre de um bloco de contexto de teste. Um bloco de
  154
+contexto de teste começa com `with('TESTER NAME')->begin()` e termina com
  155
+`end()`:
  156
+
  157
+    [php]
  158
+    $browser->
  159
+      with('request')->begin()->
  160
+        isParameter('module', 'category')->
  161
+        isParameter('action', 'index')->
  162
+      end()
  163
+    ;
  164
+
  165
+O código testa se o parâmetro da requisição `module` é igual a `category` e
  166
+se `action` é igual a `index`.
  167
+
  168
+>**TIP**
  169
+>Quando você precisar chamar apenas um método de teste em um testador, você
  170
+>não precisa criar um bloco: 
  171
+>`with('request')->isParameter('module', 'category')`.
  172
+
  173
+### O Testador de Requisição
  174
+
  175
+O **testador de requisições HTTP** fornece métodos testadores para analisar
  176
+internamente e testar o objeto `sfWebRequest`:
  177
+
  178
+ | Método             | Descrição
  179
+ | ------------------ | ------------------------------------------------
  180
+ | `isParameter()`    | Verifica o valor de um parâmetro da requisição
  181
+ | `isFormat()`       | Verifica o formato de uma requisição
  182
+ | `isMethod()`       | Verifica o método
  183
+ | `hasCookie()`      | Verifica se a requisição tem um cookie com um
  184
+ |                    | nome determinado
  185
+ | `isCookie()`       | Verifica o valor de um cookie
  186
+
  187
+### O Testador de Reposta
  188
+
  189
+Existe também uma classe **testadora de respostas HTTP** que fornece métodos
  190
+testadores direcionados ao objeto `sfWebResponse`:
  191
+
  192
+ | Método             | Descrição
  193
+ | ------------------ | -----------------------------------------------------
  194
+ | `checkElement()`   | Verifica se o seletor CSS de uma resposta casa com
  195
+ |					  | algum critério
  196
+ | `checkForm()`      | Verifica um objeto formulário `sfForm`
  197
+ | `debug()`          | Imprime a saída da resposta facilitando a depuração
  198
+ | `matches()`        | Testa uma resposta com uma expressão regular
  199
+ | `isHeader()`       | Verifica o valor do cabeçalho
  200
+ | `isStatusCode()`   | Verifica o código de status da resposta
  201
+ | `isRedirected()`   | Verifica se a página atual é um redirecionamento
  202
+ | `isValid()`        | Verifica se uma resposta é um XLM bem-formado
  203
+ |					  | (você também pode validar a resposta novamente no
  204
+ |					  |  seu documento passando `true` como um argumento)
  205
+
  206
+>**NOTE**
  207
+>Descreveremos mais classes testadoras nos próximos dias
  208
+>(para formulários, usuário, cache, ...)
  209
+
  210
+Rodando Testes Funcionais
  211
+-------------------------
  212
+
  213
+Assim como nos testes unitários, rodar os testes funcionais pode ser feito
  214
+executando o arquivo de teste diretamente:
  215
+
  216
+    $ php test/functional/frontend/categoryActionsTest.php
  217
+
  218
+Ou então usando o comando `test:functional`:
  219
+
  220
+    $ php symfony test:functional frontend categoryActions
  221
+
  222
+![Testes na linha de comando](http://www.symfony-project.org/images/jobeet/1_4/09/cli_tests.png)
  223
+
  224
+Dados de Teste
  225
+--------------
  226
+
  227
+Assim como nos testes unitários do ##ORM##, precisamos carregar dados de teste
  228
+toda vez que iniciamos um teste funcional. Podemos reutilizar o código que
  229
+escrevemos anteriormente:
  230
+
  231
+    [php]
  232
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  233
+
  234
+    $browser = new sfTestFunctional(new sfBrowser());
  235
+<propel>
  236
+    $loader = new sfPropelData();
  237
+    $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
  238
+</propel>
  239
+<doctrine>
  240
+    Doctrine_Core::loadData(sfConfig::get('sf_test_dir').'/fixtures');
  241
+</doctrine>
  242
+
  243
+Carregar dados em um teste funcional é um pouco mais fácil do que nos testes
  244
+funcionais pois o banco de dados já foi inicializado pelo script bootstrap.
  245
+
  246
+Como nos testes unitários, não iremos copiar e colar esse trecho de código em
  247
+cada arquivo de teste, em vez disso criaremos nossas próprias classes
  248
+funcionais que herdam da `sfTestFunctional`:
  249
+
  250
+    [php]
  251
+    // lib/test/JobeetTestFunctional.class.php
  252
+    class JobeetTestFunctional extends sfTestFunctional
  253
+    {
  254
+      public function loadData()
  255
+      {
  256
+<propel>
  257
+        $loader = new sfPropelData();
  258
+        $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
  259
+</propel>
  260
+<doctrine>
  261
+        Doctrine_Core::loadData(sfConfig::get('sf_test_dir').'/fixtures');
  262
+</doctrine>
  263
+
  264
+        return $this;
  265
+      }
  266
+    }
  267
+
  268
+Escrevendo Testes Funcionais
  269
+----------------------------
  270
+
  271
+Escrever testes funcionais é como rodar um cenário em um navegador. Nós já
  272
+escrevemos todos os cenários que precisamos testar nas stories do dia 2.
  273
+
  274
+Primeiro, vamos testar a página inicial do Jobeet editando o arquivo de teste
  275
+`jobActionsTest.php`. Substitua o código com o seguinte:
  276
+
  277
+### Empregos expirados não devem ser listados
  278
+
  279
+    [php]
  280
+    // test/functional/frontend/jobActionsTest.php
  281
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  282
+
  283
+    $browser = new JobeetTestFunctional(new sfBrowser());
  284
+    $browser->loadData();
  285
+
  286
+    $browser->info('1 - The homepage')->
  287
+      get('/')->
  288
+      with('request')->begin()->
  289
+        isParameter('module', 'job')->
  290
+        isParameter('action', 'index')->
  291
+      end()->
  292
+      with('response')->begin()->
  293
+        info('  1.1 - Expired jobs are not listed')->
  294
+        checkElement('.jobs td.position:contains("expired")', false)->
  295
+      end()
  296
+    ;
  297
+
  298
+Como no `lime`, uma mensagem informacional pode ser inserida chamando o método
  299
+`info()` para deixar a saída mais legível. Para verificar a exclusão dos
  300
+empregos expirados da página inicial, verificamos se o seletor CSS
  301
+`.jobs td.position:contains("expired")` não casa com nada no conteúdo HTML da
  302
+resposta (lembre-se que nos arquivos fixtures, o único emprego expirado que
  303
+criamos contém "expired" no campo cargo). Quando o segundo argumento do método
  304
+`checkElement()` for um Boolean, o método testa a existência de nós que casem
  305
+com o seletor CSS.
  306
+
  307
+>**TIP**
  308
+>O método `checkElement()` é capaz de interpretar a maioria dos seletores
  309
+CSS3 válidos.
  310
+
  311
+### Apenas n empregos são listados para uma categoria
  312
+
  313
+Adicione o seguinte código no fim do arquivo de teste:
  314
+
  315
+    [php]
  316
+    // test/functional/frontend/jobActionsTest.php
  317
+    $max = sfConfig::get('app_max_jobs_on_homepage');
  318
+
  319
+    $browser->info('1 - The homepage')->
  320
+      get('/')->
  321
+      info(sprintf('  1.2 - Only %s jobs are listed for a category', $max))->
  322
+      with('response')->
  323
+        checkElement('.category_programming tr', $max)
  324
+    ;
  325
+
  326
+O método `checkElement()` também pode verificar se o seletor CSS casa 'n' nós
  327
+no documento passando um inteiro como seu segundo argumento.
  328
+
  329
+### Uma categoria tem um link para a página da categoria apenas se tiver muitos empregos
  330
+
  331
+    [php]
  332
+    // test/functional/frontend/jobActionsTest.php
  333
+    $browser->info('1 - The homepage')->
  334
+      get('/')->
  335
+      info('  1.3 - A category has a link to the category page only if too many jobs')->
  336
+      with('response')->begin()->
  337
+        checkElement('.category_design .more_jobs', false)->
  338
+        checkElement('.category_programming .more_jobs')->
  339
+      end()
  340
+    ;
  341
+
  342
+Nesses testes, podemos verificar se não existe o link "mais empregos" para a
  343
+categoria design (`.category_design .more_jobs` não existe), e se existe
  344
+um link "mais empregos" para a categoria programming
  345
+(`.category_programming .more_jobs` existe).
  346
+
  347
+### Os empregos são ordenados por data
  348
+
  349
+    [php]
  350
+<propel>
  351
+    // most recent job in the programming category
  352
+    $criteria = new Criteria();
  353
+    $criteria->add(JobeetCategoryPeer::SLUG, 'programming');
  354
+    $category = JobeetCategoryPeer::doSelectOne($criteria);
  355
+
  356
+    $criteria = new Criteria();
  357
+    $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN);
  358
+    $criteria->add(JobeetJobPeer::CATEGORY_ID, $category->getId());
  359
+    $criteria->addDescendingOrderByColumn(JobeetJobPeer::CREATED_AT);
  360
+
  361
+    $job = JobeetJobPeer::doSelectOne($criteria);
  362
+</propel>
  363
+<doctrine>
  364
+    $q = Doctrine_Query::create()
  365
+      ->select('j.*')
  366
+      ->from('JobeetJob j')
  367
+      ->leftJoin('j.JobeetCategory c')
  368
+      ->where('c.slug = ?', 'programming')
  369
+      ->andWhere('j.expires_at > ?', date('Y-m-d', time()))
  370
+      ->orderBy('j.created_at DESC');
  371
+
  372
+    $job = $q->fetchOne();
  373
+</doctrine>
  374
+
  375
+    $browser->info('1 - The homepage')->
  376
+      get('/')->
  377
+      info('  1.4 - Jobs are sorted by date')->
  378
+      with('response')->begin()->
  379
+        checkElement(sprintf('.category_programming tr:first a[href*="/%d/"]', $job->getId()))->
  380
+      end()
  381
+    ;
  382
+
  383
+Para testar se os empregos estão realmente ordenados por data, precisamos
  384
+verificar se o primeiro emprego listado na página inicial é o que esperamos.
  385
+Isso pode ser feito verificando se a URL contém a chave primária esperada.
  386
+Como a chave primária pode mudar entre as execuções, precisamos primeiro pegar
  387
+o objeto ##ORM## do banco de dados.
  388
+
  389
+Mesmo se o teste funcionar dessa forma, precisamos refatorar o código um pouco,
  390
+assim se pegarmos o primeiro emprego da categoria programming podemos
  391
+reutilizá-lo em qualquer lugar nos nossos testes. Nós não moveremos o código
  392
+para a camada Model pois o código é específico para os testes. Em vez disso,
  393
+iremos mover o código para a classe `JobeetTestFunctional` que criamos mais
  394
+cedo. Essa classe funciona com um Testador Específico de Domínio do Jobeet:
  395
+
  396
+    [php]
  397
+    // lib/test/JobeetTestFunctional.class.php
  398
+    class JobeetTestFunctional extends sfTestFunctional
  399
+    {
  400
+      public function getMostRecentProgrammingJob()
  401
+      {
  402
+<propel>
  403
+        // most recent job in the programming category
  404
+        $criteria = new Criteria();
  405
+        $criteria->add(JobeetCategoryPeer::SLUG, 'programming');
  406
+        $category = JobeetCategoryPeer::doSelectOne($criteria);
  407
+
  408
+        $criteria = new Criteria();
  409
+        $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN);
  410
+        $criteria->add(JobeetJobPeer::CATEGORY_ID, $category->getId());
  411
+        $criteria->addDescendingOrderByColumn(JobeetJobPeer::CREATED_AT);
  412
+
  413
+        return JobeetJobPeer::doSelectOne($criteria);
  414
+</propel>
  415
+<doctrine>
  416
+        $q = Doctrine_Query::create()
  417
+          ->select('j.*')
  418
+          ->from('JobeetJob j')
  419
+          ->leftJoin('j.JobeetCategory c')
  420
+          ->where('c.slug = ?', 'programming');
  421
+        $q = Doctrine_Core::getTable('JobeetJob')->addActiveJobsQuery($q);
  422
+
  423
+        return $q->fetchOne();
  424
+</doctrine>
  425
+      }
  426
+
  427
+      // ...
  428
+    }
  429
+
  430
+Agora você pode substituir o código do teste anterior com o seguinte:
  431
+
  432
+    [php]
  433
+    // test/functional/frontend/jobActionsTest.php
  434
+    $browser->info('1 - The homepage')->
  435
+      get('/')->
  436
+      info('  1.4 - Jobs are sorted by date')->
  437
+      with('response')->begin()->
  438
+        checkElement(sprintf('.category_programming tr:first a[href*="/%d/"]',
  439
+          $browser->getMostRecentProgrammingJob()->getId()))->
  440
+      end()
  441
+    ;
  442
+
  443
+### Todo emprego na página inicial é clicável
  444
+
  445
+    [php]
  446
+    $job = $browser->getMostRecentProgrammingJob();
  447
+
  448
+    $browser->info('2 - The job page')->
  449
+      get('/')->
  450
+
  451
+      info('  2.1 - Each job on the homepage is clickable and give detailed information')->
  452
+      click('Web Developer', array(), array('position' => 1))->
  453
+      with('request')->begin()->
  454
+        isParameter('module', 'job')->
  455
+        isParameter('action', 'show')->
  456
+        isParameter('company_slug', $job->getCompanySlug())->
  457
+        isParameter('location_slug', $job->getLocationSlug())->
  458
+        isParameter('position_slug', $job->getPositionSlug())->
  459
+        isParameter('id', $job->getId())->
  460
+      end()
  461
+    ;
  462
+
  463
+Para testar o link do emprego na página inicial, simulamos um clique no texto
  464
+"Web Developer". Como existem muitos deles na página, dizemos explicitamente
  465
+para o navegador clicar no primeiro (`array('position' => 1)`).
  466
+
  467
+Cada parâmetro da requisição é então testado para garantir que o roteamento
  468
+foi feito corretamente para o emprego.
  469
+
  470
+Aprenda pelo Exemplo
  471
+--------------------
  472
+
  473
+Nessa seção, nós fornecemos todo o código necessário para testar as páginas de
  474
+emprego e categoria. Leia o código cuidadosamente para que você possa aprender
  475
+alguns truques novos e elegantes:
  476
+
  477
+    [php]
  478
+    // lib/test/JobeetTestFunctional.class.php
  479
+    class JobeetTestFunctional extends sfTestFunctional
  480
+    {
  481
+      public function loadData()
  482
+      {
  483
+<propel>
  484
+        $loader = new sfPropelData();
  485
+        $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
  486
+</propel>
  487
+<doctrine>
  488
+        Doctrine_Core::loadData(sfConfig::get('sf_test_dir').'/fixtures');
  489
+</doctrine>
  490
+
  491
+        return $this;
  492
+      }
  493
+
  494
+      public function getMostRecentProgrammingJob()
  495
+      {
  496
+<propel>
  497
+        // most recent job in the programming category
  498
+        $criteria = new Criteria();
  499
+        $criteria->add(JobeetCategoryPeer::SLUG, 'programming');
  500
+        $category = JobeetCategoryPeer::doSelectOne($criteria);
  501
+
  502
+        $criteria = new Criteria();
  503
+        $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN);
  504
+        $criteria->addDescendingOrderByColumn(JobeetJobPeer::CREATED_AT);
  505
+
  506
+        return JobeetJobPeer::doSelectOne($criteria);
  507
+</propel>
  508
+<doctrine>
  509
+        $q = Doctrine_Query::create()
  510
+          ->select('j.*')
  511
+          ->from('JobeetJob j')
  512
+          ->leftJoin('j.JobeetCategory c')
  513
+          ->where('c.slug = ?', 'programming');
  514
+        $q = Doctrine_Core::getTable('JobeetJob')->addActiveJobsQuery($q);
  515
+
  516
+        return $q->fetchOne();
  517
+</doctrine>
  518
+      }
  519
+
  520
+      public function getExpiredJob()
  521
+      {
  522
+<propel>
  523
+        // expired job
  524
+        $criteria = new Criteria();
  525
+        $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::LESS_THAN);
  526
+
  527
+        return JobeetJobPeer::doSelectOne($criteria);
  528
+</propel>
  529
+<doctrine>
  530
+        $q = Doctrine_Query::create()
  531
+          ->from('JobeetJob j')
  532
+          ->where('j.expires_at < ?', date('Y-m-d', time()));
  533
+
  534
+        return $q->fetchOne();
  535
+</doctrine>
  536
+      }
  537
+    }
  538
+
  539
+    // test/functional/frontend/jobActionsTest.php
  540
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  541
+
  542
+    $browser = new JobeetTestFunctional(new sfBrowser());
  543
+    $browser->loadData();
  544
+
  545
+    $browser->info('1 - The homepage')->
  546
+      get('/')->
  547
+      with('request')->begin()->
  548
+        isParameter('module', 'job')->
  549
+        isParameter('action', 'index')->
  550
+      end()->
  551
+      with('response')->begin()->
  552
+        info('  1.1 - Expired jobs are not listed')->
  553
+        checkElement('.jobs td.position:contains("expired")', false)->
  554
+      end()
  555
+    ;
  556
+
  557
+    $max = sfConfig::get('app_max_jobs_on_homepage');
  558
+
  559
+    $browser->info('1 - The homepage')->
  560
+      info(sprintf('  1.2 - Only %s jobs are listed for a category', $max))->
  561
+      with('response')->
  562
+        checkElement('.category_programming tr', $max)
  563
+    ;
  564
+
  565
+    $browser->info('1 - The homepage')->
  566
+      get('/')->
  567
+      info('  1.3 - A category has a link to the category page only if too many jobs')->
  568
+      with('response')->begin()->
  569
+        checkElement('.category_design .more_jobs', false)->
  570
+        checkElement('.category_programming .more_jobs')->
  571
+      end()
  572
+    ;
  573
+
  574
+    $browser->info('1 - The homepage')->
  575
+      info('  1.4 - Jobs are sorted by date')->
  576
+      with('response')->begin()->
  577
+        checkElement(sprintf('.category_programming tr:first a[href*="/%d/"]', $browser->getMostRecentProgrammingJob()->getId()))->
  578
+      end()
  579
+    ;
  580
+
  581
+    $job = $browser->getMostRecentProgrammingJob();
  582
+
  583
+    $browser->info('2 - The job page')->
  584
+      get('/')->
  585
+
  586
+      info('  2.1 - Each job on the homepage is clickable and give detailed information')->
  587
+      click('Web Developer', array(), array('position' => 1))->
  588
+      with('request')->begin()->
  589
+        isParameter('module', 'job')->
  590
+        isParameter('action', 'show')->
  591
+        isParameter('company_slug', $job->getCompanySlug())->
  592
+        isParameter('location_slug', $job->getLocationSlug())->
  593
+        isParameter('position_slug', $job->getPositionSlug())->
  594
+        isParameter('id', $job->getId())->
  595
+      end()->
  596
+
  597
+      info('  2.2 - A non-existent job forwards the user to a 404')->
  598
+      get('/job/foo-inc/milano-italy/0/painter')->
  599
+      with('response')->isStatusCode(404)->
  600
+
  601
+      info('  2.3 - An expired job page forwards the user to a 404')->
  602
+      get(sprintf('/job/sensio-labs/paris-france/%d/web-developer', $browser->getExpiredJob()->getId()))->
  603
+      with('response')->isStatusCode(404)
  604
+    ;
  605
+
  606
+    // test/functional/frontend/categoryActionsTest.php
  607
+    include(dirname(__FILE__).'/../../bootstrap/functional.php');
  608
+
  609
+    $browser = new JobeetTestFunctional(new sfBrowser());
  610
+    $browser->loadData();
  611
+
  612
+    $browser->info('1 - The category page')->
  613
+      info('  1.1 - Categories on homepage are clickable')->
  614
+      get('/')->
  615
+      click('Programming')->
  616
+      with('request')->begin()->
  617
+        isParameter('module', 'category')->
  618
+        isParameter('action', 'show')->
  619
+        isParameter('slug', 'programming')->
  620
+      end()->
  621
+
  622
+      info(sprintf('  1.2 - Categories with more than %s jobs also have a "more" link', sfConfig::get('app_max_jobs_on_homepage')))->
  623
+      get('/')->
  624
+      click('27')->
  625
+      with('request')->begin()->
  626
+        isParameter('module', 'category')->
  627
+        isParameter('action', 'show')->
  628
+        isParameter('slug', 'programming')->
  629
+      end()->
  630
+
  631
+      info(sprintf('  1.3 - Only %s jobs are listed', sfConfig::get('app_max_jobs_on_category')))->
  632
+      with('response')->checkElement('.jobs tr', sfConfig::get('app_max_jobs_on_category'))->
  633
+
  634
+      info('  1.4 - The job listed is paginated')->
  635
+      with('response')->begin()->
  636
+        checkElement('.pagination_desc', '/32 jobs/')->
  637
+        checkElement('.pagination_desc', '#page 1/2#')->
  638
+      end()->
  639
+
  640
+      click('2')->
  641
+      with('request')->begin()->
  642
+        isParameter('page', 2)->
  643
+      end()->
  644
+      with('response')->checkElement('.pagination_desc', '#page 2/2#')
  645
+    ;
  646
+
  647
+Depurando Testes Funcionais
  648
+---------------------------
  649
+
  650
+Às vezes um teste funcional falha. Como o symfony simula um navegador sem
  651
+nenhuma interface gráfica, pode ser difícil para diagnosticar o problema.
  652
+Pensando nisso o symfony fornece o método `~debug|Debug~()` para mostrar a
  653
+saída do cabeçalho e conteúdo da resposta.
  654
+
  655
+    [php]
  656
+    $browser->with('response')->debug();
  657
+
  658
+O método `debug()` pode ser inserido em qualquer lugar de um bloco testador
  659
+`response` e irá parar a execução do script.
  660
+
  661
+Functional Tests Harness
  662
+------------------------
  663
+
  664
+O comando `test:functional` também pode ser usado para iniciar todos os testes
  665
+funcionais de uma aplicação:
  666
+
  667
+    $ php symfony test:functional frontend
  668
+
  669
+A saída do comando é uma única linha para cada um dos arquivos de teste:
  670
+
  671
+![Functional tests harness](http://www.symfony-project.org/images/jobeet/1_4/09/test_harness.png)
  672
+
  673
+Tests Harness
  674
+-------------
  675
+
  676
+Como você pode esperar, existe também um comando para iniciar todos os testes
  677
+de um projeto (unitários e funcionais):
  678
+
  679
+    $ php symfony test:all
  680
+
  681
+![Tests harness](http://www.symfony-project.org/images/jobeet/1_4/09/tests_harness.png)
  682
+
  683
+Quando você tem um conjunto grande de testes, pode ser bem demorado para
  684
+iniciar todos os testes cada vez que você fizer uma mudança, especialmente se
  685
+alguns dos testes falharem. Isso porque, cada vez que você arruma um teste, você
  686
+deve rodar o conjunto de testes inteiro novamente para garantir que você não
  687
+quebrou nenhuma outra parte. Mas até que os testes falhados não forem
  688
+arrumados, não faz sentido re-executar todos os outros testes. O comando
  689
+`test:all` tem uma opção `--only-failed` que força o comando para re-executar
  690
+apenas os testes que falharam durante a última execução:
  691
+
  692
+    $ php symfony test:all --only-failed
  693
+
  694
+A primeira vez que você roda o comando, todos os testes são rodados como de
  695
+costume. Mas para as execuções subsequentes, apenas os testes que falharam
  696
+na última vez serão executados. Assim que você consertar seu código, alguns testes
  697
+passarão, e serão removidos das execuções subsequentes. Quando todos os testes
  698
+passarem novamente, o conjunto de testes completo é executado... e isso se
  699
+repete várias vezes.
  700
+
  701
+>**TIP**
  702
+>Se você quiser integrar seu conjunto de testes em um processo de integração
  703
+>contínua, use a opção `--xml` para forçar que o comando `test:all` gere uma
  704
+>saída XML compatível com o JUnit.
  705
+>
  706
+>      $ php symfony test:all --xml=log.xml
  707
+
  708
+Considerações Finais
  709
+--------------------
  710
+
  711
+Aqui termina nossa viagem pelas ferramentas de teste do symfony. Você não tem
  712
+mais desculpa para não testar suas aplicações! Com o framework lime e o
  713
+framework de testes funcionais, o symfony fornece ferramentas poderosas para
  714
+te ajudar a escrever testes com pouco esforço.
  715
+
  716
+Nós demos apenas uma pincelada nos testes funcionais. A partir de agora, toda
  717
+vez que implementar uma funcionalidade, também escreveremos testes para
  718
+aprender mais funcionalidades do framework de teste.
  719
+
  720
+Amanhã, falaremos sobre outra excelente funcionalidade do symfony: o
  721
+**framework de formulários**.
  722
+
  723
+Feedback
  724
+--------
  725
+>**Dica - pt_BR**
  726
+>Este capítulo foi traduzido por **Rogerio Prado de Jesus**. 
  727
+>Se encontrar algum erro que deseja corrigir ou quiser fazer algum comentário
  728
+>não deixe de enviar um e-mail para **rogeriopradoj [at] gmail.com**
  729
+
  730
+>**Tip - en**
  731
+>This chapter was translated by **Rogerio Prado Jesus**.
  732
+>If you find any errors to be corrected or you have any comments
  733
+>do not hesitate to send an email to **rogeriopradoj [at] gmail.com**
  734
+
  735
+
  736
+__ORM__
  737
+

0 notes on commit 432872f

Please sign in to comment.
Something went wrong with that request. Please try again.