LINUX.ORG.RU
ФорумTalks

ненависть: python - тесты курильщика

 , ,


0

2

@gaga, Вы просили о тестах, их есть у нас. Как выглядит тест на нормальном языке, пример:

use Test::More;
plan tests => 6;

require_ok 'User';
my $user = new User name => 'Вася';
ok $user => 'Пользователь инстанцирован';
ok !$user->id, 'У нового пользователя id отсутствует';
ok $user->save, 'Пользователь сохранён';
ok $user->id, 'Id назначен при сохранении';
is $user->foo, 'bla', 'Значение по умолчанию для foo';

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

Теперь тест курильщика, переписываем то же самое.

import pytest
import importlib

# без переменных уровня модуля или подобных хаков данные
# из теста в тест не передать
User = None
vuser = None

def test_import():
   User = importlib.import_module('User')
   assert User, 'Модуль загружен'

def test_constructor():
   vuser = User(name='Вася')
   assert vuser, 'Пользователь инстанцирован'

def test_id_presave():
   assert not vuser.id, 'У нового пользователя id отсутствует'

def test_save():
   assert vuser.save(), 'Пользователь сохранён'

def test_id_postsave():
   assert vuser.id, 'Id назначен при сохранении'

def test_default_foo():
   assert vuser.foo == 'bla', 'Значение по умолчанию для foo'

Итого:

  • тестовая система (самая распространенная - pytest) сделана так, чтобы пользователь писал максимально много букв не относящиеся к тестам
  • передача данных из теста в тест затруднена
  • лог тестирования страшенный не человеко- и не машино- читаемый
  • вторая по популярности (или первая?) тестовая система еще более многосложна, предполагает не только объявления функций, но и классов
  • со стороны тестовой системы отсутствует поддержка частых паттернов: например тестирование строк на регекспы (like/unlike, тестирование импортируемости, итп)
  • тестирование количества тестов так же крайне неочевидно
  • автоматический подсчет статистики числа пройденных тестов в большой системе - так же ППЦ, ввиду отсутствия машиночитаемости
  • масса типовых слов, которые зарезервированы как ключевые (str, list, is, in, from, и так далее) заставляет изобретать сложные имена переменным

и тут еще мы не рассматриваем например ситуацию, что в начале теста система должна поднять некоторую инфраструктуру.

соответственно именно поэтому в питонячьих проектах тесты народ и не пишет обычно.

а еще эта хрень-кака молча глотает двойные


def foo():
   bla

def foo():
   ble

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

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

ППЦ

PPS: а еще есть уродские декораторы. какой шизик их придумал? причины почему их придумали понятны: за отсутствием нормальных лямбд хоть какой-то заменитель

★★

Ответ на: комментарий от rsync

по тестам курильщика сказать нечего

Телепат вернулся! Телепат вернулся.

На самом деле я сегодня просто очень сдержанный и грустный. Да и на каждую твою мелочную субъективщинку отвечать лень. На две «объективщинки» - ответил. В остальном все просто: питон не городит зоопарк синтаксиса => тесты не выглядят как перловка. Что еще на это можно сказать?

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

объяснишь, почему у тебя конкретная ошибка не ловится упавшим тестом

На случай ошибки типа тест забыли написать/раскоментировать/неудалить/teSt_ом обозвать?.

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

почему взаимодействие с бд нельзя тестировать тоже? непонятно

есть класс, у него метод - сохранить в бд

почему не тестировать этот метод?

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

Для этого есть специально обученные макаки люди - тестировщики

тестировщики всегда дороже кмпьютеров

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

А так вообще «да ну нах эти тесты - код запаришься писать, ещё тесты им подавай

код с тестами развивается проще и главное - быстрее, чем без оных

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

классический случай когда ООП вреден

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

нельзя тестировать тоже? непонятно

Одновременно - нет.

почему не тестировать этот метод

Метод тестировать можно, но с фейковой БД, которая всегда отвечает ок (или не ок, смотря что требуется протестировать).

быстрее, чем без оных

