diff --git a/.rspec b/.rspec
new file mode 100644
index 00000000..c99d2e73
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--require spec_helper
diff --git a/.ruby-version b/.ruby-version
index ec1cf33c..57cf282e 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.6.3
+2.6.5
diff --git a/Gemfile b/Gemfile
index e20b1260..b78612af 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,16 +1,27 @@
 source 'https://rubygems.org'
 git_source(:github) { |repo| "https://github.com/#{repo}.git" }
 
-ruby '2.6.3'
+ruby '2.6.5'
 
 gem 'rails', '~> 5.2.3'
 gem 'pg', '>= 0.18', '< 2.0'
 gem 'puma', '~> 3.11'
 gem 'bootsnap', '>= 1.1.0', require: false
+gem 'oj'
+gem 'activerecord-import'
 
 group :development, :test do
   # Call 'byebug' anywhere in the code to stop execution and get a debugger console
   gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+  gem 'rspec-rails'
+  gem 'factory_bot_rails'
+  gem 'faker'
+  gem 'rails-controller-testing'
+  gem 'capybara'
+  gem 'rspec-benchmark'
+  gem 'pghero'
+  gem 'pry'
+  gem 'rack-mini-profiler'
 end
 
 group :development do
diff --git a/Gemfile.lock b/Gemfile.lock
index fccf6f5f..6378f33b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -33,6 +33,8 @@ GEM
       activemodel (= 5.2.3)
       activesupport (= 5.2.3)
       arel (>= 9.0)
+    activerecord-import (1.0.4)
+      activerecord (>= 3.2)
     activestorage (5.2.3)
       actionpack (= 5.2.3)
       activerecord (= 5.2.3)
@@ -42,15 +44,37 @@ GEM
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
       tzinfo (~> 1.1)
+    addressable (2.7.0)
+      public_suffix (>= 2.0.2, < 5.0)
     arel (9.0.0)
+    benchmark-malloc (0.1.0)
+    benchmark-perf (0.5.0)
+    benchmark-trend (0.3.0)
     bindex (0.6.0)
     bootsnap (1.4.2)
       msgpack (~> 1.0)
     builder (3.2.3)
     byebug (11.0.1)
+    capybara (3.31.0)
+      addressable
+      mini_mime (>= 0.1.3)
+      nokogiri (~> 1.8)
+      rack (>= 1.6.0)
+      rack-test (>= 0.6.3)
+      regexp_parser (~> 1.5)
+      xpath (~> 3.2)
+    coderay (1.1.2)
     concurrent-ruby (1.1.5)
     crass (1.0.4)
+    diff-lcs (1.3)
     erubi (1.8.0)
+    factory_bot (5.1.1)
+      activesupport (>= 4.2.0)
+    factory_bot_rails (5.1.1)
+      factory_bot (~> 5.1.0)
+      railties (>= 4.2.0)
+    faker (2.10.2)
+      i18n (>= 1.6, < 2)
     ffi (1.10.0)
     globalid (0.4.2)
       activesupport (>= 4.2.0)
@@ -76,9 +100,18 @@ GEM
     nio4r (2.3.1)
     nokogiri (1.10.2)
       mini_portile2 (~> 2.4.0)
+    oj (3.10.2)
     pg (1.1.4)
+    pghero (2.4.1)
+      activerecord (>= 5)
+    pry (0.12.2)
+      coderay (~> 1.1.0)
+      method_source (~> 0.9.0)
+    public_suffix (4.0.3)
     puma (3.12.1)
     rack (2.0.6)
+    rack-mini-profiler (1.1.6)
+      rack (>= 1.2.0)
     rack-test (1.1.0)
       rack (>= 1.0, < 3)
     rails (5.2.3)
@@ -94,6 +127,10 @@ GEM
       bundler (>= 1.3.0)
       railties (= 5.2.3)
       sprockets-rails (>= 2.0.0)
