Эффективность автоматического тестирования приложений

Атака клонов.
Эпизод: покер.


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

Это не эпизод легендарной саги «Звёздные войны» и не превью фантастической повести. Это описание нагрузочного тестирования сервера (построенного на технологиях Java), проведенного в ходе разработки игрового приложения «Покер» для социальных сетей.

Нагрузочному тестированию предшествовала реализация основной функциональности сервера: набора команд, сетевого интерфейса (Apache MINA), игрового движка и базы данных (Hibernate, MySQL). В процессе разработки использовалась непрерывная интеграция (сервер непрерывной интеграции Hudson), модульное тестирование (JUnit), интеграционное тестирование базы данных, нагрузочное тестирование сетевого интерфейса и логики приложения. В условиях, когда флеш-клиент игры находился в стадии начала разработки, стояла задача проведения интеграционного и нагрузочного тестирования игрового сервера. Был описан алгоритм инструкций для ботов покера, где поведение клона зависело от текущего состояния игровой ситуации, набора карт. С учетом того, что не было цели создать автоматического игрока с высоким мастерством, алгоритм был простым для реализации, но эффективным. Это позволило проводить тестирование игрового сервера посредством автоматических игроков наряду с реальными игроками. И клоны зачастую брали победу у людей числом, а не умением.

Как выше уже отмечалось, при разработке использовалось модульное тестирование, позволяющее проверить функциональность отдельных классов. Не обошлось без него и при разработке «мозга» движка — расчета покерной «руки» (определение выигрышной комбинации карт в ходе раздач карт). Для каждой комбинации были написаны тесты, позволяющие их проверить. Правила покера определяют следующие выигрышные комбинации:
  • Старшая карта;
  • Пара (две карты одного ранга);
  • Две пары (две пары одного ранга и еще две карты одного ранга);
  • Сет (три карты одного ранга);
  • Стрит (последовательность из пяти карт разной масти);
  • Флэш (любые пять карт одной масти);
  • Фул-хауз (три карты одного ранга и две карты тоже одного ранга);
  • Карэ (четыре карты одной и той же деноминации);
  • Стрит-флэш (последовательность из пяти карт одной и той же масти);
  • Флэш-рояль (пять старших карт какой-либо одной масти).

Таким образом имеются 10 выигрышных комбинаций карт и в простом случае максимально за 10 циклов расчета можно найти решение. Но даже начинающий игрок покера заметит, что часть игровых комбинаций имеет общие свойства, например, стрит-флэш владеет свойством стрит (последовательность из пяти карт...) и флеш (… одной масти). Таким образом число циклов расчета удалось сократить до 4. И у нас был набор юнит тестов на каждую комбинацию..., но уверенности в правильности расчета не было. Эта проблема была решена написанием теста, который использовал статистику по покерным комбинациям (вероятность выпадения покерной комбинации):

================================================================
| Тест выигрышных комбинаций
================================================================
| HIGH_CARD | 17.497% | 34994 |
| ONE_PAIR | 43.847% | 87694 |
| TWO_PAIR | 23.4185% | 46837 |
| SET | 4.8045% | 9609 |
| STRAIGHT | 4.653% | 9306 |
| FLUSH | 2.989% | 5978 |
| FULL_HOUSE | 2.581% | 5162 |
| QUADS | 0.175% | 350 |
| STRAIGHT_FLUSH | 0.033% | 66 |
| ROYAL_FLUSH | 0.0020% | 4 |
================================================================
handsNumber: 200000

В данном тесте идет расчет 200000 случайных комбинаций, представлено число их выпадения в процентном соотношении и в численном (например, сет выпал в тесте 9609 в ~4.81% случаев). Расхождение между результатами этого теста и статистическими данными указало на ошибку, возникающую в результате того, что изначально не учли ряд особых правил в комбинациях, которые опытным игрокам прекрасно известны.

Таким образом, еще раз была подтверждена эффективность автоматического тестирования (модульного, интеграционного, нагрузочного) разрабатываемых приложений.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 10

    +2
    Есть ненулевая вероятность у этого теста упасть. Это неправильно.

    Определять правила соответствия карт комбинации нужно на заданных данных (фикстурах), а не на основе генератора псевдослучайных чисел.
      0
      Смею с Вами согласиться, но…
      Изначально были созданы также тесты на заданных данных, которые постепенно дополнялись.
      Но это подразумевало 100% знание правил расчета «покерной руки», которыми я не владел.
      Тест основанный на статистике указал на неточности расчета одной из комбинации.
      Разбор этой ситуации позволил найти ошибку, и на основании этого был написан дополнительный тест
      уже с фиксированными данными.
        0
        Да, как разовый пруф оф концепт — согласен, интересный подход. Но потом такое нужно выпиливать :-)
        0
        Да не факт — рандом он тоже помагает.

        Но опять же, конечно, не как основной метод.

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

          Результаты тестов не должны изменяться от запуска к запуску.

          > Но опять же, конечно, не как основной метод.

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

          > Да даже с другой стороны — а реальные юзеры это не рандом вообще? Это тот ещё случайный случай.

          Я не совсем понимаю при чём тут это вообще. Я комментировал тестирование, а не эксплуатацию. После того как код отлажен, он должен одинаково предсказуемо работать в тестовом окружении (на основе фикстур) и и продакшне («рандомные» действия пользователей).
            0
            В чём суть автотестов? Зачем они нужны?

            Для того, чтобы качество повысить и поймать проблемы в коде. ТС как раз и нашёл проблему, которую фикстурами с большой долей вероятности никогда бы не нашёл.

            Фикстуры — всего лишь одна из форм, популярнейшая.

            Палочки не всегда должны быть попендекулярны.

            > Результаты тестов не должны изменяться от запуска к запуску.

            Если у нас всё ок с кодом, то результаты и не будут меняться — рандом у нас или «константа».

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

            Но опять же всё зависит от предметной области — к примеру у нас надо покрыть очень сложную логику на весьма большом объёме данных.
              0
              > Если у нас всё ок с кодом, то результаты и не будут меняться — рандом у нас или «константа».

              В ситуации, о которой идёт речь в посте — будут. В качестве эталонных значений были взяты статистические, а в качестве исходных данных — рандом. Потому, волей случая, тест может падать. Потому этот рандом оставлять нельзя.
                0
                А почему он может падать? А, стоп! Сравнивать рандом со стастикой то конечно не есть хорошо.

                Я про рандом в исходных данных.

                Типа ИКС = рандом от 1 до 20, а ИГРЕК д.б. ИКС * 2.
                Хотя когда мы усложняем тесты… Неее, усложнение тестов — нехорошо. Тада понял, отбой.
                  0
                  Угумц.

                  Хотя лично я и вещи вроде:

                  > Типа ИКС = рандом от 1 до 20, а ИГРЕК д.б. ИКС * 2.

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

                    * при портировании из языка А в язык Б
                    * при разработке экстра-надежных систем

                    1. Берем тесты из языка А и портируем их. Дополнительно рандомом забиваем в язык А и сравниваем с результатами из языка Б.

                    2. К примеру мы делаем систему для ракеты. И мы не будем доверять одной группе разработчиков. Это будет две группы и они друг-друга знать не должны. За этим будет у нас ФСБ следить :) Итоги разработки двух групп будет третья тестить на постоянных фикстурах и дополнительно на рандоме. Если где-то одна из разработок группы будет фейлить — пилюли.

      Only users with full accounts can post comments. Log in, please.