Заставить мертвую рыбу плавать — тестируемая разработка Golang

Сегодня мы рассмотрим решение задачи «Make The Dead Fish Swim» — A Codewars challenge ranked ‘6 kyu» и сделаем это с помощью Test Driven Development.

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

Write a simple parser that will parse and run Deadfish.

Deadfish has 4 commands, each 1 character long:

    i increments the value (initially 0)
    d decrements the value
    s squares the value
    o outputs the value into the return array

Invalid characters should be ignored.

Parse("iiisdoso") == []int{8, 64} // example output
Войти в полноэкранный режим Выход из полноэкранного режима

Небольшая оговорка: есть большая вероятность, что существует очень лаконичное и умное решение этой задачи, и я поделюсь им в конце этого поста.

Однако моя цель здесь — попрактиковаться в решении проблемы и разбиении ее на небольшие тестируемые части функциональности, которые можно использовать в других местах (если бы мы создавали что-то в реальной жизни).

Мы будем работать в двух разных файлах, main.go и main_test.go.

Я также буду использовать пакет, который мне нравится использовать при написании тестов, и этот пакет — https://github.com/stretchr/testify.

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

func TestIncrementOne(t *testing.T) {
    assert.Equal(t, 1, IncrementOne(0))
    assert.Equal(t, 5, IncrementOne(4))
    assert.Equal(t, 100, IncrementOne(99))
}

func TestDecrementOne(t *testing.T) {
    assert.Equal(t, 0, DecrementOne(1))
    assert.Equal(t, 4, DecrementOne(5))
    assert.Equal(t, 99, DecrementOne(100))
}

func TestSquare(t *testing.T) {
    assert.Equal(t, 1, Square(1))
    assert.Equal(t, 25, Square(5))
    assert.Equal(t, 64, Square(8))
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

// go test

./main_test.go:23:21: undefined: IncrementOne
./main_test.go:27:21: undefined: DecrementOne
./main_test.go:31:21: undefined: Square
FAIL    github.com/damiensedgwick/codewars/makeTheDeadFishSwim [build failed]
Вход в полноэкранный режим Выход из полноэкранного режима

Довольно просто, все они не работают, потому что ни одна из функций еще не существует, поэтому давайте создадим их.

func IncrementOne(n int) int {
    return n + 1
}

func DecrementOne(n int) int {
    return n - 1
}

func Square(n int) int {
    return n * n
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Давайте снова запустим тесты.

// go test

PASS
ok      github.com/damiensedgwick/codewars/makeTheDeadFishSwim  0.195s
Вход в полноэкранный режим Выход из полноэкранного режима

Намного лучше, так что же дальше.

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

Тест

func TestSplitString(t *testing.T) {
    assert.Equal(t, []string{
        "i",
        "i",
        "i",
        "s",
        "d",
        "o",
        "s",
        "o",
    }, SplitString("iiisdoso"))
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Если мы запустим go test, он завершится неудачей, потому что такой функции еще не существует, поэтому давайте создадим ее.

func SplitString(s string) []string {
    return strings.Split(s, "")
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

func TestParse(t *testing.T) {
    assert.Equal(t, []int{8, 64}, Parse("iiisdoso"))
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

func Parse(input string) []int {
    var output = []int{}

    count := 0

    s := SplitString(input)
    for _, v := range s {
        switch v {
        case "i":
            count = IncrementOne(count)
        case "d":
            count = DecrementOne(count)
        case "s":
            count = Square(count)
        case "o":
            output = append(output, count)
        default:
            continue
        }
    }

    return output
}
Вход в полноэкранный режим Выход из полноэкранного режима

Итак, теперь у нас есть наша функция разбора, мы можем снова запустить тесты, и все они должны быть довольны.

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

Давайте посмотрим на результаты

Test Results:
Random tests
random test #1 (`ossrshsbzotsdkpvuisekvcci`)
random test #2 (`odzosiqdshbiikiisiiyigdlzyd`)
random test #3 (`sokssndofoimuqbqipsedfodticuotosdso`)
random test #4 (`siiooisdisijoilusdshqddzosiotdoio`)
random test #5 (`oiiswwmaisdosisooitio`)
random test #6 (`sdhhidyoioimsoidioooodsiddoio`)
...
random test #94 (`dssdbiddoskuoondgirikdeuolspdjtsjkisido`)
random test #95 (`rsoboobssixzzoudfiinbddgm`)
random test #96 (`iooeioyoyviwsoqsisdhaixxl`)
random test #97 (`idsdodsfsdbxdismiskdsis`)
random test #98 (`icdzdsissuigoijvopdddpiddgsw`)
random test #99 (`diioidooooisssmdiiisiiosossiiohivvowud`)
random test #100 (`odooszoatitdwdibkdko`)
Вход в полноэкранный режим Выход из полноэкранного режима

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

package kata

func Parse(data string) []int{
  output := []int{}
  var val int
  for _, s := range data {
    switch string(s) {
      case "i":
        val++
      case "d":
        val--
      case "s":
        val = val * val
      case "o":
        output = append(output, val)
    }
  }

  return output
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Оцените статью
Procodings.ru
Добавить комментарий