Skip to content

Instantly share code, notes, and snippets.

@rilian
Created August 13, 2015 14:52
Show Gist options
  • Save rilian/0f744e2f102349170351 to your computer and use it in GitHub Desktop.
Save rilian/0f744e2f102349170351 to your computer and use it in GitHub Desktop.
structure for rails model test
describe ClassName do
describe 'Database' do
end
describe 'Relations' do
end
describe 'Validations' do
end
describe 'Scopes' do
end
describe 'Class methods' do
end
describe 'Callbacks' do
end
describe 'Instance Methods' do
end
end
@creeonix
Copy link

spec/models/<model_name>/database_spec.rb
spec/models/<model_name>/relations_spec.rb
spec/models/<model_name>/validation_spec.rb
spec/models/<model_name>/methods_spec.rb

@saaji
Copy link

saaji commented Aug 13, 2015

В предложенном варианте покрытие, думаю, будет хорошим. НО

  • мы тестируем внутреннюю реализацию модели (например в жизни scope может превратиться в метод и наоборот)
  • тесты будут хрупкими
  • тесты могут не поспевать за рефакторингом
  • тестируется отдельные методы и побочные эффекты; нет сценариев практического использования модели
  • тестируется низлежащий слой за который отвечает фреймворк; например relations.

Боюсь тут за деревьями леса не разглядеть.

Вообщем я бы в первую очередь тестировал внешний интерфейс модели и сценарии ее использования, не нарушая инкапсуляцию оной.

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme LateBinding of all things.
— Alan Kay

Вот неплохое выступление с RubyC на тему: https://www.youtube.com/watch?v=Vb8ybJuL2Xw

@rilian
Copy link
Author

rilian commented Aug 13, 2015

thanks for comments, good ideas

@windock
Copy link

windock commented Aug 18, 2015

I fully agree with saaji
This applies only to model tests, as testing of objects that do not depend on database or other 3rd-party code is a completely different story with a lot more advanced tools available to organise code and tests.

As a small compromise we can imagine that any model is actually 2 objects: Class and Instance, thus they may have separate sections.

Main reason is to minimise maintenance cost of tests but decreasing test fragility.
"Fragile Tests increase the cost of test maintenance by forcing us to visit many more tests each time we modify the functionality of the system or the fixture. It is particularly deadly on projects that do highly incremental delivery" - http://xunitpatterns.com/Fragile%20Test.html

  1. Tests that over-specify implementation are fragile because they will fail if we change implementation mechanism. Examples of that are refactoring of scope to class method or callback to wrapper method.

  2. It is not necessary. As @rilian told, sections are needed to locate tests for big models. Big models is a problem that should be fixed. Tests that are difficult to locate because there are too many of them is only a symptom of this problem, they should not cover it.

  3. They force to write separate tests for every method. But methods may be a result of refactoring, thus they will be covered in a separate group of tests, possibly for another object. If we then write additional tests for this particular method, it again creates a problem of fragile tests - we'd have to change multiple tests if we change 1 method.
    @rilian has mentioned that if we remove the original code under test, we will be left with separated methods without any tests. While it is true, that is not a big problem. When we remove code, tests for that code will fail. We will then remove tests.

  • If separated methods now do not have any tests, test coverage will decrease thus showing us that problem. This will require to write tests for these methods
  • if test coverage does not decrease, then they're covered in other tests thus nothing should be changed.
describe ClassName do
  # i'd not use Methods, since any behaviour may be implemented with a number of mechanisms
  # that are not methods: delegates, scopes, Proc, anything else
  describe 'Class' do

  end

  # the other reason is for not using methods is that they often cannot be tested in isolation,
  # for example Stack may have only 2 methods: #pop and #push without any visible state.
  # You cannot test #push in isolation so you'd write usage scenarios, not method tests
  describe 'Instance' do
    # any section may or may not have any additional sections
    # it may be reasonable to add
    describe 'Validations'
    # but not necessary - there may be only 1 validation test and separate section for it would clutter code
    # additionally, validations may be tested in usage scenarios

    describe 'Callbacks' # anything 'callbacky' that can be implemented by callbacks, database triggers, overriding instance methods, etc

    # it does not make sense to add Relations section, as, they usually are a configuration for 3rd party code.
    # In case when they are complex, they can be tested, but should not get any specific treatment or section
    # The reason for that is relations can be implemented with any different mechanisms, including regular methods. The same is true for methods - they can be refactored to relations.
  end 

  # It does not make any sense to test internal implementation of database as that would make tests fragile
  # but as @rilian really wants to put requests for future implementation changes in test section, we've agreed to have something like
  describe 'Database' do
    # but it will not have any actual tests - only pending ones
    # as not to break any deployment
  end
end

@windock
Copy link

windock commented Aug 20, 2015

Clean version:

describe ClassName do
  describe 'Class' do
  end

  describe 'Instance' do
    describe 'Validations'
    describe 'Callbacks'
  end 

  describe 'Database'
end

@rilian
Copy link
Author

rilian commented Oct 19, 2015

improved

  describe 'Class' do
    pending
  end

  describe 'Instance' do
    pending
  end

  describe 'Validations' do
    pending
  end

  describe 'Callbacks' do
    pending
  end

  # TODO: move to a spec/database folder
  describe 'Database' do
    pending
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment