2010-01-27 2 views
42

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

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

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

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

ответ

55

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

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 

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

Или ...

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

setup do 
    @object = FooClass.new 
end 

should_act_as_greeter 

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

+0

Когда вы говорите «функциональное покрытие для тестирования», я полагаю, что вы ссылаетесь на функциональность, которую приобретают модели, а не на тесты контроллера, которые хранятся в тестах/функциях? Спасибо за ваш ответ. Мне нравится идея тестирования модуля изолированно и написания помощника, который могут использовать другие классы, которые используют этот модуль. – tsdbrown

+1

По функциональности я имею в виду извне. Это обычно проверка контроллера, но не всегда. В любом случае функциональное покрытие должно касаться (или, по крайней мере, пасти) всех областей системы. Если тесты вашего устройства прочны, то функционального тестирования достаточно часто, чтобы покрыть задницу. Написание слишком много тестов низкого уровня может быть плохим вложением. Если он никогда не потерпит неудачу в одиночку, то это поймает ошибки? Является ли «вероятное время отладки сохранено» * «вероятность ошибки»> «время для написания теста»? Игнорировать это, если ошибка может убить людей или ваш бизнес. cwninja

+0

№ Контрольные тесты (почти) всегда плохие идеи (рассказы огурцов делают то же самое лучше), и они все равно не имеют отношения к проблеме. Просто модульный тест, как в первом примере кода. –

3

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

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

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

+0

Спасибо, я согласен с тем, что вы говорите о тестировании функциональности в классах, в которые он включен. Так что у вас есть тестовый файл для каждого дополнительного класса в модуле или тестовый файл для модуля в целом? Наверное, я больше повесил трубку на фактические тестовые файлы (имена файлов, местоположения и т. Д.) В отличие от того, что тестировать. – tsdbrown

4

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

Например, у меня есть модуль под названием 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 

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

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

+0

Спасибо за подробный ответ с примерами. –

+0

Недостатком этого подхода является то, что он фактически создает класс, который может столкнуться с другими тестами. См. Ответы с более высоким рейтингом для подходов, которые не оставляют побочных эффектов. – mrm

13

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

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 

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

Смежные вопросы