#18 golang go test Автоматическое тестирование - пример программы
Primary tabs
Forums:
Напишем простую программу для "уникализации" строк, которые подаются в стандартный ввод:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewScanner(os.Stdin)
var prev string
// потенциально бесконечный цикл:
for in.Scan() {
txt := in.Text()
if txt == prev {
continue
}
if txt < prev {
panic("Строки не отсортированы!")
}
prev = txt
fmt.Println(txt)
}
}
(при запуске в vscode возможна проблема, но есть решение)
Нам потребуется несколько стандартных пакетов:
- «os» для получения доступа к стандартному вводу,
- «fmt» для форматированного вывода, буферизированный ввод-вывод из пакета io.
В функции для считывания ввода не посимвольно, а сразу всей строкой нам нужен сканер ввода. После того как мы его создали, будем построчно двигаться по вводу в цикле. Когда сканировать будет больше нечего, то scan вернет false, мы выйдем из цикла.
Каждую строку, которую мы считали, будем выводить на экран, если она прежде не встречалась (по факту - просто не совпадается с предыдущей)
Если новая строка больше текущей — будем сравнивать просто по байтам — то она новая, напечатаем ее. Если совпадает, то мы ее уже выводили. Если меньше, то это значит, что поступающие данные (условно "файл") не отсортирован, наша программа в этом случае работать не может - просто бросим панику.
Попробуем ее запустить и протестировать, верно ли работает. Вдруг мы сделали какую-то ошибку в ней, и на каких-то условиях она будет работать некорректно.
В следующем блоке мы напишем тесты для программы, чтобы быть уверенными, что она всегда срабатывает, даже если внести в неё какие-то модификации.
Порефакторим перед тестированием
Для того чтобы написать тесты, нам нужно программу немножко «отрефакторить». Важно, чтобы тестируемый код находился в функциях, отличных от main.
Вот так изменится программа после рефакторинга:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func uniq(input io.Reader, output io.Writer) error {
in := bufio.NewScanner(input)
var prev string
// потенциально бесконечный цикл:
for in.Scan() {
txt := in.Text()
if txt == prev {
continue
}
// отказываемся от паники в пользу возврата
// признака ошибки
if txt < prev {
return fmt.Errorf("строки не отсортированы")
}
prev = txt
fmt.Fprintln(output, txt)
}
return nil // пустая ошибка, если всё окей
}
func main() {
err := uniq(os.Stdin, os.Stdout)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
-- что мы тут изменили:
- Вынесем весь наш код в отдельную функцию, которая будет просто принимать на вход поток, из которого мы будем читать, и поток, куда мы будем писать результат. Это всё реализуется через интерфейс Reader и Writer.
- Вместо стандартного вывода будем использовать функцию Fprintln, которая первым параметром принимает интерфейс Writer.
- В случае ошибки больше не будем паниковать, а будем возвращать ошибку и обрабатывать ее уже в main или тестах.
- Не забудем вернуть пустую ошибку при корректном завершении функции.
Пишем тесты
Теперь давайте приступим к написанию непосредственно тестов. Тесты в Go должны лежать в файле, который имеет суффикс test.
В нашем случае файл будет называеться main_test.go - создадим его рядом с файлом программы.
Все тесты в Go:
- начинаются с префикса Test
- и принимают на вход единственный параметр тестирующего модуля
*testing.T
Заполним main_test.go следующим содержимым:
package main
import (
"bufio"
"bytes"
"strings"
"testing"
)
var testOkInput = `1 2
3
3
4
5`
// контрольные данные
var testOkResult = `1 2
3
4
5
` // последний перенос строки важен!
// тестируем корректных случай
func TestOk(t *testing.T) {
in := bufio.NewReader(strings.NewReader(testOkInput))
out := new(bytes.Buffer)
err := uniq(in, out)
if err != nil {
t.Errorf("Функция вернула ошибку")
}
result := out.String()
if result != testOkResult {
t.Errorf("Тестовые и контрольные данные не совпадают\n %v %v",
result, testOkResult)
}
}
// Тут вторая строка короче первой
// -- программа должна бросить ошибку
var testFailInput = `1 2
1`
// тестируем некорректный случай
func TestForError(t *testing.T) {
in := bufio.NewReader(strings.NewReader(testFailInput))
out := new(bytes.Buffer)
err := uniq(in, out)
if err == nil {
t.Errorf("Мы ожидали непустую ошибку! - а получили: %v", err)
}
}
-- теперь мы можем открыть в терминале папку с этим файлом и запустить тесты командой:
go test -v
-- где флаг -v указывает на то, что мы хотим видеть подробности запуска.
В примере выше мы описали два теста:
- TestOk() - проряет ситуацию, когда функция ошибку возвращать не должна и сравнивает ожидаемый вывод в консоль (в тесте мы его перехватываем) с реальным
- TestForError() - наоборот подает на вход данные, при которых тестируемая функция должна вернуть ошибку, этот тест упадет (сам бросит ошибку), если тестируемая функция не вернет ошибку.
Явное указание проблемы в тесте - вызов t.Error()
Как видим из кода выше, здесь, в тестах, мы в явной форме должны прописывать ситуации "падения" теста, вызывая, например t.Error() или t.Errorf() на единственном аргументе наших тестов, и передавая туда информацию о том, почему мы считаем в такой ситуации
- Log in to post comments
- 103 reads
vedro-compota
Mon, 03/30/2026 - 19:49
Permalink
отладка:
отладка:
package main import ( "bufio" "bytes" "fmt" "strings" "testing" ) var testOk = `1 2 3 3 4 5` var testOkResult = `1 2 3 4 5` func TestOk(t *testing.T) { in := bufio.NewReader(strings.NewReader(testOk)) out := new(bytes.Buffer) err := uniq(in, out) if err != nil { t.Errorf("Функция вернула ошибку") } result := out.String() fmt.Println(len(result)) fmt.Println(len(testOkResult)) fmt.Println(testOkResult == result) if result != testOkResult { t.Errorf("Тестовые и контрольные данные не совпадают\n %v %v", result, testOkResult) } }_____________
матфак вгу и остальная классика =)