Внедрение стека с помощью разработки через тестирование - PullRequest
14 голосов
/ 24 мая 2010

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

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

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

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

Как мне начать с этого?

EDIT

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

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

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

Ответы [ 5 ]

8 голосов
/ 24 мая 2010

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

Проверка на нулевые элементы.

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

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

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

Тест на> 1 элементах:

  • Теперь нажмите 2 и количество тестов равно двум.
  • Pop 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());
}
4 голосов
/ 24 мая 2010

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

Например, если требование «pop () в пустом стеке создает исключение NoSuchElementException», тогда вы должны начать с

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

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

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

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

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

1 голос
/ 24 мая 2010

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

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

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

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

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

1 голос
/ 24 мая 2010

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

Вот несколько примеров тестов для стека 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 () должны возвращать одно и то же значение и т.д ...

1 голос
/ 24 мая 2010

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

  • create() - IsEmpty() == true -> OK
  • 2x push() - count() == 2 -> OK
  • peek() - T == ожидается (последний нажат) -> ОК (peek предполагает, что поиск является опечаткой)
  • 2x pop() - count() == 0 && isEmppty -> OK
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...