Как стать автором
Обновить

Размышления о TDD. Почему эта методология не получила широкого признания

Блог компании Издательский дом «Питер» Высокая производительность *Программирование *TDD *Профессиональная литература
Перевод
Всего голосов 11: ↑10 и ↓1 +9
Просмотры 14K
Комментарии 62

Комментарии 62

«В TDD подобное необходимо из-за повторения действий» — что-то тут не так. При правильной организации труда повторения действий программиста должны быть минимальны — пусть повторением компьютер занимается. (и вроде с правильным инструментом так и получается, разве нет?)
НЛО прилетело и опубликовало эту надпись здесь
Что-то слишком много воды и очень абстрактных размышлений без конкретики.
Может именно поэтому TDD не заходит людям?
А может он не заходит, потому что не обеспечивает необходимую производительность труда сравнительно с классическим подходом, в котором юнит-тесты пишут после кода?

Лично я пишу до написания кода только функциональные позитивные тесты, чтобы упростить тестирование разрабатываемого куска кода. Юнит-тесты — только после кода и только на сложные участки. Ведь даже 100% покрытие не спасет от ошибок, связанных с интеграцией компонентов.
НЛО прилетело и опубликовало эту надпись здесь
Очередная статья, где кроме TDD и полного отсутствия тестирования других вариантов не рассматривается. TDD не заходит людям главным образом потому, что по нему тесты надо писать до написания кода, а это куда сложнее для человеческого мозга, чем когда уже есть код и видно как с ним взаимодействовать.
НЛО прилетело и опубликовало эту надпись здесь
Хотелось бы спросить автора и его дядюшку, почему они уверены, что их тесты корректны? И кто тогда будет писать тесты, тестирующие тесты? А тесты, тестирующие тесты, тестирующие тесты? Ну и так далее.
Как тестировать приватные методы? А методы с побочными эффектами? Я пытался узнать у тех, кто не испытывает неприязни к ТДД, и получил ответ, что никак. Какой тогда в этом смысл? Большая часть функционала обычно бывает скрыта, и тестировать публичные методы, предполагая, что приватные выдадут корректный результат, как-то несерьезно. Получается, что покрытие тестами будет достигать в лучшем случае процентов тридцать (чаще намного меньше).
Думаю, что я могу ответить на вопрос, почему эта методология не получила широкого признания. Потому что она бессмысленна. Люди не хотят тратить свое время на то, что заведомо не сработает или даст мизерный выхлоп.
Хотелось бы спросить автора и его дядюшку, почему они уверены, что их тесты корректны?

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


И кто тогда будет писать тесты, тестирующие тесты? А тесты, тестирующие тесты, тестирующие тесты? Ну и так далее.

Кто тестирует тех кто тестирует вручную?


Как тестировать приватные методы?

Вызовом публичных?


А методы с побочными эффектами?

Проверкой на наличие побочных эффектов?


Я пытался узнать у тех, кто не испытывает неприязни к ТДД, и получил ответ, что никак.

Про тесты написаны кучи книжек — попробуйте почитать хотя бы одну.


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

Если у большая часть функционала скрыта, это значит что у вас god object или подобный антипаттерн. Надо рефакторить — разделять уровни абстракции и их тестировать отдельно.


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

А как вы тестируете вручную — не покрываете приватные методы?


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

Люди не хотят тратить время на то, чтобы хотя бы погуглить

Как тестировать приватные методы?
Вызовом публичных?

У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.


Если у большая часть функционала скрыта, это значит что у вас ~~god object ~~

Инкапсуляция.


Надо рефакторить — разделять уровни абстракции и их тестировать отдельно.

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

У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.

Во-первых, это зависит от того, насколько они друг на друга влияет. TDD потребует столько тестов, сколько надо чтобы написать этот код. Если ветка в приватном методе вызывается независимо от веток в вызывающем методе, то TDD не потребует рассматривать ее в других комбинациях.