+    rails-controller-testing (1.0.4)
+      actionpack (>= 5.0.1.x)
+      actionview (>= 5.0.1.x)
+      activesupport (>= 5.0.1.x)
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
@@ -109,6 +146,33 @@ GEM
     rb-fsevent (0.10.3)
     rb-inotify (0.10.0)
       ffi (~> 1.0)
+    regexp_parser (1.6.0)
+    rspec (3.9.0)
+      rspec-core (~> 3.9.0)
+      rspec-expectations (~> 3.9.0)
+      rspec-mocks (~> 3.9.0)
+    rspec-benchmark (0.5.1)
+      benchmark-malloc (~> 0.1.0)
+      benchmark-perf (~> 0.5.0)
+      benchmark-trend (~> 0.3.0)
+      rspec (>= 3.0.0, < 4.0.0)
+    rspec-core (3.9.1)
+      rspec-support (~> 3.9.1)
+    rspec-expectations (3.9.0)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.9.0)
+    rspec-mocks (3.9.1)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.9.0)
+    rspec-rails (3.9.0)
+      actionpack (>= 3.0)
+      activesupport (>= 3.0)
+      railties (>= 3.0)
+      rspec-core (~> 3.9.0)
+      rspec-expectations (~> 3.9.0)
+      rspec-mocks (~> 3.9.0)
+      rspec-support (~> 3.9.0)
+    rspec-support (3.9.2)
     ruby_dep (1.5.0)
     sprockets (3.7.2)
       concurrent-ruby (~> 1.0)
@@ -129,22 +193,35 @@ GEM
     websocket-driver (0.7.0)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.3)
+    xpath (3.2.0)
+      nokogiri (~> 1.8)
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  activerecord-import
   bootsnap (>= 1.1.0)
   byebug
+  capybara
+  factory_bot_rails
+  faker
   listen (>= 3.0.5, < 3.2)
+  oj
   pg (>= 0.18, < 2.0)
+  pghero
+  pry
   puma (~> 3.11)
+  rack-mini-profiler
   rails (~> 5.2.3)
+  rails-controller-testing
+  rspec-benchmark
+  rspec-rails
   tzinfo-data
   web-console (>= 3.3.0)
 
 RUBY VERSION
-   ruby 2.6.3p62
+   ruby 2.6.5p114
 
 BUNDLED WITH
-   2.0.2
+   2.1.4
diff --git a/app/controllers/trips_controller.rb b/app/controllers/trips_controller.rb
index acb38be2..1431925c 100644
--- a/app/controllers/trips_controller.rb
+++ b/app/controllers/trips_controller.rb
@@ -2,6 +2,6 @@ class TripsController < ApplicationController
   def index
     @from = City.find_by_name!(params[:from])
     @to = City.find_by_name!(params[:to])
-    @trips = Trip.where(from: @from, to: @to).order(:start_time)
+    @trips = Trip.includes(bus: :services).where(from: @from, to: @to).order(:start_time)
   end
 end
diff --git a/app/models/bus.rb b/app/models/bus.rb
index 1dcc54cb..98e9f010 100644
--- a/app/models/bus.rb
+++ b/app/models/bus.rb
@@ -13,8 +13,13 @@ class Bus < ApplicationRecord
   ].freeze
 
   has_many :trips
-  has_and_belongs_to_many :services, join_table: :buses_services
+  has_many :buses_services
+  has_many :services, through: :buses_services
 
   validates :number, presence: true, uniqueness: true
   validates :model, inclusion: { in: MODELS }
+
+  def services_names
+    services.pluck(:name)
+  end
 end
diff --git a/app/models/buses_service.rb b/app/models/buses_service.rb
new file mode 100644
index 00000000..6219d44e
--- /dev/null
+++ b/app/models/buses_service.rb
@@ -0,0 +1,4 @@
+class BusesService < ApplicationRecord
+  belongs_to :bus
+  belongs_to :service
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index 9cbb2a32..1781543c 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -12,7 +12,8 @@ class Service < ApplicationRecord
     'Можно не печатать билет',
   ].freeze
 