Лучше конечно хоть какие-то тесты, чем никаких. Но как тебе уже сказали, никто не мешает запихать всё в один тест, раз уж ты всё равно мешаешь всё в кучу.

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

Сохранили куда? Это совсем другой вид тестов

сводить всё только к функциональным тестам - глупость

использовать разные подходы для функциональных и пусть и частично-интеграционных тестов - глупость в двойне

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

Зато дешевле разработчиков.

разработчики, которые не пишут тестов не стоят зарплат тестировщиков

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

один глупость пишет другой ее картинками снабжает

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

инструмент некачественный

«плохая снасть отдохнуть не даст»

так говорили рыбаки в Мурманске

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

я портировал уже Test::More на Python

и еще пару модулей. Выпилил из тестов ООП-мусор, получилось более -менее прилично, с поправкой на ацтойность питона конечно, но всё равно лучше чем то что было до меня

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

Так за твоими переписывать тебе же приходится. Зачем тебе этот пучок? Почему нельзя нанять одного нормального? Зачем вообще вам там на нём писать, если всё равно на нём писать у вас не умеет?

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

причём тут вообще запятая? он не осилил найти unittest и использует библиотеку для импорта, хотя есть конструкция __import__

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

он не осилил найти unittest

с unittest я начал. он хуже pytest ибо многословнее

чтобы сделать одну проверку нужен class и def в нем

в pytest достаточно def

но тест - это обычно просто плоский скрипт и ООП ему вредит!

использует библиотеку для импорта, хотя есть конструкция __import__

чем мне нравятся адепты питона что сами не знают как правильно и что. Читай внимательно первый абзац:

__import__(...)
    __import__(name, globals={}, locals={}, fromlist=[], level=-1) -> module
    
    Import a module. Because this function is meant for use by the Python
    interpreter and not for general use it is better to use
    importlib.import_module() to programmatically import a module.
    
    The globals argument is only used to determine the context;
    they are not modified.  The locals argument is unused.  The fromlist
    should be a list of names to emulate ``from name import ...'', or an
    empty list to emulate ``import name''.
    When importing a module from a package, note that __import__('A.B', ...)
    returns package A when fromlist is empty, but its submodule B when
    fromlist is not empty.  Level is used to determine whether to perform 
    absolute or relative imports.  -1 is the original strategy of attempting
    both absolute and relative imports, 0 is absolute, a positive number
    is the number of parent directories to search relative to the current module.
rsync ★★
() автор топика
Ответ на: комментарий от rsync

А если нормально, в твоём примере неправильно просто всё. Ты же не используешь импортированный pytest, а если предполагался именно тест импорта - исключение не будет корректно обработано, функция вылетит до assert. Остальной тест тоже вылетит, ибо ты забыл про global и глобальная переменная осталось None. А ещё потому что ты используешь User как класс, но импортируешь модуль. Для импорта модуля __import__ ничем не хуже. Но это само по себе бред, нормальный человек просто сделал бы from user import User. Хз зачем вообще писать юнит-тесты для импорта, типа кодер настолько тупой, что у него что-то крашится в коде модуля и он не сможет ничего понять в консольном выхлопе? Или это проект со сфеерическими наворотами в вакууме?

Python применяет оптимизации для классов. Отказ от ООП приводит к падению производительности и возрастанию количества костылей, чтобы реализовать подобие ООП на уровне модуля. ООП поймёт любой новичок, выгода от такого решения не ясна. Экономия строк, серьёзно?

Список придирок звучит как полный бред и требует озвучивания ТЗ. Возможно Python не подходит для твоих задач и потому не стоит продолжать себя насиловать. Но больше похоже на то, что ты просто нахватался плохих практик, а другой язык (Python) препятствует их использованию.

import User from user


test_user = User('Вася')

# это бред, ну ладно
assert test_user, 'Пользователь не инициализирован')
assert test_user.id, 'У пользователя отсутствует id.'
assert test_user.save(), 'Пользователь не сохранён.'
assert test_user.foo == 'bla', 'Значение foo по-умолчанию не равно "bla".'

А вот правильный тест:

import unittest