Во-вторых, если вы действительно настаиваете на тестировании всех кобинаций, расскажите как вы это делаете? Мне сейчас представляется, что с любым другим подходом эта проблема станет больше — при тестировании через UI вручную количество комбинаций станет больше.


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

Это не делается механически. Если что-то хочется протестировать отдельно, надо подумать почему — представляет оно отдельную концепцию или нет. Условно, если класс Клиент содержит код проверки счета, надо рассмотреть возможность создать класс Счет, перенести код проверки туда и протестировать отдельно.


Т.е. выделение дополнительного слоя абстракции со своей инкапсуляцией.

Если ветка в приватном методе вызывается независимо от веток в вызывающем методе

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


если вы действительно настаиваете на тестировании всех кобинаций

Нет, я настаиваю на компонентном тестировании (Подробнее об этом тут: https://habr.com/post/351430/). Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.


Если что-то хочется протестировать отдельно, надо подумать почему — представляет оно отдельную концепцию или нет.

Ну а если нет? Просто алгоритм сложный. Например, вам нужно спозиционировать слова в параграфе. Для этого нужно определить ширины слов, определить какое слово окажется на какой строке, в каждой строке отсортировать в соотстветствии с направлением письменности, вычислить размер лишнего пространства, распределить его по всем блокам, которые допускают ресайз (пробелы там, инлайн-блоки), рассчитать общую базовую линию для каждой строки, рассчитать высоты строк, и, наконец, спозиционировать слова учитывая выравнивание текста по горизонтали и вертикали. Тестировать все эти шаги через единственный публичный метод "рассчитай лейаут" — сдохнуть можно.

Вы сейчас говорите про тестирование белого ящика

Я сейчас говорю о TDD. У меня складывается впечатление, то вы сейчас пытаетесь критиковать процесс, про который не знаете даже теоретически


Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.

Расскажите, как TDD сделает из этого 27 кейзов.


Каждый из этих приватных методов (а точнее их видимость придётся повысить)

Ээээ? Вы ранее тут что-то говорили при нарушение инкапсуляции?


нужно определить ширины слов

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

У меня складывается впечатление, то вы сейчас пытаетесь критиковать процесс, про который не знаете даже теоретически

Похоже вы вкладываете в понятие ТДД существенно больше, чем все остальные. Не поделитесь с нами вашим видением что же такое ТДД на самом деле?


Расскажите, как TDD сделает из этого 27 кейзов.

ТДД-то ту при чём? Это следствие предлагаемого вами косвенного тестирования.


Вы ранее тут что-то говорили при нарушение инкапсуляции?

А чем инкапсуляция от сокрытия отличается знаете?


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

Что вы там тестировать в слове собрались? Константы?

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

Мне кажется, что такую фразу может сказать либо тот, что прочитал всех остальных, либо тот, кто не очень-то отвечает за свои слова.


ТДД-то ту при чём?

https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html


A few years later this fine granularity was codified into three rules: the so-called Three Laws of TDD.
  1. You must write a failing test before you write any production code.
  2. You must not write more of a test than is sufficient to fail, or fail to compile.
  3. You must not write more production code than is sufficient to make the currently failing test pass.

Как только вы покроете ваши 9 случаев у вас не получится написать красный тест.


А чем инкапсуляция от сокрытия отличается знаете?

The term encapsulation is often used interchangeably with information hiding. Not all agree on the distinctions between the two though; one may think of information hiding as being the principle and encapsulation being the technique. A software module hides information by encapsulating the information into a module or other construct which presents an interface.


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


Что вы там тестировать в слове собрались? Константы?

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

Как только вы покроете ваши 9 случаев у вас не получится написать красный тест.

Этот момент может настать и после 5 тестов, и после 3 и даже после первого же цикла. Значит ли это, что остальные тесты писать не надо? Будете ли вы уверены в последующем рефакторинге, если эти 5/3/1 тест всё время будут зелёными?


использование компонента не через его интерфейс

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


если вы делаете что-то приватное публичным

Собственно, кроме public и private других модификаторов доступа не знаете?


может приводить к хрупким тестам

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


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

Вызов внешнего апи, там нечего тестировать.

Этот момент может настать и после 5 тестов, и после 3 и даже после первого же цикла.

Продемонстрируйте мне, пожалуйста, тест который приводит к написанию кода из двух уровней с тремя вариантами каждый при соблюдении правила 2 TDD. "You must not write more of a test than is sufficient to fail, or fail to compile." и 3 "You must not write more production code than is sufficient to make the currently failing test pass.".


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

Не нашел в статье определения юнит теста, по которому они должны быть хрупкими. Нашел в статье, что solitary юнит тесты с использованием моков должны иногда давать не те же результаты, что и тестирование с продакшн кодом. Про Solitary vs Sociable и Mocks aren't stubs можно почитать у Фаулера.


Вызов внешнего апи, там нечего тестировать.

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

Еще хотелось бы отметить элегантноть выбранного фреймворка и стиля тестов в статье:


// Нахрена везде повторять этот "мол"?
$mol_test({
 // В названии теста не отражено требование которое он проверяет
'print greeting to defined target'() {
        const app = new $my_hello_message
        app.target = ()=> 'Jin'
        // опять mol. зачем, мол, повторять, мол, везде, мол?
        $mol_assert_equal( app.sub().join( '' ) , 'Hello, Jin' )
    } ,

})

Сравним с mocha.


import { hello } from './hello-world';
import { expect } from 'chai';
import 'mocha';

describe('Hello function', () => {

  it('should return hello world', () => {
    const result = hello();
    expect(result).to.equal('Hello world!');
  });

});

Читается почти как английский текст

Нахрена везде повторять этот "мол"?

Это пространства имён. Если вам режет глаз, то вы точно так же как и с импортами можете переименовать:


const hello = $my_hello
const equal = $mol_assert_equal
const test = $mol_test

test({
    'returns greetings'() {
        equal( hello() , 'Hello world!' )
    } ,
})

Вообще, забавно, что вы сравниваете совершенно разные тесты совершенно разных вещей.


Читается почти как английский текст

Вы таки гуманитарий?


В названии теста не отражено требование которое он проверяет

Там же английским по белому написано: печатает правильное приветствие указанному адресату. Или вам без повторения "should" (опять should. зачем, should, повторять, should, везде, should?) не понятно?

Это пространства имён. Если вам режет глаз, то вы точно так же как и с импортами можете переименовать:

Зачем же вы живете с этим mol не переименовывая?


Вы таки гуманитарий?

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


даже test('returns greeting') читается чуть легче чем mol_test('return greeting') за счет третьего лица и удаления префикса.


в 'print greeting to defined target' вообще неоднозначность — я сначала прочитал это что надо распечатать что-то в target.


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

Неа, там написано "напечатать", а не "печатает", а во-вторых не однозначно, к чему относится "to" — к print или greeting.


"печатать приветствие к определенной цели".


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


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


Кстати, что такое sub — там про сендвичи, или это какая-то концепция понятная вебдевам с первого взгляда?

Зачем же вы живете с этим mol не переименовывая?

Это удобно и позволяет не запутаться. Одна и та же сущность во всех контекстах называется одинаково.


иметь офигеть какие преимущества

Это тот самый случай.


там написано "напечатать", а не "печатает"

Ага, опечатался.


не однозначно, к чему относится "to" — к print или greeting

Я хз, как это однозначно сформулировать на английском. Поэтому читайте лучше код — он однозначный.


никто не стал даже и пытаться?

Отвечу за всех. Никто не стал. Остальные наслаждаются.


Кстати, что такое sub — там про сендвичи, или это какая-то концепция понятная вебдевам с первого взгляда?

Сокращение от submissive.

Это удобно и позволяет не запутаться. Одна и та же сущность во всех контекстах называется одинаково.

По мне, так это недостаток — отсутствие учета контекста. Ну да ладно. Многочисленные пользователи фреймворка смотрят на меня свысока ;)


