Внедрение стека с использованием тестовой разработки

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

Предположим, что я хочу написать класс Stack со следующими методами (я выбираю его как простой пример):

Stack<T>
 - Push(element : T)
 - Pop() : T
 - Peek() : T
 - Count : int
 - IsEmpty : boolean

Как бы вы это поняли? Я никогда не понимал, стоит ли тестировать несколько угловых случаев для каждого метода класса Stack или начинать с нескольких "вариантов использования" с классом, например, добавлять 10 элементов и удалять их. Какова идея? Чтобы сделать код, который использует стек как можно ближе к тому, что я буду использовать в своем реальном коде? Или просто сделайте простые тесты "добавить один элемент", где я проверяю, были ли изменены IsEmpty и Count, добавив этот элемент?

Как я должен начать с этого?

ИЗМЕНИТЬ

Здесь моя реализация грубых тестов:

    [TestMethod]
    public void PushTests() {
        StackZ<string> stackz = new StackZ<string>();

        for (int i = 0; i < 5; ++i) {
            int oldSize = stackz.Size;
            stackz.Push(i.ToString());
            int newSize = stackz.Size;
            Assert.AreEqual(oldSize + 1, newSize);
            Assert.IsFalse(stackz.IsEmpty);
        }
    }

    [TestMethod, ExpectedException(typeof(InvalidOperationException))]
    public void PeekTestsWhenEmpty() {
        StackZ<double> stackz = new StackZ<double>();
        stackz.Peek();
    }

    [TestMethod]
    public void PeekTestsWhenNotEmpty() {
        StackZ<int> stackz = new StackZ<int>();
        stackz.Push(5);

        int firstPeekValue = stackz.Peek();

        for (int i = 0; i < 5; ++i) {
            Assert.AreEqual(stackz.Peek(), firstPeekValue);
        }
    }

    [TestMethod, ExpectedException(typeof(InvalidOperationException))]
    public void PopTestsWhenEmpty() {
        StackZ<float> stackz = new StackZ<float>();
        stackz.Pop();
    }

    [TestMethod]
    public void PopTestsWhenNotEmpty() {
        StackZ<int> stackz = new StackZ<int>();

        for (int i = 0; i < 5; ++i) {
            stackz.Push(i);
        }

        for (int i = 4; i >= 0; ++i) {
            int oldSize = stackz.Size;
            int popValue = stackz.Pop();
            Assert.AreEqual(popValue, i);
            int newSize = stackz.Size;
            Assert.AreEqual(oldSize, newSize + 1);
        }

        Assert.IsTrue(stackz.IsEmpty);
    }

Любые исправления/идеи об этом? Благодаря

Ответы

Ответ 1

Начните с тестирования основных принципов вашего API.

Тест на нулевые элементы.

  • Проверьте, что он пуст.
  • Граф равен нулю.
  • Ошибка при неудаче.

Тест на один элемент:

  • Вызов вызова.
  • Проверьте, что он не пуст.
  • Проверить, что счетчик равен 1.
  • Проверьте, что Pop возвращает элемент.
  • Проверьте, что он теперь пуст.
  • Проверить, что счет теперь 0.

Тест нa > 1 элемент:

  • Теперь нажмите 2, и количество тестов будет два.
  • Pop 2 и убедитесь, что они входят в LIFO.
  • Проверьте пустоту и количество.

Каждый из них будет по меньшей мере одним тестовым примером.

Например (грубо изложенный в Google unit test framework для С++):

TEST(StackTest, TestEmpty) {
  Stack s;
  EXPECT_TRUE(s.empty());
  s.push(1);
  EXPECT_FALSE(s.empty());
  s.pop();
  EXPECT_TRUE(s.empty());
}

TEST(StackTest, TestCount) {
  Stack s;
  EXPECT_EQ(0, s.count());
  s.push(1);
  EXPECT_EQ(1, s.count());
  s.push(2);
  EXPECT_EQ(2, s.count());
  s.pop();
  EXPECT_EQ(1, s.count());
  s.pop();
  EXPECT_EQ(0, s.count());
}

