LINUX.ORG.RU

Что такое юнит-тесты и как они работают?

 


3

1

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

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

Вот бредовый пример на Haskel:

module MyTest where
import Test.Hspec
import Test.QuickCheck

main = hspec $ do
  describe "myfunc" $ do
    -- проверяем ответ на конкретных входных данных
    it "should work for some examples" $ do
      myfunc "AWUBBWUBC" `shouldBe` "A B C"
      myfunc "AWUBWUBWUBBWUBWUBWUBC" `shouldBe` "A B C"
      myfunc "WUBAWUBBWUBCWUB" `shouldBe` "A B C"

      
    -- Или если мы знаем медленное решение, то проверяем что наше
    -- быстрое решение выдаёт теже ответы что и быстрое 
    -- на случайных данных
    it "should work for WUBranWUBWUBdomWUBWUB values" $ do
      property $ forAll (fmap concat $ listOf $ elements $ replicate 5 "WUB" ++ map return ['A'..'Z']) $ \xs ->
        myfunc xs `shouldBe` slow_solution xs
        
  where slow_solution :: String -> String
        slow_solution = unwords . words . go
          where go []               = []
                go ('W':'U':'B':xs) = ' ' : go xs
                go (x:xs)           =   x : go xs

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

вот небольшой список бесплатных статеек на некоторых языках: https://github.com/unicodeveloper/awesome-tdd

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

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

samson_b
() автор топика
Ответ на: комментарий от samson_b

Да. Это хорошо работает для чистых функций.

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

Вот тут немного про моки: https://habr.com/ru/post/141209/

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Спасибо.

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

Еще вопрос, а откуда эти правильные ответы должны взяться? Вот например моя функция берет HTML документ и возвращает список хэшей по определенному алгоритму. Как я узнаю правильный ответ без использования моего же алгоритма?

samson_b
() автор топика
Ответ на: комментарий от samson_b

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

Ну вообще-то, почти любая программа (кроме, возможно, мелкой скриптоты) должна быть на такие кусочки разбита, вне зависимости от наличия юнит-тестов. А так да, пишешь тесты на каждый модуль.

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

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

Спорное замечание №1. Не для любой программы модульное тестирование эффективно. К примеру для какой-нибудь гуйни к базе данных, где собственнно кода вне UI код наплакал, юнит-тесты, скорее, вредны. Ошибки, отлавливаемые юнит-тестами, там допустить сложно. Там, скорее, вылезают косяки проектирования, когда вроде бы всё предусмотрено, а нужную информацию в БД вводить невозможно или крайне неудобно. Тут гораздо эффективнее ручное тестирование человеком.

Другое дело, когда, например, у тебя большой объём «чистой логики». Тут юнит-тесты писать сам Бог велел.

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

hobbit ★★★★★
()
Ответ на: комментарий от samson_b

Еще вопрос, а откуда эти правильные ответы должны взяться? Вот например моя функция берет HTML документ и возвращает список хэшей по определенному алгоритму. Как я узнаю правильный ответ без использования моего же алгоритма?

А как без юнит тестирования ты бы понял что ты считаешь правильно, а не выводишь какую-то дичь? На минимальном примере, когда ты можешь посчитать на листочке что работает правильно.

Тесты ещё помогают в регресиях.

Вот есть у тебя лишь тест лишь на

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <title>Hello!</title>
    </head>

    <body>
        <h1>Hello World!</h1>
    </body>

</html>

не суть. И вроде как-то работает. Потом ты встречаешь страничку на которой твой скрипт не работает. Чинишь алгоритм, и добавляешь эту страничку с правильным ответом в тесты.

Так постепенно тесты и копятся…

fsb4000 ★★★★★
()
Ответ на: комментарий от samson_b

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

saibogo ★★★★
()
Ответ на: комментарий от fsb4000

кроме тестов мы также пишем моки

Или не пишем. Спасибо докеру.