-  has_and_belongs_to_many :buses, join_table: :buses_services
+  has_many :buses_services
+  has_many :buses, through: :buses_services
 
   validates :name, presence: true
   validates :name, inclusion: { in: SERVICES }
diff --git a/app/services/reload_json.rb b/app/services/reload_json.rb
new file mode 100644
index 00000000..8416e29f
--- /dev/null
+++ b/app/services/reload_json.rb
@@ -0,0 +1,78 @@
+class ReloadJson
+  class << self
+    def call(file_name)
+      new.call(file_name)
+    end
+  end
+
+  attr_reader :cities, :services, :buses, :trips
+
+  def initialize
+    @cities = {}
+    @services = {}
+    @buses = {}
+    @trips = []
+  end
+
+  def call(file_name)
+    json = Oj.load(File.read(file_name))
+
+    ActiveRecord::Base.transaction do
+      Trip.delete_all
+      City.delete_all
+      BusesService.delete_all
+      Bus.delete_all
+      Service.delete_all
+
+      json.each do |trip|
+        from = find_or_create_city(trip['from'])
+        to = find_or_create_city(trip['to'])
+
+        bus_services = []
+
+        trip['bus']['services'].each do |service|
+          bus_services << find_or_create_service(service)
+        end
+
+        bus = find_or_create_bus(
+          trip['bus']['number'], trip['bus']['model'], bus_services
+        )
+
+        trips << {
+          from_id: from.id,
+          to_id: to.id,
+          bus_id: bus.id,
+          start_time: trip['start_time'],
+          duration_minutes: trip['duration_minutes'],
+          price_cents: trip['price_cents'],
+        }
+      end
+
+      Trip.import trips, batch_size: 1000
+    end
+  end
+
+  private
+
+  def find_or_create_city(name)
+    return cities[name] if cities.key?(name)
+
+    cities[name] = City.create(name: name)
+  end
+
+  def find_or_create_service(name)
+    return services[name] if services.key?(name)
+
+    services[name] = Service.create(name: name)
+  end
+
+  def find_or_create_bus(number, model, services)
+    return buses[number] if buses.key?(number)
+
+    buses[number] = Bus.create(
+      number: number,
+      model: model,
+      services: services
+    )
+  end
+end
diff --git a/app/views/trips/index.html.erb b/app/views/trips/index.html.erb
index a60bce41..14e12166 100644
--- a/app/views/trips/index.html.erb
+++ b/app/views/trips/index.html.erb
@@ -2,14 +2,14 @@
   <%= "Автобусы #{@from.name} – #{@to.name}" %>
 </h1>
 <h2>
-  <%= "В расписании #{@trips.count} рейсов" %>
+  <%= "В расписании #{@trips.size} рейсов" %>
 </h2>
 
 <% @trips.each do |trip| %>
   <ul>
-    <%= render "trip", trip: trip %>
-    <% if trip.bus.services.present? %>
-      <%= render "services", services: trip.bus.services %>
+    <%= render trip %>
+    <% trip.bus.services_names.each do |name| %>
+      <li><%= "#{name}" %></li>
     <% end %>
   </ul>
   <%= render "delimiter" %>
diff --git a/config/database.yml b/config/database.yml
index e116cfa6..b61dd8b2 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -17,6 +17,9 @@
 default: &default
   adapter: postgresql
   encoding: unicode
+  username: postgres
+  password: 12345678
+  host: localhost
   # For details on connection pooling, see Rails configuration guide
   # http://guides.rubyonrails.org/configuring.html#database-pooling
   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
diff --git a/config/routes.rb b/config/routes.rb
index a2da6a7b..92916b51 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,4 +2,6 @@
   # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
   get "/" => "statistics#index"
   get "автобусы/:from/:to" => "trips#index"
+
+  mount PgHero::Engine, at: "pghero"
 end