Ага, опечатался.

Возможно, если бы была привычка писать should было бы сложнее


Я хз, как это однозначно сформулировать на английском.

Я бы написал что-то типа
"prints a given recipient name in a greeting"


Поэтому читайте лучше код — он однозначный.

Я бы предпочел читать код того, кто дает понятные имена


Сокращение от submissive.

Это общепринятое сокращение? Честно говоря, мне от расшифровки яснее не стало. Почему добавление в submissive это print?

Возможно, если бы была привычка писать should было бы сложнее

С чего бы?


"prints a given recipient name in a greeting"

Хороший вариант, да.


Это общепринятое сокращение?

Да.


Почему добавление в submissive это print?

Потому, что это не print, а конфигурирование компонент. Одно свойство задали, другое проверили.

С чего бы?

С того, что описаться в буквах s, h, o, u, l и d сложнее чем в одной s.


Это общепринятое сокращение?

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


Я поискал "submissive sub programming" и в первой строке было вот что


For in BDSM the submissive (or “sub”) willingly grants the dominant (or “dom”) power over them, and they do so out of trust and respect.

Теперь мне стало понятно, в какой именно среде это общепринятые термины и все стало ясно. Спасибо!

Ну хозяев с рабами то у всех есть, а вот какая целевая аудитория должна понимать сразу что sub это submissive? Ссылку можете дать на кого-то кто не вы и не BDSM (если это разные категории) и использует это сокращение в программировании? Я вот только знаю тег sub.
Это одна категория.

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