WitcherGeralt ★★
()
Ответ на: комментарий от Deleted

не… Это я вам описал как в идеале все происходить должно.

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

saibogo ★★★★
()
Ответ на: комментарий от samson_b

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

Данные обычно: тривиалтные значения, граничные. То, что ты по сути гарантируешь в документации. Со временем, как правильно пишут, тест расширяется.

invy ★★★★★
()
Ответ на: комментарий от saibogo

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

Miguel ★★★★★
()
Ответ на: комментарий от fsb4000

А как без юнит тестирования ты бы понял что ты считаешь правильно, а не выводишь какую-то дичь?

Да никак, т.е. я не проверял математику расчетов. Проверял другие вещи. Например моя функция последовательно делает определенные преобразования с HTML документом. 1. Удаляет HTML тэги. Соответственно когда я делал отладку функции - смотрел как выглядит текст до и после преобразования. 2. Замена всех символов, кроме букв, на пробелы. Тоже самое, смотрю на текст до и после обработки, вручную. и т.д.

Все эти этапы внутри одной финкции. Не буду же я разбивать функцию из 15 строк на 4 модуля и каждый модуль тестировать юнит-тестами отдельно?

samson_b
() автор топика
Ответ на: комментарий от invy

как вы достали все, кушать не могу. в мире миллион разных методологий для разных команд, продуктов, технологий, но дурачки до сих пор считают, что достаточно выучить мантру «тесты пишутся до/вместе/вместо/после кода» и её хватит на все случаи жизни

goper48265
()
Ответ на: комментарий от samson_b

вообще-то если функция делает больше двух независимых операций — не важно, сколько они занимают строк, хороший тон распилить её на несколько.

goper48265
()
Ответ на: комментарий от fsb4000

Вот есть у тебя лишь тест лишь на [skip] не суть. И вроде как-то работает. Потом ты встречаешь страничку на которой твой скрипт не работает. Чинишь алгоритм, и добавляешь эту страничку с правильным ответом в тесты. Так постепенно тесты и копятся…

О! Хороший пример, у меня примерно так отладка и идет, только без юнит тестов :) Скрипт работает, нарывается на HTML, на котором не работает, я разбираюсь в чем дело, правлю алгоритм и скрипт теперь справляется и этим HTML тоже. Вот только какой смысл писать тест, когда я уже пофиксил эту ошибку? Я теперь уверен, скрипт будет обрабатывать такого рода HTML тоже.

samson_b
() автор топика
Ответ на: комментарий от samson_b

Как я узнаю правильный ответ без использования моего же алгоритма?

В общем случае, если алгоритм уникальный и не разбивается на композицию под-алгоритмиков (для которых уже существуют эталонные реализации, которым доверяешь как ground truth) - никак. Юнит-тесты - это такие вспомогательные подпорки для основного кода, чтобы у разрабов и их манагеров была психологическая уверенность, что «что-то там в релизе работает», и защита от новичков, правящих легаси-систему, чтобы они не поломали ее сразу. Для проверки корректности алгоритма юнит-тесты не предназначены.

seiken ★★★★★
()
Ответ на: комментарий от goper48265

вообще-то если функция делает больше двух независимых операций — не важно, сколько они занимают строк, хороший тон распилить её на несколько.

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

    def words_hash(text):
        #removing all html tags
        text = re.sub('<.*?>', ' ', text)
        #replacing all non alphabetical characters with spaces
        text = re.sub('[^a-z]', ' ', text.lower())
        #removing all words with less than 3 letters and extra spaces
        normal_text = ' '.join(wrd for wrd in text.split() if len(wrd)>2)
        words = normal_text.split()
        i = len(words)
        n = 0
        hashes = []
        while n <= i-3:
            st = words[n] + words[n+1] + words[n+2]
            hashes.append(int(hashlib.sha256(st.encode('utf-8')).hexdigest(), 16) % 10**19)
            n = n + 1
        hashes.sort()
        return list(dict.fromkeys(hashes))
