Реализация Стека с помощью Разработки через тестирование

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

Давайте предположим, что я хочу записать класс Стека со следующими методами (я выбираю его, поскольку это - легкий пример):

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

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

Как я, как предполагается, запускаюсь с этого?

Править

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

    [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);
    }

Какие-либо исправления/идеи об этом?Спасибо

14
задан devoured elysium 24 May 2010 в 16:14
поделиться

5 ответов

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

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

  • Проверить, что он пуст.
  • Счетчик равен нулю.
  • Поп не справляется.

Проверка одного элемента:

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

Тестировать на> 1 элементах:

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

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

Например (примерно указано в структуре модульного тестирования Google для c ++):

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());
}
8
ответ дан 1 December 2019 в 14:10
поделиться

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

Вот несколько примеров тестов для API стека выше:

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

2) Pop в пустом стеке должно вызывать исключение

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

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

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

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

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

1
ответ дан 1 December 2019 в 14:10
поделиться

Если вы немного подробнее напишете требования для каждого метода, это даст вам больше подсказок относительно необходимых модульных тестов. Затем вы можете кодировать эти тесты. Если у вас есть IDE с автозаполнением, такая как 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 будет жаловаться на отсутствие переменной size. Я бы оставил это до тех пор, пока вы не создадите тестовый пример «вновь созданный стек пуст», где вы затем можете создать переменную, поскольку ее инициализация проверяется в этом тесте.

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

4
ответ дан 1 December 2019 в 14:10
поделиться

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

  • create () - IsEmpty () == true -> ОК
  • 2x push () - count () == 2 -> OK
  • peek () - T == ожидалось (последнее нажатие) -> OK (peek предполагает, что поиск - это опечатка)
  • 2x pop () - count () == 0 && isEmppty -> OK
1
ответ дан 1 December 2019 в 14:10
поделиться

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

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

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

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

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

1
ответ дан 1 December 2019 в 14:10
поделиться