Статьи

Как написать тесты дыма для Ember Rails Stack

Следующая история показывает важность тестов дыма при написании приложения с использованием стека Ember + Rails. Он охватывает самый простой пример, и даже тогда все идет не так, как надо. Кроме того, он показывает пример сквозного испытания и некоторые трудности в настройке среды для испытаний на дым. Наконец, в нем упоминается выхлоп , библиотека, которая может облегчить эту боль.

Оригинальный автор: Мика Вудс ( @ mwoods79 )

Представьте, что вы готовы начать писать свое следующее замечательное приложение. Это похоже на то, как TodoMVC встречается с Twitter, встречается с eBay: пользователи будут записывать свои списки дел и продавать задания другим пользователям длиной не более 140 символов. Инвесторы очень рады. Вы очень взволнованы. Высокие пятерки вокруг. Вы собираетесь изменить мир, и вы будете делать это, используя все правильные технологии: Ember для клиентской части и Rails для серверной части!

Вот ваша первая пользовательская история:

cucumber
Given I am a user
When I go to the todos page
Then I should see a list of all todos

Начало работы с Ember Rails

Кусок пирога! Вы начинаете с установки последней версии Ember:

> npm install -g ember bower 
> ember new todo-frontend 
> cd todo-frontend

«Мне все равно, что говорит @dhh», — подумаете вы вслух. «Я разработчик Rockstar, поэтому я собираюсь протестировать его, как будто украл!» У вас еще нет API для тестирования, поэтому вы начинаете с него издеваться. Вы устанавливаете pretenderи создаете свой первый приемочный тест:

> ember install ember-cli-pretender 
> ember generate acceptance-test todos
// tests/acceptance/todos.js 
import Ember from 'ember'; 
import { module, test } from 'qunit'; 
import startApp from 'emberbook/tests/helpers/start-app'; 
import Pretender from 'pretender';

let TODOS = [ 
  {id: 1, title: "write a blog post"}, 
  {id: 2, title: "let people read it"}, 
  {id: 3, title: "... profit"} 
];

module('Acceptance | todos', { 
  beforeEach: function() { 
    this.application = startApp();

    this.server = new Pretender(function() {
      this.get('/todos', function(){
        var json = {
          todos: TODOS
        };

        return [200, {}, JSON.stringify(json)];
      });
    });
  },

  afterEach: function() { 
    Ember.run(this.application, 'destroy'); 
    this.server.shutdown(); 
  } 
});

test('visiting /todos', function(assert) { 
  visit('/todos');

  andThen(function() { 
    var title = find('h1'); 
    assert.equal(title.text(), 'Todo List');

    var todos = find('.todo-item');
    assert.equal(todos.length, TODOS.length);

    assert.equal(currentURL(), '/todos');
  }); 
});

Время запустить тестового бегуна и посмотреть, как все горит:

ember test --serve

Да, он красный, как дьявол. Первая ошибка, нет маршрута.

// app/router.js 
import Ember from 'ember'; 
import config from './config/environment';

var Router = Ember.Router.extend({ 
  location: config.locationType 
});

Router.map(function() { 
  this.route("todos"); 
});

export default Router;

Теперь тест не может найти текст «Список Todo». Вы также исправите это:

{{!- app/templates/todos.hbs }}

# Todo List

