2 ноября 2010 г.

Equivalence Class Testing

Вступление

Equivalence Classes (Класс эквивалентности) – это входные (а иногда и выходные) данные, которые обрабатываются приложением одинаково или обработка которых приводит к одному и тому же результату.

Equivalence Class Testing (Тестирование классами эквивалентности) это техника тест дизайна способная резонно уменьшить количество ваших тест-кейсов. Использовать ее можно на всех уровнях тестирования - unit, integration, system, and system-integration test levels.

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

Подход

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

Использование классов эквивалентности будем рассматривать на примерах.

Пример 1. Одна сущность.



Представим, что мы тестируем модуль для HR, который определяет брать на работу кандидата или нет, базируясь на возрасте кандидата. Условия такие:
  • 0–16 : Не нанимать
  • 16–18 : Можем нанять только на part time
  • 18–55 : Можем нанять на full time
  • 55–99 : Не нанимать
Стоит ли в этом случае применять exhaustive testing, т.е. должны ли мы протестировать модуль на всех возможных входных данных, на всех возрастах: 0, 1, 2, 3, 4, 5, 6, 7, 8, ..., 97, 98, 99? Если бы имели на это ресурсы, то это был бы неплохой вариант. Либо в случае если девелопер реализовал модуль как нижепередставленный код, то пожалуй тоже бы пришлось тестировать все возраста.

        If (applicantAge == 0) hireStatus="NO";
        If (applicantAge == 1) hireStatus="NO";
        …
        If (applicantAge == 14) hireStatus="NO";
        If (applicantAge == 15) hireStatus="NO";
        If (applicantAge == 16) hireStatus="PART";
        If (applicantAge == 17) hireStatus="PART";
        If (applicantAge == 18) hireStatus="FULL";
        If (applicantAge == 19) hireStatus="FULL";
        …
        If (applicantAge == 53) hireStatus="FULL";
        If (applicantAge == 54) hireStatus="FULL";
        If (applicantAge == 55) hireStatus="NO";
        If (applicantAge == 56) hireStatus="NO";
        …
        If (applicantAge == 98) hireStatus="NO";
        If (applicantAge == 99) hireStatus="NO";
К счастью, наши родные девелоперы так код не пишут (по крайней мере как правило :)). На самом деле для этого модуля мы, как правило, увидим примерно такой код:

If (applicantAge > 0 && applicantAge <=16)
                  hireStatus="NO";
    If (applicantAge > 16 && applicantAge <=18)
                  hireStatus="PART";
    If (applicantAge > 18 && applicantAge <=55)
                  hireStatus="FULL";
    If (applicantAge > 55 && applicantAge <=99)
                  hireStatus="NO";
Поэтому явно видно, что не стоит тестировать все значения 0, 1, 2, ... 14, 15, 16. Более разумно будет протестировать диапазоны каждого условия, что собственно и есть наши классы эквивалентности.:
  1. Класс эквивалентности NO: 0-16.
  2. Класс эквивалентности PART: 17-18.
  3. Класс эквивалентности FULL: 19-55.
  4. Класс эквивалентности NO: 56-99.
Вспомним правила - после определения классов эквивалентности мы должны создать тест кейс с любым значением из диапазона класса эквивалентности. Итого у нас 4 позитивных тест кейса вместо 100. Неплохая оптимизация.

Так же не стоит забывать о не валидных диапазонах, добавим классы эквивалентности и для них:
  1. (-100) – (-1). Значнеие (-100) было взято наугад, по поводу подобных границ лучше консультироваться с девелоперами.
  2. 100-1000. Значнеие (1000) было взято наугад, по поводу подобных границ лучше консультироваться с девелоперами.
  3. 0.1-0.9. Выбрано любое дробное значение входящее в валидный диапазон.
  4. Символы.
  5. Пустой ввод.
  6. ...
Вот собственно и всё. Осталось на каждый класс эквивалентности создать тест-кейс.

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

Для этого лучше рассмотреть следующий пример.



Пример 2. Несколько сущностей.


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


Сущность Тип ввода Допустимые значения
Логин Ввод с клавиатуры Латинские символы
Пароль Ввод с клавиатуры Латинские символы
Возраст Выбор из списка (список от 1 до 100) От 16 до 100
Способ оплаты Выбор из списка Оплата картой, интернет деньги, наличные


Определеим классы эквивалентности по сущностям:
  • 1. Логин:
    • a. Латинские символы
    • b. Не латинские символы
    • c. Пустой ввод
  • 2. Пароль:
    • a. Латинские символы
    • b. Не латинские символы
    • c. Пустой ввод
  • 3. Возраст:
    • a. От 16 до 100
    • b. От 1 до 16
  • 4. Способ оплаты:
    • a. Любое значение из списка
Примечание: Некоторые сущности, например "не латинские символы", стоило бы разложить на подсущности, но для простоты примера мы этого делать не будем.

