Лучший способ проверить все комбинации на входах в функцию в рубине

72
11

Я обнаружил, что я часто повторяю аналогичную модель в своих тестах и ​​задавался вопросом, есть ли инструмент, который мог бы помочь мне СУШИТЬ их. Если этого не произойдет, мне хотелось бы получить некоторые отзывы о том, считаете ли вы, что это может быть полезно. Или, может быть, я не правильно структурирую свой тест?


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


def should_display(controller_name, account, user)
controller_name == 'Dashboard' &&
account.has_feature(:secret_widget) &&
user.can_access(:secret_feature)
end

Тестирование этого простого метода традиционным способом может быть болезненным, потому что вам нужно будет проверить каждую возможность.


Полный тест будет выглядеть примерно так:


setup do
@my_object = MyObject.new
end

test "should display with controller name = 'DashBoard', account has feature, user can access secret feature" do
assert @my_object.should_display 'DashBoard', make_account_with_secret_widget_feature(), make_user_with_secret_feature_access())
end

test "should display with controller name != 'DashBoard', account has feature, user can access secret feature" do
assert_false @my_object.should_display 'DashBoard2', make_account_with_secret_widget_feature(), make_user_with_secret_feature_access())
end

test "should display with controller name != 'DashBoard', account does has feature, user can access secret feature" do
assert_false @my_object.should_display 'DashBoard', make_account_without_secret_widget_feature(), make_user_with_secret_feature_access())
end

test "... 2^3 tests total to cover every possibility" do
end


То, что я хотел бы сделать, - это определить возможный ввод и ожидаемый результат в родовом мастере. Что-то вроде (псевдо-код впереди):


controler_names = ['DashBoard', 'DashBoard2']
users = [make_user_with_secret_feature_access, make_user_without_secret_feature_access]
accounts = [make_account_with_secret_feature_access, make_account_without_secret_feature_access]

TestCombinations(controller_names, users, accounts) do
execute do |args*|
MyObject.new.should_display(*args)
end
for_inputs('DashBoard', users[0], accounts[0]).expects(true)
othewise.expects(false)
end


Я знаю, что есть такие вещи, как RubyFIT (http://fit.rubyforge.org/), который делает что-то подобное (например, тестирование бизнес-правил), но для этого требуется внешний текстовый файл, который я не большой поклонник. Я бы предпочел оставить его в коде.


Если вы знаете что-нибудь, что могло бы помочь, я был бы признателен.


Спасибо!

спросил(а) 2011-10-18T22:17:00+04:00 9 лет назад
1
Решение
71

Ну, не уверен в вашей структуре тестирования, но я (ab?) использовал RSpec для подобных вещей. Поэтому я буду использовать это здесь, если вы не возражаете.
Адаптация к вашему примеру:


describe MyObject do
controller_names = ['Dashboard', 'Dashboard2']

users = [ User.new('Chuck', [:secret_feature, :normal_feature]),
User.new('Joe', :normal_feature)]

accounts = [ Acc.new('VIP account', :secret_widget),
Acc.new('Normal account', :boring_widget)]

allowed = [
[controller_names[0], users[0], accounts[0]] #, etc, ..
]

before :all do
@my_object = MyObject.new
end

controller_names.product(users,accounts).each do |cn, u, a|
expected_result = allowed.grep([cn, u, a]).size > 0
it "should return #{expected_result} for controller:#{cn} user:#{u.name} account:#{a.name} combo" do
result = @my_object.should_display(cn, a, u)
result.should == expected_result
end
end
end

Чтобы запустить это, вот некоторая фальшивая бизнес-логика:


class MyObject;
def should_display(controller_name, account, user)
controller_name == 'Dashboard' &&
account.has_feature(:secret_widget) &&
user.can_access(:secret_feature)
end
end
class Base < Struct.new(:name,:features);
def has?(feature)
[*features].grep(feature).size > 0
end
end
class Acc < Base; alias has_feature has? end

class User < Base; alias can_access has? end

ответил(а) 2011-10-19T00:54:00+04:00 9 лет назад
74

Прежде чем вы это сделаете, прочитайте Pairwise Testing:

Проверка Pairwise (a.k.a. all-pair) - эффективный метод генерации тестового случая, основанный на наблюдении, что большинство ошибок вызвано взаимодействием не более двух факторов. Паровые тестовые комплекты охватывают все комбинации из двух, поэтому намного меньше, чем исчерпывающие, но все же очень эффективны при обнаружении дефектов.


ответил(а) 2011-10-19T00:42:00+04:00 9 лет назад
41

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


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


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


    Имя пользователя должно быть не менее 5 символов
    Он должен начинаться с буквы
    Он должен содержать только буквы, цифры и символы подчеркивания

Настройка/тестирование образца:


setup do
@invalid_usernames = ["^%$#@", "123abc", "a", "", " aaa", "otherwise valid username but it has spaces"]
@valid_usernames = ["dude_", "frank", "dude123", "as_many_underscores_as_you_want"]
end

test "valid and invalid inputs work as expected" do
@invalid_usernames.each do |iun|
get some_action(iun)
assert response.should_not be :success # or whatever the proper response is
end

@valid_usernames.each do |vun|
get some_action(vun)
assert response.should be :success
end
end


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


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


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

ответил(а) 2011-10-18T22:36:00+04:00 9 лет назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема