Ruby on Rails - Why Use Tests

Ruby on Rails - Why use tests?

Shouldn't
the validations in rails just work? It
seems more like testing the framework
than testing the your code. Why would
you need to test validations?

The validations in Rails do work -- in fact, there are unit tests in the Rails codebase to ensure it. When you test a model's validation, you're testing the specifics of the validation: the length, the accepted values, etc. You're making sure the code was written as intended. Some validations are simple helpers and you may opt not to test them on the notion that "no one can mess up a validates_numericality_of call." Is that true? Does every developer always remember to write it in the first place? Does every developer never accidentally delete a line on a bad copy paste? In my personal opinion, you don't need to test every last combination of values for a Rails' validation helper, but you need a line to test that it's there with the right values passed, just in case some punk changes it in the future without proper forethought.

Further, other validations are more complex, requiring lots of custom code -- they may warrant more thorough testing.

Furthermore, the tests seem super
fragile to any change in your code. So
if you change anything in your models,
you have to change your tests and
fixtures to match. Doesn't this
violate the DRY principle?

I don't believe it violates DRY. They're communicating (that's what programming is, communication) two very different things. The test says the code should do something. The code says what it actually does. Testing is extremely important when there is a disconnect between those things.

Test code and application code are intimately linked, obviously. I think of them as two sides of a coin. You wouldn't want a front without a back, or a back without a front. Good test code reinforces good application code, and vice versa. The two together are used to understand the whole problem that you're trying to solve. And well written test code is documentation -- it shows how the application code should be used.

Third, writing test code seems to take
alot of time. Is that normal? Wouldn't
it just be faster to refresh my
browser and see if it worked? I
already have to play with my
application just to see if it flows
correctly and make sure my CSS hasn't
exploded. Why wouldn't manual testing
be enough?

You've only worked on very small projects, for which that testing is arguably sufficient. However, when you work on a project with several developers, thousands or tens of thousands of lines of code, integration points with web services, third party libraries, multiple databases, months of development and requirements changes, etc, there are a lot of other factors in play. Manual testing is simply not enough. In a project of any real complexity, changes in one place can often have unforeseen results in others. Proper architecture helps mitigate this problem, but automated testing helps as well (and helps identify points where the architecture can be improved) by identifying when a change in one place breaks another.

My problem is that
costs of writing tests seem absurdly
high compared to the benefits. That
said, any detailed response is welcome
because I probably missed a benefit or
two.

I'll list a few more benefits.

If you test first (Test Driven Development) your code will probably be better. I haven't met a programmer who gave it a solid shot for whom this wasn't the case. Testing first forces you to think about the problem and actually design your solution, versus hacking it out. Further, it forces you to understand the problem domain well enough to where if you do have to hack it out, you know your code works within the limitations you've defined.

If you have full test coverage, you can refactor with NO RISK. If a software problem is very complicated (again, real world projects that last for months tend to be complicated) then you may wish to simplify code that has previously been written. So, you can write new code to replace the old code, and if it passes all of your tests, you're done. It does exactly what the old code did with respect to the tests. For a project that plans to use an agile development method, refactoring is absolutely essential. Changes will always need to be made.

To sum up, automated testing, especially test driven development, is basically a method of managing the complexity of software development. If your project isn't very complex, the cost may outweigh the benefits (although I doubt it). However, real world projects tend to be very complex, and the results of testing and TDD speak for themselves: they work.

(If you're curious, I find Dan North's article on Behavior Driven Development to be very helpful in understanding a lot of the value in testing: http://dannorth.net/introducing-bdd)

When To Use A Particular Rails Environment for Testing?

When you say I tested within Development Environment, it Failed., you are not running automated tests. You ran the rake db:seed script against the development database. The same task can be run against the test environment with rake db:seed RAILS_ENV=test. Again, this is not an automated test.

There are many reasons why rake db:seed run against the development environment failed in your case. The specific reason could be identified based on the error message.

development environment is one where you work on a day to day basis, adding/changing functionality by making code changes. By default, most scripts assume that you are working with development environment.

test environment is the environment against which the automated tests are run. In the case of rails tutorial, the automated tests are written in the files under test folder. When automated tests are run on a rails application - with rake test or some other way - the test environment is used to run these tests against. The test database gets cleaned up before running the tests to ensure the tests are run starting with a blank state.

Hope this clarifies.

When should I use development vs testing group in gemfile for testing gems?

Gems that you run from the development environment should be present in both the development and test groups. You run things like rspec cucumber and guard from development and they run in the test environment, you need them in development to run the rake tasks and the executables.

Gems that only run while in test mode, such as capybara email_spec and launchy can exist only in the test group and still function correctly.

I hope this helps clear things up.

As a general rule, gems that are executable need to be in both. Also, if you are unsure, put it in both groups too.

Edit

If the gem you are using has generators (rails generate), it needs to be present in both test and development.

Rails5 - what is the best approach for writing unit tests for services?

You probably have simple ruby classes inside your /app/services folder.
Then just write very simple unit test for each single service.
Place them under /test/services/ or spec/services/ folder.
You should have one unit test file for each class you define for your application.

Why are tests failing in Ruby on Rails?

You're mixing up two different kinds of tests.

The first is functional controller tests in the form of ActionController::TestCase which you probably have in your book. These are tests that create a mocked request object and pass it to an instance of the controller. The use of these is greatly discouraged outside of legacy apps since they tend to let tons of bugs through. In functional tests you would write post :create, params: { product: { ... }} because you are actually calling the create method on the controller instance.

The second is integration tests in the form of ActionDispatch::IntegrationTest where you send actual HTTP requests to your Rails application. This is the modern approach and it's just as fast as functional tests in modern versions of Rails which was not the case when the book was written.

In an integration test you pass a path or url:

post '/products', params: { product: ... }

A second issue is the mandatory separation the of keyword parameters that was introduced in Rails 5.

In previous versions of Rails you could call:

get '/foo', bar: 1, baz: 2

And Rails would treat any unknown keys (not params, headers, format, etc) as parameters which was the source of many bugs. In Rails 5+ you explicitly need to pass parameters in the params option.

get '/foo', params: { bar: 1, baz: 2 }

I would suggest you complement the book with some more modern sources or get a newer edition as quite a lot has changed since Rails 4 and following a book that old in Rails 6 is going to be painful.

How to test a gem that depends on Rails and uses Rails commands

A technique we use for WickedPDF is in the default rake task, before we run the tests, is to delete & generate a full Rails application in a gitignored subdirectory of the gem.

As a high-level simplified example of this Rakefile, it looks something like this:

Rakefile

require 'rake'
require 'rake/testtask'

# This gets run when you run `bin/rake` or `bundle exec rake` without specifying a task.
task :default => [:generate_dummy_rails_app, :test]

desc 'generate a rails app inside the test directory to get access to it'
task :generate_dummy_rails_app do
if File.exist?('test/dummy/config/environment.rb')
FileUtils.rm_r Dir.glob('test/dummy/')
end
system('rails new test/dummy --database=sqlite3')
system('touch test/dummy/db/schema.rb')
FileUtils.cp 'test/fixtures/database.yml', 'test/dummy/config/'
FileUtils.rm_r Dir.glob('test/dummy/test/*') # clobber existing tests
end

desc 'run tests in the test directory, which includes the generated rails app'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

Then, in test/test_helper.rb, we require the generated Rails app, which loads Rails itself and it's environment:

test/test_helper.rb

ENV['RAILS_ENV'] = 'test'

require File.expand_path('../dummy/config/environment.rb', __FILE__)
require 'test/unit' # or possibly rspec/minispec

# Tests can go here, or other test files can require this file to have the Rails environment available to them.
# Some tests may need to copy assets/fixtures/controllers into the dummy app before being run. That can happen here, or in your test setup.

You could skip parts of Rails that aren't needed by customizing the command that generates the app. For example, your gem may not need a database at all or a lot of things by default, so you command could be customized for a simpler app. Something like this maybe:

system("rails new test/dummy --skip-active-record \
--skip-active-storage --skip-action-cable --skip-webpack-install \
--skip-git --skip-sprockets --skip-javascript --skip-turbolinks")

In the WickedPDF project, we wanted to test across a wide range of "default" Rails installs, so we don't customize the command much, but that may generate much more than what you need to test some generator tasks.

WickedPDF also tests against multiple versions of Rails with TravisCI and multiple Gemfiles, but this could also be accomplished with the Appraisal gem that Luke suggested in this thread.



Related Topics



Leave a reply



Submit