Вопрос

Прежде всего, я знаю, что использовать несколько утверждений в модульном тесте — плохая практика.

Но иногда вам нужно протестировать какую-нибудь атомарную транзакцию.В качестве упрощенного примера возьмем какое-нибудь банковское приложение, имеющее класс Account:

class Account 
  attr_accessor :balance

  def transfer(to_account, amount)
    self.balance -= amount
    to_account.balance += amount
    Audit.create(message: "Transferred #{amount} from #{self.number} to #{to_account.number}."
  end

end

В этой ситуации я хочу проверить 3 вещи вместе:

  1. Баланс исходного счета уменьшился на amount
  2. Баланс целевого счета увеличился на amount
  3. Запись аудита вставлена.

Как лучше всего протестировать @account.transfer метод?

Это было полезно?

Решение

В этой ситуации я хочу проверить 3 вещи вместе:

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

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

RSpec упрощает извлечение фазы настройки теста, чтобы она повторялась для каждого примера:

class Account 
  attr_accessor :balance

  def transfer(to_account, amount)
    self.debit!(amount)
    to_account.credit!(amount)
    Audit.create!(message: "Transferred #{amount} from #{self.number} to #{to_account.number}."
  rescue SomethingBadError
    # undo all of our hard work
  end

end

describe Account do
  context "when a transfer is made to another account" do
    let(:other_account} { other_account }
    context "and the subject account has sufficient funds" do
      subject { account_with_beaucoup_bucks }
      it "debits the subject account"
      it "credits the other account"
      it "creates an Audit entry"
    end
    context "and the subject account is overdrawn" do
      subject { overdrawn_account }
      it "does not debit the subject account"
      it "does not credit the other account"
      it "creates an Audit entry" # to show the attempted transfer failed
    end
  end
end

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

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

Другие советы

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

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

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

  • 1, чтобы проверить поведение текущего to_account (самый простой случай);

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

Поскольку аудит аудита довольно прост и не требует дополнительной настройки, вы также можете протестировать его вместе с Account и to_account.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top