TEST(StackTest, TestOneElement) {
  Stack s;
  s.push(1);
  EXPECT_EQ(1, s.pop());
}

TEST(StackTest, TestTwoElementsAreLifo) {
  Stack s;
  s.push(1);
  s.push(2);
  EXPECT_EQ(2, s.pop());
  EXPECT_EQ(1, s.pop());
}

TEST(StackTest, TestEmptyPop) {
  Stack s;
  EXPECT_EQ(NULL, s.pop());
}


TEST(StackTest, TestEmptyOnEmptyPop) {
 Stack s;
  EXPECT_TRUE(s.empty());
  s.pop();
  EXPECT_TRUE(s.empty());
}

TEST(StackTest, TestCountOnEmptyPop) {
  Stack s;
  EXPECT_EQ(0, s.count());
  s.pop();
  EXPECT_EQ(0, s.count());
}

Ответ 2

Если вы напишете требования к каждому методу более подробно, это даст вам больше намеков на требуемые модульные тесты. Затем вы можете запрограммировать эти тесты. Если у вас есть ID автозаполнения, как и IDEA, то сделать TDD просто, потому что он подчеркивает все биты, которые вы еще не реализовали.

Например, если требование "pop() в пустом стеке выбрасывает исключение NoSuchElementException", то вы начинаете с

@Test(exception=NoSuchElementException.class)
void popOnEmptyStackThrowsException()
{
   Stack s = new Stack();
   s.pop();
}

Затем IDE предложит вам, что делать с отсутствующим классом Stack. Один из вариантов - "создать класс", поэтому вы создаете класс. Затем он спрашивает о методе pop, который вы также хотите создать. Теперь вы можете реализовать свой метод pop, используя то, что вам нужно для реализации контракта. то есть.

T pop() {
   if (size==0) throw new NoSuchElementException();
}

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

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

Ответ 3

Я бы начал вот так:

  • create() - IsEmpty() == true → OK
  • 2x push() - count() == 2 → OK
  • peek() - T == ожидается (последний нажат) → ОК (peek предполагает, что поиск является опечаткой)
  • 2x pop() - count() == 0 && & isEmppty → OK

Ответ 4

В идеале тесты должны охватывать все функциональные возможности этого класса. Они должны проверять, ведет ли каждая операция согласно своему контракту. Теоретически, я вижу контракт как сопоставление между < prev state, params > to < new state, return value > . Поэтому перед проектированием тестов вы должны хорошо определить контракты всех операций.

Ниже приведены некоторые примеры тестов для API стека:

1) Push должен увеличить значение, возвращаемое Count(), на 1

2) Поп в пустом стеке должен генерировать исключение

3) Pop должен уменьшить значение, возвращаемое Count(), на 1

4) Нажатие x1, x2,..., xn и последующие всплывающие окна должны возвращать их в обратном порядке xn,..., x1

5) Добавление элементов, проверка isEmpty() == false, а затем всплытие всех и проверка isEmpty () == TRUE

6) Seek() не должен изменять значение, возвращаемое Count()

7) Последовательные вызовы Seek() должны возвращать одно и то же значение и т.д...

Ответ 5

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

В то время как ваша реализация класса Stack соответствует вашим потребностям, вам не нужно полностью его реализовывать. Под капотом он может даже вернуть вам константы или ничего не делать.

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

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

Сказав это, я могу сказать вам, что это не интересно и, вероятно, полезно изучать TDD, внедряя модульные тесты на интерфейсы superutil, полученные через страдания нескольких поколений разработчиков. Вы просто не почувствуете ничего захватывающего. Просто возьмите любой класс из написанного вами приложения и попробуйте написать ему тесты. Рефакторинг их даст вам много удовольствия.