Rails: Как мне написать тесты для модуля ruby? - PullRequest
47 голосов
/ 27 января 2010

Я хотел бы знать, как написать модульные тесты для модуля, который смешан в пару классов, но не совсем знаю, как это сделать:

  1. Тестировать ли методы экземпляра, записывая тесты в один из тестовых файлов для включающего их класса (кажется неправильным), или вы можете как-то хранить тесты для включенных методов в отдельном файле? к модулю?

  2. Тот же вопрос относится к методам класса.

  3. Должен ли я иметь отдельный тестовый файл для каждого из классов в модуле, как это делают нормальные модели рельсов, или они живут в общем тестовом файле модуля, если таковой существует?

Ответы [ 6 ]

57 голосов
/ 27 января 2010

ИМХО, вы должны выполнить функциональное тестирование покрытия, которое охватит все области применения модуля, а затем протестировать его изолированно в модульном тесте:

setup do
  @object = Object.new
  @object.extend(Greeter)
end

should "greet person" do
  @object.stubs(:format).returns("Hello {{NAME}}")
  assert_equal "Hello World", @object.greet("World")
end

should "greet person in pirate" do
  @object.stubs(:format).returns("Avast {{NAME}} lad!")
  assert_equal "Avast Jim lad!", @object.greet("Jim")
end

Если ваши юнит-тесты хороши, вы можете просто проверить работоспособность модулей, в которые он входит.

Или ...

Напишите тестовый помощник, который утверждает правильное поведение, затем используйте его для каждого класса, в который он входит. Использование будет следующим:

setup do
  @object = FooClass.new
end

should_act_as_greeter

Если ваши юнит-тесты хороши, это может быть простой дымовой тест ожидаемого поведения, проверка вызова правильных делегатов и т. Д.

13 голосов
/ 28 января 2010

Использовать встроенные классы (я не использую какие-либо модные флексмоки или stubba / mocha только для того, чтобы показать смысл)

def test_should_callout_to_foo
   m = Class.new do
     include ModuleUnderTest
     def foo
        3
     end
   end.new
   assert_equal 6, m.foo_multiplied_by_two
 end

Любая библиотека для насмешек / окурков должна дать вам более чистый способ сделать это. Также вы можете использовать структуры:

 instance = Struct.new(:foo).new
 class<<instance
     include ModuleUnderTest
 end
 instance.foo = 4

Если у меня есть модуль, который используется во многих местах, у меня есть для него модульный тест, который делает именно это (вставьте тестовый объект под методы модуля и проверьте, правильно ли работают методы модуля на этом объекте).

4 голосов
/ 04 июля 2018

В minitest, поскольку каждый тест явно является классом, вы можете просто включить модуль в тест и протестировать методы:

class MyModuleTest < Minitest::Test
   include MyModule

   def my_module_method_test
     # Assert my method works
   end
end
4 голосов
/ 27 января 2010

Я стараюсь сосредоточить свои тесты только на контракте для этого конкретного класса / модуля. Если я докажу поведение модуля в тестовом классе для этого модуля (обычно путем включения этого модуля в тестовый класс, объявленный в спецификации для этого модуля), то я не буду дублировать этот тест для производственного класса, который использует этот модуль. Но если я хочу проверить дополнительное поведение для производственного класса или проблемы интеграции, я напишу тесты для производственного класса.

Например, у меня есть модуль с именем AttributeValidator, который выполняет легкие проверки, похожие на ActiveRecord. Я пишу тесты для поведения модуля в спецификации модуля:

before(:each) do
  @attribute_validator = TestAttributeValidator.new
end

describe "after set callbacks" do
  it "should be invoked when an attribute is set" do
    def @attribute_validator.after_set_attribute_one; end
    @attribute_validator.should_receive(:after_set_attribute_one).once
    @attribute_validator.attribute_one = "asdf"
  end
end

class TestAttributeValidator 
    include AttributeValidator
    validating_str_accessor [:attribute_one, /\d{2,5}/]      
end

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

describe "ProductionClass validation" do
  it "should return true if the attribute is valid" do
    @production_class.attribute = @valid_attribute 
    @production_class.is_valid?.should be_true
  end
  it "should return false if the attribute is invalid" do
    @production_class.attribute = @invalid_attribute
    @production_class.is valid?.should be_false
  end
end

Здесь есть некоторое дублирование (как и в большинстве интеграционных тестов), но тесты доказывают мне две разные вещи. Один набор тестов подтверждает общее поведение модуля, другой - конкретные проблемы реализации производственного класса, использующего этот модуль. Из этих тестов я знаю, что модуль будет проверять атрибуты и выполнять обратные вызовы, и я знаю, что мой производственный класс имеет определенный набор проверок для конкретных критериев, уникальных для производственного класса.

Надеюсь, это поможет.

3 голосов
/ 27 января 2010

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

Тогда у меня, вероятно, также будут тесты для классов, в которые включены модули. Я могу не тестировать каждый класс, но буду тестировать достаточно классов, чтобы получить хорошее покрытие и разобраться в любых возникающих проблемах. Эти тесты не должны явно тестировать модуль, но обязательно проверят его использование в определенных сценариях.

Каждый набор тестов будет иметь свой собственный файл.

1 голос
/ 22 сентября 2018

Мне нравится создавать новый хост-класс и смешивать в нем модуль, что-то вроде этого:

describe MyModule do
  let(:host_class) { Class.new { include MyModule } }
  let(:instance) { host_class.new }

  describe '#instance_method' do
    it 'does something' do
      expect(instance.instance_method).to do_something
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...