diff --git a/db/migrate/20200218193515_create_pghero_query_stats.rb b/db/migrate/20200218193515_create_pghero_query_stats.rb
new file mode 100644
index 00000000..fbf41263
--- /dev/null
+++ b/db/migrate/20200218193515_create_pghero_query_stats.rb
@@ -0,0 +1,15 @@
+class CreatePgheroQueryStats < ActiveRecord::Migration[5.2]
+  def change
+    create_table :pghero_query_stats do |t|
+      t.text :database
+      t.text :user
+      t.text :query
+      t.integer :query_hash, limit: 8
+      t.float :total_time
+      t.integer :calls, limit: 8
+      t.timestamp :captured_at
+    end
+
+    add_index :pghero_query_stats, [:database, :captured_at]
+  end
+end
diff --git a/db/migrate/20200218193618_create_pghero_space_stats.rb b/db/migrate/20200218193618_create_pghero_space_stats.rb
new file mode 100644
index 00000000..6198a6f8
--- /dev/null
+++ b/db/migrate/20200218193618_create_pghero_space_stats.rb
@@ -0,0 +1,13 @@
+class CreatePgheroSpaceStats < ActiveRecord::Migration[5.2]
+  def change
+    create_table :pghero_space_stats do |t|
+      t.text :database
+      t.text :schema
+      t.text :relation
+      t.integer :size, limit: 8
+      t.timestamp :captured_at
+    end
+
+    add_index :pghero_space_stats, [:database, :captured_at]
+  end
+end
diff --git a/db/migrate/20200219193035_add_uniq_index_to_city_name.rb b/db/migrate/20200219193035_add_uniq_index_to_city_name.rb
new file mode 100644
index 00000000..d02993e1
--- /dev/null
+++ b/db/migrate/20200219193035_add_uniq_index_to_city_name.rb
@@ -0,0 +1,5 @@
+class AddUniqIndexToCityName < ActiveRecord::Migration[5.2]
+  def change
+    add_index :cities, :name, unique: true
+  end
+end
diff --git a/db/migrate/20200219193338_add_uniq_index_to_bus_number.rb b/db/migrate/20200219193338_add_uniq_index_to_bus_number.rb
new file mode 100644
index 00000000..c058c811
--- /dev/null
+++ b/db/migrate/20200219193338_add_uniq_index_to_bus_number.rb
@@ -0,0 +1,5 @@
+class AddUniqIndexToBusNumber < ActiveRecord::Migration[5.2]
+  def change
+    add_index :buses, :number, unique: true
+  end
+end
diff --git a/db/migrate/20200219193829_add_uniq_index_to_service_name.rb b/db/migrate/20200219193829_add_uniq_index_to_service_name.rb
new file mode 100644
index 00000000..0e313fb4
--- /dev/null
+++ b/db/migrate/20200219193829_add_uniq_index_to_service_name.rb
@@ -0,0 +1,5 @@
+class AddUniqIndexToServiceName < ActiveRecord::Migration[5.2]
+  def change
+    add_index :services, :name, unique: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f6921e45..9f656b29 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,14 +10,16 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2019_03_30_193044) do
+ActiveRecord::Schema.define(version: 2020_02_19_193829) do
 
   # These are extensions that must be enabled in order to support this database
+  enable_extension "pg_stat_statements"
   enable_extension "plpgsql"
 
   create_table "buses", force: :cascade do |t|
     t.string "number"
     t.string "model"
+    t.index ["number"], name: "index_buses_on_number", unique: true
   end
 
   create_table "buses_services", force: :cascade do |t|
@@ -27,10 +29,32 @@
 
   create_table "cities", force: :cascade do |t|
     t.string "name"