Продемонстрируйте мне, пожалуйста, тест

У меня нет ни времени, ни желания вам что-то доказывать. Вот вам простой пример, где ваши скрижали рассыпаются в прах:


Пишем тест — он красный:


assert( pixel_pos( 1.1 ) === 1 )

Пишем код — тест зеленеет.


function pixel_pos( x ) {
    return Math.floor( x )
}

Больше красных тестов написать не удалось — функция полностью удовлетворяет всем требованиям.


Побенчмаркали и выяснили, что Math.trunc работает быстрее, чем Math.floor. Отрефакторили код:


function pixel_pos( x ) {
    return Math.trunc( x )
}

Тесты по прежнему зелёные — довольные коммитим и выкладываем в прод.


На следующий день получаем весь ютуб заваленный прикольными багами в районе нулевой координаты.


Sociable

Это не юнит тест по определению, ибо тестирует разрозненные куски кода, не являющиеся единым целым. Это компонентный тест.


можно почитать у Фаулера.

У программистов вообще всё плохо с теорией тестирования. Про компонентные тесты никто не слышал. Вот и изобретают химер типа "социальных индивидуальных тестов".


Тогда приведите, пожалуйста, код

Это не имеет значения. Максимум, что тут можно сделать — создать для каждой строки по отдельному компоненту и позволить ему спозиционировать слова внутри чисто горизонтально. Это разобьёт все шаги на 2 примерно равные группы (распределение по строкам и позиционирование в строке), что не сильно меняет дело.

Пишем код — тест зеленеет.

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


Это не юнит тест по определению, ибо тестирует разрозненные куски кода, не являющиеся единым целым. Это компонентный тест.

здесь, скорее всего, мы повторим аргументы из вот этого обсуждения


UPD: вернее, само обсуждение начинается отсюда


Это не имеет значения. Максимум, что тут можно сделать — создать для каждой строки по отдельному компоненту и позволить ему спозиционировать слова внутри чисто горизонтально.

Уверенность в себе это хорошо. Но я не всегда доверяю чужой уверенности в себе :)

Простейший код — это возвращение константы в данном случае.