samson_b
() автор топика
Последнее исправление: samson_b (всего исправлений: 1)
Ответ на: комментарий от seiken

Юнит-тесты - это такие вспомогательные подпорки для основного кода, чтобы у разрабов и их манагеров была психологическая уверенность, что «что-то там в релизе работает», и защита от новичков, правящих легаси-систему, чтобы они не поломали ее сразу. Для проверки корректности алгоритма юнит-тесты не предназначены.

Я понял, о каком-то глубоком и всестороннем тестировании каждой функции речи не идет.

samson_b
() автор топика
Ответ на: комментарий от samson_b

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

goper48265
()
Ответ на: комментарий от samson_b

разделить и переписать — да, разложить по разным файлам — это уже как тебе удобнее

goper48265
()
Ответ на: комментарий от samson_b

Не буду же я разбивать функцию из 15 строк на 4 модуля и каждый модуль тестировать юнит-тестами отдельно?

С т.з. «чисто Юнит-теста» (такого, который «тест после продакшн кода» в отличие от всяких TDD методологий, в которых UT используются не только ради тестирования, но и для поддержания простой и понятной структуры кода) по большому счету не важно, как структурирован код, если это не создает мороки при написалнии UT.

Важно, например, что все юнит-тесты в совокупности покрывают как можно более широко продакшн код. Например, если какое-то условие внутри функции не тестируется ни одним UT, нужно такой UT придумать и добавить.

seiken ★★★★★
()
Ответ на: комментарий от samson_b

о каком-то глубоком и всестороннем тестировании каждой функции речи не идет.

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

seiken ★★★★★
()

как юнит-тесты будут его тестировать? Как можно протестировать кусок кода внутри файла?

«Кусок кода внутри файла» - это функция. Функцию можно вызвать с разными аргументами и сравнить её выходные значения с ожидаемыми. В общем-то и всё.

slovazap ★★★★★
()
Ответ на: комментарий от samson_b

какой смысл писать тест, когда я уже пофиксил эту ошибку

Смысл в том, что бы после каких-то доработок быстро проверить что ничего не отвалилось.

no-such-file ★★★★★
()
Ответ на: комментарий от samson_b

Не буду же я разбивать функцию из 15 строк на 4 модуля и каждый модуль тестировать юнит-тестами отдельно?

Нет, конечно. У тебя неверное понятие о модуле. Модуль включает в себя функции (и целые классы), а не наоборот.

Если у тебя функция из 15 строк — пусть тест и вызывает её как единое целое. Другое дело, что, возможно, стоит предусмотреть несколько вызовов с разными наборами исходных данных.

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

Для такого рода программ юнит-тест и интеграционный тест перестают отличаться. «Пирамиду тестов» строят, когда речь идёт про проект из нескольких модулей, в каждом модуле одна или несколько функций или целый класс. У тебя просто вырожденный случай.

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от Miguel

мИГУЕЛ ★★★★★ (01.04.20 10:36:56)

Ребят, я понимаю, что первое апреля, но лучше уберите это нах.

а я уж испугался..

AndreyKl ★★★★★
()
Ответ на: комментарий от samson_b

Вот только какой смысл писать тест, когда я уже пофиксил эту ошибку?

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

а уже написали такой же ответ

Смысл в том, что бы после каких-то доработок быстро проверить что ничего не отвалилось.

+1

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от goper48265

непосредственно. как ты проверяешь работоспособность того что ты написал, если у тебя не hello world?

invy ★★★★★
()
Ответ на: комментарий от goper48265

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

invy ★★★★★
()
Ответ на: комментарий от invy

карпин.wmv

ну и? код может остаться говном и после написания тестов, и чё?

goper48265
()

Найди что-нибудь – смотреть покрытие (coverage). Общее и по веткам. Помогает при тестировании.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.