+    t.index ["name"], name: "index_cities_on_name", unique: true
+  end
+
+  create_table "pghero_query_stats", force: :cascade do |t|
+    t.text "database"
+    t.text "user"
+    t.text "query"
+    t.bigint "query_hash"
+    t.float "total_time"
+    t.bigint "calls"
+    t.datetime "captured_at"
+    t.index ["database", "captured_at"], name: "index_pghero_query_stats_on_database_and_captured_at"
+  end
+
+  create_table "pghero_space_stats", force: :cascade do |t|
+    t.text "database"
+    t.text "schema"
+    t.text "relation"
+    t.bigint "size"
+    t.datetime "captured_at"
+    t.index ["database", "captured_at"], name: "index_pghero_space_stats_on_database_and_captured_at"
   end
 
   create_table "services", force: :cascade do |t|
     t.string "name"
+    t.index ["name"], name: "index_services_on_name", unique: true
   end
 
   create_table "trips", force: :cascade do |t|
diff --git a/lib/tasks/utils.rake b/lib/tasks/utils.rake
index 540fe871..95389006 100644
--- a/lib/tasks/utils.rake
+++ b/lib/tasks/utils.rake
@@ -1,34 +1,11 @@
 # Наивная загрузка данных из json-файла в БД
 # rake reload_json[fixtures/small.json]
-task :reload_json, [:file_name] => :environment do |_task, args|
-  json = JSON.parse(File.read(args.file_name))
-
-  ActiveRecord::Base.transaction do
-    City.delete_all
-    Bus.delete_all
-    Service.delete_all
-    Trip.delete_all
-    ActiveRecord::Base.connection.execute('delete from buses_services;')
-
-    json.each do |trip|
-      from = City.find_or_create_by(name: trip['from'])
-      to = City.find_or_create_by(name: trip['to'])
-      services = []
-      trip['bus']['services'].each do |service|
-        s = Service.find_or_create_by(name: service)
-        services << s
-      end
-      bus = Bus.find_or_create_by(number: trip['bus']['number'])
-      bus.update(model: trip['bus']['model'], services: services)
+require 'benchmark'
 
-      Trip.create!(
-        from: from,
-        to: to,
-        bus: bus,
-        start_time: trip['start_time'],
-        duration_minutes: trip['duration_minutes'],
-        price_cents: trip['price_cents'],
-      )
-    end
+task :reload_json, [:file_name] => :environment do |_task, args|
+  time = Benchmark.realtime do
+    ReloadJson.call(args.file_name)
   end
+
+  puts "Finish in #{time.round(2)}"
 end