После нескольких бессмысленных итераций с очевидно неверными реализациями, вы таки дойдёте до описанного мною кейса. Подумайте над ним. Или пообщайтесь на эту тему с настоящими тестировщиками и полюбуйтесь их выпученными глазами.

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

Какие аргументы у сторонников TDD за эти итерации? Или вы опять пытаетесь очень уверенно критиковать то, в чем не разобрались?


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

Подумал. А тут не тот же самый компромисс между трудоемкостью покрытия и точностью результата как и в вашей фразе ранее?


"Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов."


Т.е. у вас дополнительный вызов метода уровня 2 покрывается ровно одним кейзом уровня 1. Разница только в том, что в этой новой задаче этот дополнительный вызов находится в отдельном методе, а метод уровня два библиотечный.

А тут не тот же самый компромисс между трудоемкостью покрытия и точностью результата как и в вашей фразе ранее?

Слепое следование ТДД даёт гарантированно меньшее покрытие при большей трудоёмкости.


метод уровня два библиотечный

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

Слепое следование ТДД даёт гарантированно меньшее покрытие при большей трудоёмкости.

Я не понял о чём вы

Вы не поняли и при этом продолжили рассуждения!


Я вот о чем — вы вашем примере снизили количество кейзов, как с 27 до 9 за счет того, что тестируете кейзы вложенных выовов только в одном кейзе вызывающей функции — так? Условно, соответственно когда тестируюете другой кейз, вызывающей функции вы не гарантируете от соверщенно такой же подмены вызываемой функции на другую, совпадающую в одном тестируемом кейзе но не совпадающую в других. Совершенно так же как замена round на trunc.


чтобы не мокать каждый оператор и стандартную функцию,

Таким образом, никаких модульных тестов не существует: как только мы используем что-то не мокая оно обращается в компонентный тест! (вы повторили цепочку рассуждений из другого обсуждения, но с другими выводами :D )

тестируете кейзы вложенных выовов только в одном кейзе вызывающей функции — так?

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


Таким образом, никаких модульных тестов не существует: как только мы используем что-то не мокая оно обращается в компонентный тест!

Именно так, модульные тесты — фикция и профанация. Поэтому в их определении всегда есть оговорка "кроме модулей стандартной библиотеки". А потом ещё одна: "если эти модули не взаимодействуют с внешним миром и выдают детерменированный результат за разумное время".

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

Тогда почему вас насторожило что round не покрыт косвенными тестами настолько что он может быть отличен от trunc?


Именно так, модульные тесты — фикция и профанация.

Теперь надо применить эту же логику последовательно к компонентным тестам и end-to-end тестам: если X используется в соединении с Y для тестирования, то это уже тестирование не X а еще и Y.


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


А у меня другая логика unit тестом называется тест юнита отдельно от остальной системы. При этом можно переиспользовать части которые используются для строительства этой системы.


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

Тогда почему вас насторожило что round не покрыт косвенными тестами настолько что он может быть отличен от trunc?

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


Исходя из этого нет никаких компонентных тестов, а есть только вселенские тесты.

Вы статью-то мою перечитайте, там чёткая классификация.

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

Это все одна и та же тема. У нас есть вызов функции f1 из функции f2. Если тест функции f2 не содержит все кейзы функции f1 допустимые на тех данных которые может ей предоставить f2 значит мы можем заменить ее на функцию f3, которая отличается от f1 в кейзах, которые не тестируются тестами f2.


Это вы же сами продемонстрировали в случае замены round на trunc.


Теперь у нас есть функция f1 у которой есть три кейза, которая вызывает f2 у которой есть три кейза. Обе функции приватные. Если я тестирую 3+3=6 кейзов то я в каких-то кейзах f1 тестирую не все возможности f2 и там они могут быть заменены на функцию f3 точно таким же образом.


Вы статью-то мою перечитайте, там чёткая классификация.

Перечитал