Для тестирования способа оплаты мы должны выбрать значение «Оплата картой», «интернет деньги» или «наличные». Это тот случай, когда здравый смысл должен победить любые правила :) Хотя правило предлагает нам выбрать одно значение из класса эквивалентности, здесь будет лучше проверить каждое из них. Это имеет смысл потому что здесь входных значений немного. 

Возможно появится вопрос – а что с возрастом? Может и тут стоит все значения протестировать? А если бы мы вводили город проживания? Стоило бы все возможные значения городов из списка проверить? Ответ на эти вопросы зависит от риска пропустить серьезные дефекты и от количества времени выделенного под тестирование.

Итого у нас получатся следующие валидные тест-кейсы:
Логин Пароль Возраст Способ оплаты Результат
Латинские символы Латинские символы От 16 до 100 Оплата картой Успешная регистрация
Латинские символы Латинские символы От 16 до 100 Интернет деньги Успешная регистрация
Латинские символы Латинские символы От 16 до 100 наличные Успешная регистрация


При составлении _не_ валидных тест кейсов советую создавать по 1 тест кейсу на каждое _не_ валидное значение 1 сущности:
Логин Пароль Возраст Способ оплаты Результат
Не латинские символы Латинские символы От 16 до 100 Оплата картой Ошибка регистрации
Пустой ввод Латинские символы От 16 до 100 Оплата картой Ошибка регистрации
Латинские символы Не Латинские символы От 16 до 100 Интернет деньги Ошибка регистрации
Латинские символы Пустой ввод От 16 до 100 Интернет деньги Ошибка регистрации
Латинские символы Латинские символы От 1 до 16 наличные Ошибка регистрации


Ну вот собственно и всё. Готово. Можно тестировать. 


Этот пост является вольным переводом Chapter 3 из книги "A Practitioner'S Guide To Software Test Design" by Lee Copeland.

13 комментариев:

  1. If (applicantAge > 0 && applicantAge <16)
    hireStatus="NO";
    If (applicantAge > 16 && applicantAge <18)
    hireStatus="PART";
    If (applicantAge > 18 && applicantAge <55)
    hireStatus="FULL";
    If (applicantAge > 55 && applicantAge <99)
    hireStatus="NO";

    В этом примере - у нас выпадут значения 16, 18, 55 и 99. Т.е. там, имхо, лучше юзать <=

    ОтветитьУдалить
  2. Да, моя вина, спасибо. Исправил.

    ОтветитьУдалить
  3. Кстати с чего вы взяли что программисты не пишут так как у вас нарисовано в первом примере пишут - пишут , если это будет работать быстрее (например на процессоре с длинным командным словом особенно если от if - избавится)
    ============================
    Вопрос а можно разделить на классы эквивалентности входное значение которое нельзя упорядочить, на котором не определены операции больше ,меньше?

    ОтветитьУдалить
  4. Прочитал, интуитивное определение классов эквивалентности.
    Целевая функция hireStatus от applicantAge на отрезках [0;16] , [55;99] - имеет одно и тоже значение, так почему вы эти значения относите к разным классам эквивалентности?

    ОтветитьУдалить
  5. Да, результат один, выходное значение одинаковое. Но входные данные для этих диапазонов разные:
    * Во-первыйх. Возможно разный код отработает по этим диапазонам. Поэтому стоит проверить каждый из диапазонов.
    * Во-вторых. Все таки анализ граничных значений мы применять будем, поэтому удобней будет разделить эти диапазоны.
    * Ну и в-третьих. Это все таки логика приложения. Есть такой диапазон входных значений сущности - лучше его проверить.

    ОтветитьУдалить
  6. Извините, пропустил ваш комментарий.

    >>Вопрос а можно разделить на классы эквивалентности входное значение которое нельзя упорядочить, на котором не определены операции больше ,меньше?

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

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

    ОтветитьУдалить
  7. Возможно ли найти граничные значения для латинских символов?

    ОтветитьУдалить
    Ответы
    1. Какие по вашему мнению являются граничными для символов?

      Удалить
    2. Зависит от контекста. Символы или алфавит, кодировка, раскладка, один символ или совокупности и так далее.

      P.S.: Этот пост про классы эквивалентности ;)
      P.P.S.: Отличные у вас бложики, заценил :D

      Удалить
  8. Всем привет! Только изучаю тестирование, еще даже нигде не работал. Попал на этот интересный блог, изучая техники тест-дизайна. Заметил ошибку (математическую) во втором примере. Прошу прощения, если конечно, ошибаюсь сам.

    Собственно ошибка: в позитивном тесте №3 мы указываем возраст от 16 до 100 и при этом он проходит. Негативный тест №5 предусматривает диапазон возрастов от 1 до 16 и при этом тест должен завалить приложение. Так вот неясным остается значение возраста 16, ибо нигде не нашел условия включающего или исключающего цифру 16 из диапазонов значений.
    Спасибо за внимание!

    ОтветитьУдалить
    Ответы
    1. Все верно, при возрасте 16 непонятно и неизвестно что произойдет.

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

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

      Спасибо за вопрос, удачного изучения тестирования ;)

      Удалить
  9. кашерный дизайн :D

    ОтветитьУдалить