diff --git a/spec/controllers/trips_controller_spec.rb b/spec/controllers/trips_controller_spec.rb
new file mode 100644
index 00000000..9d87ad05
--- /dev/null
+++ b/spec/controllers/trips_controller_spec.rb
@@ -0,0 +1,32 @@
+require 'rails_helper'
+
+RSpec.describe TripsController, type: :controller do
+  let(:bus) { FactoryBot.create(:bus) }
+  let(:from_city) { FactoryBot.create(:city) }
+  let(:to_city) { FactoryBot.create(:city) }
+  let!(:trips) { FactoryBot.create_list(:trip, 3, from_id: from_city.id, to_id: to_city.id, bus_id: bus.id) }
+
+  describe 'GET #index' do
+    before { get :index, params: { from: from_city.name, to: to_city.name } }
+
+    it 'Возврат http success' do
+      expect(response).to have_http_status :success
+    end
+
+    it 'Переменная @from содержит город из параметра from' do
+      expect(assigns(:from)).to eq from_city
+    end
+
+    it 'Переменная @to содержит город из параметра to' do
+      expect(assigns(:to)).to eq to_city
+    end
+
+    it 'Возврат массива маршрутов' do
+      expect(assigns(:trips)).to match_array(trips)
+    end
+
+    it 'Возврат index шаблона' do
+      expect(response).to render_template :index
+    end
+  end
+end
diff --git a/spec/factories/bus.rb b/spec/factories/bus.rb
new file mode 100644
index 00000000..7431ab5f
--- /dev/null
+++ b/spec/factories/bus.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+  factory :bus do
+    number { Faker::Number.number(digits: 10) }
+    model { 'Сканиа' }
+  end
+end
diff --git a/spec/factories/city.rb b/spec/factories/city.rb
new file mode 100644
index 00000000..34c1399c
--- /dev/null
+++ b/spec/factories/city.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+  factory :city do
+    name { Faker::Address.city.delete(' ') }
+  end
+end
diff --git a/spec/factories/service.rb b/spec/factories/service.rb
new file mode 100644
index 00000000..fb1ae28d
--- /dev/null
+++ b/spec/factories/service.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+  factory :service do
+    name { 'WiFi' }
+  end
+end
diff --git a/spec/factories/trip.rb b/spec/factories/trip.rb
new file mode 100644
index 00000000..d706a798
--- /dev/null
+++ b/spec/factories/trip.rb
@@ -0,0 +1,10 @@
+FactoryBot.define do
+  factory :trip do
+    from_id { nil }
+    to_id { nil }
+    start_time { '11:00' }
+    duration_minutes { 168 }
+    price_cents { 173 }
+    bus_id { nil }
+  end
+end
diff --git a/spec/features/show_trips_list_spec.rb b/spec/features/show_trips_list_spec.rb
new file mode 100644
index 00000000..30068c38
--- /dev/null
+++ b/spec/features/show_trips_list_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+feature 'Просмотр списка маршрутов' do
+  given(:bus) { FactoryBot.create(:bus) }
+  given(:from_city) { FactoryBot.create(:city) }
+  given(:to_city) { FactoryBot.create(:city) }
+  given!(:trips) { FactoryBot.create_list(:trip, 3, from_id: from_city.id, to_id: to_city.id, bus_id: bus.id) }
+
+  scenario 'Пользователь открывает список маршрутов' do
+    visit "#{URI.encode('автобусы')}/#{from_city.name}/#{to_city.name}"
+
+    expect(page).to have_content from_city.name
+    expect(page).to have_content from_city.name
+    expect(page).to have_content "В расписании #{trips.size} рейсов"
+
+    trips.each do |trip|
+      expect(page).to have_content "Отправление: #{trip.start_time}"
+      expect(page).to have_content "Прибытие: #{(Time.parse(trip.start_time) + trip.duration_minutes.minutes).strftime('%H:%M')}"
+      expect(page).to have_content "В пути: #{trip.duration_minutes / 60}ч. #{trip.duration_minutes % 60}мин."
+      expect(page).to have_content "Цена: #{trip.price_cents / 100}р. #{trip.price_cents % 100}коп."
+      expect(page).to have_content "Автобус: #{trip.bus.model} №#{trip.bus.number}"
+    end
+  end
+end
diff --git a/spec/models/city_spec.rb b/spec/models/city_spec.rb
new file mode 100644
index 00000000..88cc1880
--- /dev/null
+++ b/spec/models/city_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+RSpec.describe City, type: :model do
+  describe 'Валидация названия города' do
+    context 'Название содержит пробелы' do
+      let(:city) { FactoryBot.build(:city, name: 'Новые Васюки') }
+
+      it 'Появляется ошибка' do
+        city.save
+        expect(city.errors.messages).to eq({ name: ["has spaces"] })
+      end
+    end
+
+    context 'Название содержит не робелы' do
+      let!(:city) { FactoryBot.create(:city) }
+
+      it 'Модель сохраняется' do
+        expect(city.persisted?).to eq true
+      end
+    end
+  end
+end
diff --git a/spec/models/trip_spec.rb b/spec/models/trip_spec.rb
new file mode 100644
index 00000000..fbe83763
--- /dev/null
+++ b/spec/models/trip_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+RSpec.describe Trip, type: :model do
+  let(:service) { FactoryBot.create(:service) }
+  let(:bus) { FactoryBot.create(:bus, services: [service]) }
+  let(:from_city) { FactoryBot.create(:city) }
+  let(:to_city) { FactoryBot.create(:city) }
+  let(:trip) { FactoryBot.create(:trip, from_id: from_city.id, to_id: to_city.id, bus_id: bus.id) }
+
+  describe '#to_h' do
+    it 'Сериализованный маршрут' do
+      expect(trip.to_h).to eq({
+        from: from_city.name,
+        to: to_city.name,
+        start_time: trip.start_time,
+        duration_minutes: trip.duration_minutes,
+        price_cents: trip.price_cents,
+        bus: {
+          number: bus.number,
+          model: bus.model,
+          services: bus.services.map(&:name),
+        }
+      })
+    end
+  end
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 00000000..01d20622
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1,67 @@
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+require 'spec_helper'
+ENV['RAILS_ENV'] ||= 'test'
+
+require File.expand_path('../config/environment', __dir__)
+
+# Prevent database truncation if the environment is production
+abort("The Rails environment is running in production mode!") if Rails.env.production?
+require 'rspec/rails'
+require 'capybara/rails'
+# Add additional requires below this line. Rails is not loaded until this point!
+
+# Requires supporting ruby files with custom matchers and macros, etc, in
+# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
+# run as spec files by default. This means that files in spec/support that end
+# in _spec.rb will both be required and run as specs, causing the specs to be
+# run twice. It is recommended that you do not name files matching this glob to
+# end with _spec.rb. You can configure this pattern with the --pattern
+# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
+#
+# The following line is provided for convenience purposes. It has the downside
+# of increasing the boot-up time by auto-requiring all files in the support
+# directory. Alternatively, in the individual `*_spec.rb` files, manually
+# require only the support files necessary.
+#
+# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
+
+# Checks for pending migrations and applies them before tests are run.
+# If you are not using ActiveRecord, you can remove these lines.
+begin
+  ActiveRecord::Migration.maintain_test_schema!
+rescue ActiveRecord::PendingMigrationError => e
+  puts e.to_s.strip
+  exit 1
+end
+
+RSpec.configure do |config|
+  config.include RSpec::Benchmark::Matchers
+
+  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
+  config.fixture_path = "#{::Rails.root}/fixtures"
+
+  # If you're not using ActiveRecord, or you'd prefer not to run each of your
+  # examples within a transaction, remove the following line or assign false
+  # instead of true.
+  config.use_transactional_fixtures = true
+
+  # RSpec Rails can automatically mix in different behaviours to your tests
+  # based on their file location, for example enabling you to call `get` and
+  # `post` in specs under `spec/controllers`.
+  #
+  # You can disable this behaviour by removing the line below, and instead
+  # explicitly tag your specs with their type, e.g.:
+  #
+  #     RSpec.describe UsersController, :type => :controller do
+  #       # ...
+  #     end
+  #
+  # The different available types are documented in the features, such as in
+  # https://relishapp.com/rspec/rspec-rails/docs
+  config.infer_spec_type_from_file_location!
+
+  # Filter lines from Rails gems in backtraces.
+  config.filter_rails_from_backtrace!
+  # arbitrary gems may also be filtered via:
+  # config.filter_gems_from_backtrace("gem name")
+end
diff --git a/spec/services/reload_json_spec.rb b/spec/services/reload_json_spec.rb
new file mode 100644
index 00000000..07222f83
--- /dev/null
+++ b/spec/services/reload_json_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+RSpec.describe ReloadJson, type: :service do
+  let(:file_name) { "#{Rails.root}/fixtures/example.json" }
+
+  it 'Обработка выполняется не более 100 мс' do
+    expect do
+      described_class.call(file_name)
+    end.to perform_under(100).ms.warmup(2).times.sample(2).times
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 00000000..ce33d66d
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,96 @@
+# This file was generated by the `rails generate rspec:install` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+  # rspec-expectations config goes here. You can use an alternate
+  # assertion/expectation library such as wrong or the stdlib/minitest
+  # assertions if you prefer.
+  config.expect_with :rspec do |expectations|
+    # This option will default to `true` in RSpec 4. It makes the `description`
+    # and `failure_message` of custom matchers include text for helper methods
+    # defined using `chain`, e.g.:
+    #     be_bigger_than(2).and_smaller_than(4).description
+    #     # => "be bigger than 2 and smaller than 4"
+    # ...rather than:
+    #     # => "be bigger than 2"
+    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+  end
+
+  # rspec-mocks config goes here. You can use an alternate test double
+  # library (such as bogus or mocha) by changing the `mock_with` option here.
+  config.mock_with :rspec do |mocks|
+    # Prevents you from mocking or stubbing a method that does not exist on
+    # a real object. This is generally recommended, and will default to
+    # `true` in RSpec 4.
+    mocks.verify_partial_doubles = true
+  end
+
+  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+  # have no way to turn it off -- the option exists only for backwards
+  # compatibility in RSpec 3). It causes shared context metadata to be
+  # inherited by the metadata hash of host groups and examples, rather than
+  # triggering implicit auto-inclusion in groups with matching metadata.
+  config.shared_context_metadata_behavior = :apply_to_host_groups
+
+# The settings below are suggested to provide a good initial experience
+# with RSpec, but feel free to customize to your heart's content.
+=begin
+  # This allows you to limit a spec run to individual examples or groups
+  # you care about by tagging them with `:focus` metadata. When nothing
+  # is tagged with `:focus`, all examples get run. RSpec also provides
+  # aliases for `it`, `describe`, and `context` that include `:focus`
+  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+  config.filter_run_when_matching :focus
+
+  # Allows RSpec to persist some state between runs in order to support
+  # the `--only-failures` and `--next-failure` CLI options. We recommend
+  # you configure your source control system to ignore this file.
+  config.example_status_persistence_file_path = "spec/examples.txt"
+
+  # Limits the available syntax to the non-monkey patched syntax that is
+  # recommended. For more details, see:
+  #   - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+  #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+  #   - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
+  config.disable_monkey_patching!
+
+  # Many RSpec users commonly either run the entire suite or an individual
+  # file, and it's useful to allow more verbose output when running an
+  # individual spec file.
+  if config.files_to_run.one?
+    # Use the documentation formatter for detailed output,
+    # unless a formatter has already been configured
+    # (e.g. via a command-line flag).
+    config.default_formatter = "doc"
+  end
+
+  # Print the 10 slowest examples and example groups at the
+  # end of the spec run, to help surface which specs are running
+  # particularly slow.
+  config.profile_examples = 10
+
+  # Run specs in random order to surface order dependencies. If you find an
+  # order dependency and want to debug it, you can fix the order by providing
+  # the seed, which is printed after each run.
+  #     --seed 1234
+  config.order = :random
+
+  # Seed global randomization in this process using the `--seed` CLI option.
+  # Setting this allows you to use `--seed` to deterministically reproduce
+  # test failures related to randomization by passing the same `--seed` value
+  # as the one that triggered the failure.
+  Kernel.srand config.seed
+=end
+end
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
deleted file mode 100644
index d19212ab..00000000
--- a/test/application_system_test_case.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require "test_helper"
-
-class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
-  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
-end
diff --git a/test/controllers/.keep b/test/controllers/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/fixtures/.keep b/test/fixtures/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/helpers/.keep b/test/helpers/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/integration/.keep b/test/integration/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/mailers/.keep b/test/mailers/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/models/.keep b/test/models/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/system/.keep b/test/system/.keep
deleted file mode 100644
index e69de29b..00000000
diff --git a/test/test_helper.rb b/test/test_helper.rb
deleted file mode 100644
index 3ab84e3d..00000000
--- a/test/test_helper.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-ENV['RAILS_ENV'] ||= 'test'
-require_relative '../config/environment'
-require 'rails/test_help'
-
-class ActiveSupport::TestCase
-  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
-  fixtures :all
-
-  # Add more helper methods to be used by all tests here...
-end