Когда и когда не заглывать / издеваться над тестом - PullRequest
8 голосов
/ 14 сентября 2010

Я прилагаю согласованные усилия, чтобы обернуть голову вокруг Rspec, чтобы продвинуться в направлении большей части схемы разработки TDD / BDD. Тем не менее, мне еще далеко до борьбы с некоторыми основами:

Например, когда именно мне следует использовать макеты / заглушки, а когда нет?

Возьмем для примера такой сценарий: у меня есть модель Site, которая has_many :blogs и модель Blog has_many :articles. В моей модели Site у меня есть фильтр обратного вызова, который создает набор блогов и статей по умолчанию для каждого нового сайта. Я хочу проверить этот код, поэтому здесь идет:

describe Site, "when created" do

  include SiteSpecHelper

  before(:each) do
    @site = Site.create valid_site_attributes
  end

  it "should have 2 blogs" do
    @site.should have(2).blogs
  end

  it "should have 1 main blog article" do
    @site.blogs.find_by_slug("main").should have(1).articles
  end

  it "should have 2 secondary blog articles" do
    @site.blogs.find_by_slug("secondary").should have(2).articles
  end

end

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

describe Site, "when created" do

  include SiteSpecHelper

  before(:each) do
    site = Site.new
    @blog = Blog.new
    @article = Article.new
    Site.stub!(:create).and_return(site)
    Blog.stub!(:create).and_return(@blog)
    Article.stub!(:create).and_return(@article)
    @site = Site.create valid_site_attributes
  end

  it "should have 2 blogs" do
    @site.stub!(:blogs).and_return([@blog, @blog])
    @site.should have(2).blogs
  end

  it "should have 1 main blog article" do
    @blog.stub!(:articles).and_return([@article])
    @site.stub_chain(:blogs, :find_by_slug).with("main").and_return(@blog)
    @site.blogs.find_by_slug("main").should have(1).articles
  end

  it "should have 2 secondary blog articles" do
    @blog.stub!(:articles).and_return([@article, @article])
    @site.stub_chain(:blogs, :find_by_slug).with("secondary").and_return(@blog)
    @site.blogs.find_by_slug("secondary").should have(2).articles
  end

end

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

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

  • Улучшите мои тесты, описанные выше, чтобы они использовали заглушки или макеты таким образом, чтобы фактически проверять мой код, а не мои тесты.
  • Или, скажите мне, должен ли я даже использовать заглушки здесь - или действительно ли это совершенно не нужно, и я должен записать эти модели в тестовую базу данных.

Ответы [ 3 ]

2 голосов
/ 14 сентября 2010

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

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

Как правило, я не люблю что-либо издеваться / заглушки, если не обязан. Например, когда я пишу тест контроллера, и я хочу убедиться, что соответствующее действие происходит, когда запись не удается сохранить, я считаю, что метод save объекта заглушки для возврата false, а не тщательная обработка параметры, чтобы убедиться, что модель не может быть сохранена.

Другим примером является помощник по имени admin?, который просто возвращает истину или ложь в зависимости от того, является ли текущий вошедший в систему пользователь администратором или нет. Я не хотел подделывать логин пользователя, поэтому я сделал это:

# helper
def admin?
  unless current_user.nil?
    return current_user.is_admin?
  else
    return false
  end
end

# spec
describe "#admin?" do
  it "should return false if no user is logged in" do
    stubs(:current_user).returns(nil)
    admin?.should be_false
  end

  it "should return false if the current user is not an admin" do
    stubs(:current_user).returns(mock(:is_admin? => false))
    admin?.should be_false
  end

  it "should return true if the current user is an admin" do
    stubs(:current_user).returns(mock(:is_admin? => true))
    admin?.should be_true
  end
end

В качестве среднего уровня вы можете захотеть заглянуть в Следует . Таким образом, вы можете просто убедиться, что ваши модели имеют ассоциацию , определенную , и верить, что Rails достаточно хорошо протестирован, чтобы ассоциация «просто работала» без необходимости создавать связанную модель и затем считать ее.

У меня есть модель под названием Member, с которой в основном все в моем приложении связано. Определено 10 ассоциаций. Я мог бы проверить каждую из этих ассоциаций, или я мог бы просто сделать это:

it { should have_many(:achievements).through(:completed_achievements) }
it { should have_many(:attendees).dependent(:destroy) }
it { should have_many(:completed_achievements).dependent(:destroy) }
it { should have_many(:loots).dependent(:nullify) }
it { should have_one(:last_loot) }
it { should have_many(:punishments).dependent(:destroy) }
it { should have_many(:raids).through(:attendees) }
it { should belong_to(:rank) }
it { should belong_to(:user) }
it { should have_many(:wishlists).dependent(:destroy) }
1 голос
/ 12 февраля 2011

Я не уверен, что согласен с остальными. Настоящая проблема (на мой взгляд) заключается в том, что вы тестируете несколько кусков интересного поведения с помощью одних и тех же тестов (поведение при поиске и создание). По причинам, почему это плохо, смотрите этот доклад: http://www.infoq.com/presentations/integration-tests-scam. В оставшейся части этого ответа я предполагаю, что вы хотите проверить, что создание - это то, что вы хотите проверить.

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

Для начала, для запроса дизайна, имеет ли смысл Site добавлять статьи в блог? Как насчет метода класса в Blog, который называется что-то вроде Blog.with_one_article. Это означает, что все, что вам нужно проверить, - это то, что метод класса был вызван дважды (если [как я понимаю сейчас), у вас есть «первичный» и «вторичный» Blog для каждого Site, и ассоциации установлены (я еще не нашел отличный способ сделать это в рельсах, я обычно не проверяю это).

Кроме того, вы переопределяете метод create ActiveRecord при вызове Site.create? Если это так, я бы предложил создать новый метод класса на сайте с именем что-то еще (Site.with_default_blogs возможно?). Это всего лишь моя общая привычка, переопределение вещей, как правило, вызывает проблемы позже в проектах.

1 голос
/ 14 сентября 2010

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

Существуют более эффективные способы ускорить время тестирования, и Ник Готье дает хороший доклад, охватывающий множество из них - см. Видео и слайды .

Кроме того, я думаю, что хорошим вариантом будет опробовать базу данных sqlite в памяти для ваших тестовых прогонов.Это должно значительно сократить время работы вашей базы данных, так как вам больше не придется нажимать на диск.Я сам не пробовал этого (хотя я в основном использую MongoDB, которая имеет то же преимущество), поэтому действуйте осторожно. Вот довольно недавнее сообщение в блоге об этом.

...