{{#each model as |todo|}}
  <p class='todo-item'>
    {{todo.title}}
  </p>
{{/each}}

Теперь тест перерывается, потому что на странице нет элементов списка дел. Вы двигаетесь вперед:

// app/routes/todos.js 
import Ember from 'ember';

export default Ember.Route.extend({ 
  model() { 
    return this.store.findAll("todo"); 
  } 
});

Не могу найти модель «todo». Еще одно исправление:

// app/models/todo.js 
import DS from 'ember-data';

export default DS.Model.extend({ 
  title: DS.attr(), 
  complete: DS.attr("boolean") 
});

Вау, это зеленый! Ты похлопываешь себя по спине. Но на самом деле вы не можете донести историю до тех пор, пока не появится API для ее поддержки. Пришло время взять последнюю версию Rails.

Тестирование приложения Ember Rails

Это будет API, а не полноценное приложение, так что вам нужно всего несколько гемов. Вы потратили несколько минут на обдумывание поста, который вы только что прочитали, о создании приложений Rails API и решении, использовать ли --apiфлаг Rails 5 . Тем не менее, вы хотите, чтобы этот продукт появился на рынке «вчера», поэтому вы просто выбираете то, что стабильно:

> gem install rails 
> rails new todo_api --skip-test-unit --skip-bundle 
> cd todo_api

Вы также пропустили, test-unitпотому что вы предпочитаете использовать красноречивый DSL Rspec для тестов этого проекта.

Время свести Gemfile к нескольким драгоценным камням, которые вам действительно нужны. Этот API должен обслуживать только JSON, поэтому все, что связано с конвейером ресурсов:

# Gemfile
source 'https://rubygems.org'

gem 'rails', '4.2.3' 
gem 'sqlite3' 
gem 'active_model_serializers'

group :development, :test do 
  gem 'rspec-rails' 
end

Связывайте его и будьте готовы написать некоторые спецификации!

> bundle install 
> rails generate rspec:install

Время, чтобы ударить первый запрос спецификации:

# spec/requests/todos_spec.rb
require "rails_helper"

RSpec.describe "Todos", :type => :request do

  # letBANG because `before {expected_todos}` looks funny 
  let!(:expected_todos) do 
    3.times.map do |i| 
      Todo.create(title: "Todo #{i+1}") 
    end 
  end

  it "lists all todos" do 
    get "/todos" 
    todos = extract_key_from_response("todos", response)   
    expect(todos.count).to eq expected_todos.count 
  end

  def extract_key_from_response(key, response) 
    json_response = JSON.parse(response.body) 
    expect(json_response).to have_key key 
    json_response[key] 
  end

end

Вы запускаете тест. Смотри, как он покраснел! Первое замечание: нет модели. Вы исправите это:

class Todo < ActiveRecord::Base
end

Время миграции!

<code class="language-bash rails generate migration CreateTodos title:string</code>
# db/migrate/#{timestamp}\_create\_todos.rb
class CreateTodos < ActiveRecord::Migration 
  def change 
    create_table :todos do |t| 
      t.string :title 
      t.timestamps null: false 
    end 
  end 
end
> rake db:migrate

И конечно, ему нужен маршрут.

# config/routes.rb
Rails.application.routes.draw do 
  resources :todos, only: :index 
end

Тест все еще красный, потому что нет контроллера. Следующее исправление:

# controllers/todos_controller.rb
class TodosController < ApplicationController 
  def index 
    render json: todos 
  end

  private

  def todos 
    Todo.all 
  end 
end

Все зеленые! Время загрузить приложение (я) и насладиться собственным творческим гением. Вы запускаете следующие команды в разных терминалах:

> rails server 
> ember server

Вы не можете ждать, чтобы увидеть ваш ребенок работает. Вы открываете «http: // local: 4200 / todos»… и получаете пустой экран. Теперь это было неутешительно. Оба набора тестов зеленого цвета. Что могло пойти не так?

После некоторой проверки проблемы вы замечаете GET http://localhost:4200/todos 404 (Not Found)в своих журналах. Ну дерьмо. Эмбер не знает, что API находится в другом домене. Ну, это легко исправить. Вам просто нужно указать хост в адаптере приложения. А поскольку вы привлекательны и умны, вы знаете лучше, чем жестко закодировать хост. В конце концов, это изменится для постановки и производства. Итак, вы открываете config/environment.jsприложение Ember и добавляете следующее:

// config/environment.js                                                                                             if (environment === 'test') {
  ENV.API_HOST = ''
} else {
  ENV.API_HOST = (process.env.API_HOST || 'http://localhost:3000')
}

Вы задаете пустую строку API_HOST тестовой среды, чтобы текущие тесты продолжали работать. Все остальные среды используют Rails по умолчанию, http://localhost:3000если не установлена API_HOSTпеременная среды. Теперь вы можете создать адаптер приложения и исправить ошибку:

// app/adapters/application.js 
import DS from 'ember-data'; 
import config from '../config/environment';

export default DS.RESTAdapter.extend({ 
  host: config.API_HOST 
});

W00t! Исправлена ​​эта проблема. Время проводить тесты. Еще раз, вы запускаете приложения Rails и Ember в двух разных терминалах.

> rails server 
> ember server

Черт возьми. Приложение все еще не работает. Вы не настроили CORS, чтобы современные браузеры могли общаться с Rails API. Вы добавляете стойки в Gemfile, связываете его и добавляете простейшую политику, которую вы можете придумать, чтобы все заработало. Разрешить все!

# Gemfile
gem 'rack-cors', :require => 'rack/cors'
bundle install
# config/application.rb
config.middleware.insert_before 0, "Rack::Cors", :debug => true, :logger => (-> { Rails.logger }) do 
  allow do 
    origins '*' 
    resource '*', 
      :headers => :any, 
      :methods => [:get, :post, :delete, :put, :options, :head], 
      :max_age => 0 
  end 
end

Вы скрещиваете пальцы, запускаете тесты и запускаете серверы. Когда вы посещаете todosмаршрут, вы видите, что ваше приложение работает. Уф! Даже если ваше приложение работает сейчас, вы не на 100 процентов счастливы. Вы провели последний часовой тест, управляя приложением, которое не работало, хотя все тесты были зелеными. И вы даже не сфокусировали юнит-тесты — все ваши тесты были интеграционными. Разве интеграционные тесты не должны предотвращать подобные вещи?

Попробуйте Codeship - самый простой сервис непрерывной доставки.

Написание тестов дыма для приложения Ember Rails

В этот момент вы помните пост в блоге Hashrocket о тестировании вождения приложений эликсира с огурцом . Вы решили попробовать эти методы на практике и написать несколько тестов на дым. Тип интеграционных тестов, которые были только что написаны, отлично подходит для тестирования приложений в отдельности. Эти изолированные интеграционные тесты работают очень быстро. А поскольку они работают быстро, большинство ваших тестов должны быть либо запросить спецификации, либо интеграцию с Ember. Нет причин иметь полное покрытие (тестирование каждой возможности) с помощью тестов дыма.

Однако, чтобы убедиться, что приложение действительно работает, должен быть как минимум один тест на дым для каждой конечной точки API. Зная, что огурцы-рельсы уже загружают вашу тестовую среду, вы думаете о простейшем решении, которое вы можете попробовать написать тест на дым. Просто добавьте огуречные рельсы, вручную запустите Ember и Rails, а затем перезапишите, как работает капибара. Это решение, которое вы придумали. Вы добавляете огурец в Gemfile и устанавливаете его.

# Gemfile
group :development, :test do 
  gem 'cucumber-rails', require: false 
  gem 'database_cleaner' 
  gem 'capybara-webkit' # ... 
end
> bundle install 
> rails generate cucumber:install

Затем вы перезаписываете поведение капибары.

# features/support/env.rb
Capybara.configure do |config| 
  # Don't start rails 
  config.run_server = false 
  # Set capybara's driver. Use your own favorite 
  config.default_driver = :webkit 
  # Make all requests to the Ember app 
  config.app_host = 'http://localhost:4200' 
end

И вы пишете первую характеристику и определения шагов.

# features/todo.feature
Feature: Todos 
  When a user visits "/todos", they should see all todos

  Scenario: User views todos 
    Given 4 todos 
    When I visit "/todos" 
    Then I should see "Todo List" 
    And I see 4 todos
# features/steps/todo.rb
Given(/^(\d+) todos$/) do |num| 
  num = num.to_i 
  num.times do |i| 
    Todo.create(title: "Title #{i}") 
  end 
end

When(/^I visit "(.*?)"$/) do |path| 
  visit path 
end

Then(/^I should see "(.*?)"$/) do |text| 
  expect(page).to have_text(text) 
end

Then(/^I see (\d+) todos$/) do |num| 
  expect(all(".todo-item").length).to eq num.to_i 
end

Теперь в отдельных терминалах вы запускаете как Rails, так и Ember. На этот раз Rails запускается в тестовой среде, поэтому запросы Ember попадают в тестовую базу данных Rails.

> rails server --environment test 
> ember server

Вы запускаете тестовый набор с rake, и вуаля! Вы видите, что набор тестов проходит. Вы знаете, что и Ember, и Rails общаются друг с другом. И хотя вы очень гордитесь собой, вы не счастливы. «Серверы должны запускаться автоматически. Почему я не могу просто напечатать rake? ты думаешь про себя. Вот первое решение, которое вы придумали.

# features/support/env.rb
ember_pid = fork do 
  puts "Starting Ember App" 
  Dir.chdir("/your/user/directory/todo-frontend") do 
    exec({"API_HOST" => "http://localhost:3001"}, "ember server --port 4201 --live-reload false") 
  end 
end 

rails_pid = fork do 
  puts "Starting Rails App" 
  Dir.chdir(Rails.root) do 
    exec("rails server --port 3001 --environment test") 
  end 
end 

sleep 2

at_exit do #kill the Ember and Rails apps 
  puts("Shutting down Ember and Rails App") 
  Process.kill "KILL", rails_pid 
  Process.kill "KILL", ember_pid 
end

Capybara.configure do |config| 
  config.run_server = false 
  config.default_driver = :webkit 
  config.app_host = 'http://localhost:4201' 
end

Это решение работает хорошо. Rails и Ember работают на отдельных портах, поэтому рабочие версии двух серверов могут работать. Единственная проблема — это sleep 2. По мере роста приложений Rails и Ember будет расти время, необходимое для запуска серверов. Также это число волшебно; это может занять больше времени на другой машине. Сколько времени это займет на сервере CI?

Что вы действительно хотите сделать, так это остановить тесты, пока не узнаете, что Rails и Ember работают. Однако после некоторого расследования вы понимаете, что нет способа узнать, успешно ли работает Ember. Но потом вы замечаете, как дым EmberCLI сам себя тестирует .

  it('ember new foo, server, SIGINT clears tmp/', function() {
    return runCommand(path.join('.', 'node_modules', 'ember-cli', 'bin', 'ember'), 'server', '--port=54323','--live-reload=false', {
        onOutput: function(string, child) {
          if (string.match(/Build successful/)) {
            killCliProcess(child);
          }
        }
      })
      .catch(function() {
        // just eat the rejection as we are testing what happens
      });
  });

Теперь вы знаете, что Ember загрузился, когда выдает «Build success», и знаете, как вы можете ждать Rails.

# features/support/env.rb
begin 
  DatabaseCleaner.strategy = :truncation 
rescue NameError 
  raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." 
end

ember_server = nil 
rails_server = nil

Dir.chdir("/Users/mwoods/hashrocket/todo-frontend") do 
  ember_server = IO.popen([{"API_HOST" => "http://localhost:3001"}, "ember", "server", "--port", "4201", "--live-reload", "false", :err => [:child, :out]]) 
end

Dir.chdir(Rails.root) do 
  rails_server = IO.popen(['rails', 'server', '--port', '3001', '--environment', 'test', :err => [:child, :out]]) 
end

# Before timeout loop to prevent orphaning processes
at_exit do 
  Process.kill 9, rails_server.pid, ember_server.pid 
end

# if it takes longer than 30 seconds to boot throw an error
Timeout::timeout(30) do 
  # wait for the magic words from ember  
  while running = ember_server.gets 
    if running =~ /build successful/i 
      break 
    end 
  end

  # when rails starts logging, it's running 
  while running = rails_server.gets 
    if running =~ /info/i 
      break 
    end 
  end 
end

Capybara.configure do |config| 
  config.run_server = false 
  config.default_driver = :webkit 
  config.app_host = 'http://localhost:4201' 
end

Это не идеальное решение, но достаточно для первого дня. На второй день вы можете перенести тесты дыма в свое хранилище. В конце концов, они не являются специфичными для Rails, поэтому они, вероятно, должны быть отдельным проектом.

Подобные и многие другие проблемы случались со мной. Вот почему я представляю выхлоп , новый драгоценный камень, который, мы надеемся, снимает некоторую головную боль, связанную с тестированием дыма в стеке Ember + Rails. Наслаждайтесь!