class MyTest(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(MyTest, self).__init__(*args, **kwargs)
        with self.assertRaises(ТвояError):
            user_module = __import__('user')
            self.user = user_module.User('Вася')
            self.assertIsNotNone(self.user)

    def test_id(self):
        self.assertIsNotNone(self.user.id)
        self.assertTrue(self.user.id > 0)
    
    def test_foo(self):
        self.assertEquals(self.user.foo, 'bla')

    def test_save(self):
        with self.assertRaise(ТвояError):
            self.assertTrue(self.user.save())


if __name__ == '__main__':
    unittest.main()

Тебе следует научится читать доки. С помощью unittest можно делать тесты импорта, последовательность тестов, группировать тесты, даже тест по регуляркам там есть (который ты не увидел), поднятие/свёртывание инфраструктуры (до/после тестов и до/после вызова каждого теста). И ещё много чего.

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

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

А вот тест на pytest:

import pytest
from importlib import import_module


user = None


def setup():
    global user
    with pytest.raises(ТвояError):
        user_module = import_module('user')
        user = user_module.User('Вася')
        assert user, 'Пользователь не инициализирован'


def teardown():
    with pytest.raises(ТвояError):
        assert user.save(), 'Пользователь не сохранён.'


def test_id():
    assert user.id, 'У пользователя отсутствует id.'


def test_foo():
    assert user.foo == 'bla', 'Значение foo по-умолчанию не равно "bla".'

Тут правильный тест импорта, с обработкой краша (там очевидная ModuleNotFoundError, но хз что ты мог наворотить в модуле, поэтому сам думай) и соблюдением порядка. Сначала делается тест модуля и инициализируется класс User, потом прогоняются тесты по нему, потом делается тест сохранения.

И если уж ты взялся читать доки по importlib, то читай их полностью:

The import_module() function acts as a simplifying wrapper around importlib.__import__().

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

Экономия строк, серьёзно?

IMO всё ещё хуже class:def: на ровном месте отъедает 2 этажа зазубрин. Это действительно раздражает.

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

И если уж ты взялся читать доки по importlib, то читай их полностью

так я и прочитал. и процитировал: «не делайте так, а делайте этак, потому что это внутренняя кухня питон»

то есть поведение может измениться, метод - переименоваться, то что сейчас это враппер - не значит что так останется в будущем.

насчет globals и исключения от импорта модулей - моя ошибка, признаю. Связано с тем что писалось прямо в тексте комента.

с вот этим категорически не согласен:

ООП поймёт любой новичок, выгода от такого решения не ясна. Экономия строк, серьёзно?

  1. экономия строк - это серьезно, поскольку это наглядность.

    Тест - это всегда плоский алгоритм, когда пункт 1 предваряет пункты 2 и 3.

    Есть многие алгоритмы приведение которых к ООП им вредит.

    даже при наличии ветвления

    Вот например решение квадратного уравнения:

    import math
    def foo(a, b, c):
       if a == 0:
          raise ValueError('Это уравнение не квадратное')
       d = b * b - 4 * a * c
       if d < 0:
          return ()
       if d == 0:
          return (-b / (2 * a))
       return ((-b + math.sqrt(d)) / (2 * a), (-b - math.sqrt(d)) / (2 * a))
    

    Если переписать эти пять строк на ООП, это только повредит коду. а зачем здесь ООП обосновать будет так же сложно, как и зачем ООП в тестах.

  2. многословность ООП скрывает от нас алгоритм и вредит наглядности (еще раз)
  3. мало того - эти pytest и unittest выполняют тесты не в том порядке что описаны, что я считаю крайним злом.

    Например в твоем коде

        def test_foo(self):
            self.assertEquals(self.user.foo, 'bla')
    
        def test_save(self):
            with self.assertRaise(ТвояError):
                self.assertTrue(self.user.save())
    

    порядок этих двух тестов зависит от (возможно) имен функций, что крайне неочевидно, возможно в рантайме меняется (это не точно). А тест на foo вполне может зависеть на факт save (рядовая ситуация). Если foo - дефолт, который назначает БД, то так обычно и есть

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

А так вообще «да ну нах эти тесты - код запаришься писать, ещё тесты им подавай».

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

bread
()
Ответ на: комментарий от InterVi

if __name__ == '__main__':

super(MyTest, self).__init__(*args, **kwargs)

Жесть как она есть.

показывает что ООП тут не нужен и аффтор просто тупо копирует паттерн, сам не понимая занахрена он тут

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

выполняют тесты не в том порядке

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

bread
()
Ответ на: комментарий от rsync

эти pytest и unittest выполняют тесты не в том порядке что описаны, что я считаю крайним злом.

считающих это крайним злом нужно изолировать в районах Крайнего Севера

тест на foo вполне может зависеть на факт save (рядовая ситуация)

ох

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

ох

понятие «значение по умолчанию» должно быть в одном месте.

если оно в двух местах (в модели и в базе, например) это иногда приводит к трудным багам.

поэтому тест на значение дефолта после сохранения - естественен

так же естественно тестировать успешность сохранения.

соответственно два теста должны следовать друг за другом

ООП здесь вреден, а ООП от фонаря назначающий порядок вызовов тестам - так и вдвойне вреден

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

два теста должны следовать друг за другом

а автор таких тестов должен последовать туда, где расстояние до ближайшего компьютера не меньше 500 км

ООП от фонаря назначающий порядок вызовов тестам - так и вдвойне вреден

ООП тут при том, что что?

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

автор таких тестов должен последовать туда, где расстояние до ближайшего компьютера не меньше 500 км

я помню этих идиотов, пропагандирующих что тех кто использует goto надо гнать

полезного кода, ими написанного, я не встречал

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

ООП и знание языка и тесты - понятия не связанные

напишите решение квадратного уравнения в ООП стиле? можно, но это - дебилизм

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

вот например JS https://qunitjs.com

да функционально, но не ООП:

проблем с планами нет

проблем с порядком выполнения тестов нет (при необходимости тестировать асинхронщину - есть поддержка)

итп

rsync ★★
() автор топика
Последнее исправление: rsync (всего исправлений: 2)

Лол, проснулись

В 2018 году люди спорят, сможет ли Rust/WebAssembly потеснить жабоскрибт на фронтенде, а они тут бои зомби vs престарелый устраивают.

Pacmu3ka
()
Ответ на: Лол, проснулись от Pacmu3ka

В 2018 году люди спорят, сможет ли Rust/WebAssembly

не сможет

rsync ★★
() автор топика
Ответ на: Лол, проснулись от Pacmu3ka

жабоскрибт

кстати более вменяемый язык чем питон: если что-то есть, то оно доделанное, а не как у питона - кусок технологии в языке (async/await) другой - в либе (asyncio)

и тот что в либе меняется между минорами (sic!)

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

Аргументы будут? На нормальных проектах тесты таки на ООП, с использованием фреймворков. А вот портянки встречаются только у васянских поделий с гитхаба, которыми пользуются полторы калеки.

Ты не задумывался, что это лично тебе сложно и не понятно, а всем остальным - нормально?

InterVi ★★★★
()
Ответ на: комментарий от rsync
if __name__ == '__main__'

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

super(MyTest, self).__init__(*args, **kwargs)

Конструктор предка переопределён, а в нём что-то делается. Без вызова super класс не инициализируется.

Ещё одна демонстрация незнания языка с твоей стороны. Так если не знаешь, зачем выделываешься?

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

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

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

Ещё одна демонстрация незнания языка с твоей стороны.

когда врач говорит «ужас какой!» это не означает его некомпетентность

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

А плохо поддерживаемые, плохо читаемые, медленные и багованные тесты не раздражают?

плохо поддерживаемые тесты - как раз из за ООП, который в них вреден

медленные - оттуда же

багованные - опять же из за неправильного применения инструмента (ООП)

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

На любом другом языке класс:метод точно так же съест отступ

Ну, да. Это не проблема питона, это проблема избыточной классовости.

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

тут многие почему-то ассоциируют Питон и ООП: типа одно без другого ни-ни

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