Модуль или юнит — минимальный кусок кода, который можно протестировать независимо от всего остального кода.

Ок модули определяются через независимое тестирование.


Тестирование модулей так же известно как "юнит-тестирование".

Ok. Это независимое тестирование и есть юнит тестирование.


Также из обсуждения мы знаем что модульных тестов не бывает "модульные тесты — фикция и профанация" => модулей тоже не бывает, так как они определены через тестирование.


Компонент — относительно самостоятельная часть приложения. Может включать в себя другие компоненты и модули.

Как компонент может включать в себя то, чего не бывает?

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

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

Это странно или коряво сформулированно. Алгоритмы различны. Частный случай — вызов не тех функций. Никакого противопоставления нет.


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

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


https://habr.com/post/279535/


• Исчерпывающее тестирование (Exhaustive Testing — ET) — это крайний случай. В пределах этой техники вы должны проверить все возможные комбинации входных значений, и в принципе, это должно найти все проблемы. На практике применение этого метода не представляется возможным, из-за огромного количества входных значений.


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

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


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

Не хотите — не отвечайте.

Полное покрытие в практическом смысле — это не тестирование всех вариантов входных параметров, а тестирование всех классов эквивалентности и граничных условий.

Отлично! У нас есть определение.


Теперь давайте рассмотрим подробнее корневое сообщение из этой ветки:


У вас может быть 3 приватных последовательно вызывающих друг друга метода. В каждом есть, допустим, 3 ветки логики. Если тестировать через публичный интерфейс, то нужно проверить 3х3х3 = 27 кейсов. Помножьте на число граничных условий и будет совсем ахтунг.

далее


Каждый из этих приватных методов (а точнее их видимость придётся повысить) тестируется в предположении, что вызываемые им методы уже протестированы. Это даст 3+3+3 = 9 кейсов.

Т.е. для функции верхнего уровня есть 27 классов жквивалентности, мы уменьшили их до 9 за счет того, что вызываемые методы "уже протестированы".


То есть я так понимаю, что функция f1 вызывает функцию f2, которая вызывает f3. Допустим у f1 есть три ветки логики, 1, 2, 3 соответственно. Обозначим комбинации так 1.1 — кейз когда отрабатывает 1 кейз f1 и 1 кейз f2 и так далее.


Если вы можете убрать из тестирования кейзы f2 в каком-то кейзе f1, потому, что они "уже протестированны" то значит тест не свалится в том случае, если мы заменим f2 на какую-то другую функцию f4, у которой кейзы совпадают с убранными и несовпадают с оставшимися.


То есть если мы оставили кейз 1.1 то если мы заменим в вызове функцию f2 на f3, для которой кейз 1.1 проходит, а 1.2 и 1.3 не проходят, то мы получим то же самое.


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


Если вы видите ошибку в моей логике, укажите на нее.

Ошибка тут:


Т.е. для функции верхнего уровня есть 27 классов жквивалентности,

У каждой из этих 3 функций по 3 класса эквивалентности. Если на каждую из них написать тесты (модульные или компонентные — не важно), то это будет 3+3+3 теста. Если же писать тесты на одну из них, косвенно тестируя остальные, то классы эквивалентности перемножаются, потому как аргументы, с которыми вызываются зависимости перестают контролироваться кодом тестов, а контролируются кодом, который собственно и тестируется.

Давайте примем что это так. Отсюда два вопроса:


  1. Какой именно тест предотвратит замену f2 на f4 (напомню, что f4 ведет себя так же как f2 на всех кейзах, кроме тех которые опущены при тестировании f1 на основании того, что f2 уже протестирован)
  2. Если заменить, f1 на pixel_pos а f2 на round, получаем, что у pixel_pos одна ветка => один класс эквивалентности, а round уже протестирован => нам нужен ровно один кейз для pixel_pos

Примените пожалуйста те же рассуждения для обоих примеров. В чем именно вы видите разницу?

Какой именно тест предотвратит замену f2 на f4

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


нам нужен ровно один кейз для pixel_pos

Ноль, если линковка и типизация статические.

Это уже вопрос сверки контрактов.

При чем тут какая-то "сверка контрактов" — любой тест проверяет, что тестируемая система соблюдает свой контракт. В f1 использовали f2 а не f4 чтобы f1 выполнил свой контракт, а не почему-то еще.


Ноль, если линковка и типизация статические.

Имеется ввиду test case

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

Совершенно верно. Вы как программист для какого-то случая решили а для кого-то нет и непонятно, почему.


Итого, вы из 27 приписанных кому-то тестов сделали 9, но теперь их не 9 а больше, но какие непонятно, но таким же способом полученный 1 тест для pixel_pos недостаточен и даже 2 теста по TDD это мало.


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


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


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


Будете ли вы отвечать на вопрос вот тут?

Отдельный вопрос — насколько практично "полное покрытие" в реально существующих проектах. Например, какое покрытие у mol. Я не вижу бейджа хотя бы с code
coverage по запускавшимся строчкам, было бы интересно, если бы вы поделились статистикой.

> Например, на уровне методов, их содержимое инкапсулировано, а сигнатура является публичным интерфейсом.

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

Если что-то хочется протестировать отдельно — как правило это отдельный уровень абстракции который можно отдельно протестировать.
+1, не хватает кармы для голоса, плюсую комментом, да простит меня хабр.
НЛО прилетело и опубликовало эту надпись здесь

С моей точки зрения это значит, что есть уровень абстракции, который явно не выражен.


см также Front Door First


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

Мне кажется, что если покрытие тестами будет достигать в лучшем случае 30%, то это означает, что ваш код, как бы это по мягче сказать… плохой и требует рефакторинга.
Если невозможно через внешние интерфейсы пройти все ветки кода, то что-то в это есть не правильное.

«Как тестировать приватные методы» — через публичные или у вас так они написаны, что они работают только с приватными данными, которые не использую никакие методы кроме приватных и которые невозможно никаким публичными методами узнать? Мне кажется тут есть что-то не то в архитектуре приложения.

«Методы с побочным эффектом» — это что? Побочный эффект в чем выражается? Почему этот побочный эффект нельзя проверить? Запись в БД это побочный эффект? если да, то почему нельзя сделать заглушку для библиотечной функции записи в БД и проверить наличие запроса?

Тестировать надо не публичные методы, а то как ваш объект обрабатывает те или иные входные данные и что от него можно ожидать на выходе, а если выходов нет, то что-то тут не то.
Покрытие тестами это метрика абстрактного коня в вакууме. Надо четко понимать, что означает 30% покрытия тестами и что покрыто этими тестами, а главное за чем.
Из практики, модульными тестами надо покрывать обработку всевозможных данных, когда некоректный расчет ведет, к ошибке, а не к падению, которое тестер или тестовая среда с легостью обнаружат.
Например парсинг, процессинг и т.д. покрывал бы сильно, а модель поведения кнопки отправить, да там и так все предельно просто. Но как всегда все зависит.
Опять таки неплохо трекать какие тесты падаюбт часто, а какие не падают вообще, на каком участке кода дефектов много, а на каком их нет вообще. В зависимости от этого и организовывать покрытие тестами.
Абстракная цифра покрытие тестами в 30% не говорит абсолютно ни о чем.
Тем более, что по строчкам код может быть покрыт и на 100%, а по классам эквивалентности не дотягивать и до 1.

Абстрактная цифра 30% покрытия говорит от том, что не покрыто по крайней мере 70% :)

НЛО прилетело и опубликовало эту надпись здесь

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


Писать для каждого метода программу, журналировать вывод в консоль и проверять визуально? Писать для каждого метода программу, запускать ее в отладчике и вручную просматривать значения всех переменных?

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