Pull to refresh
-13
0

Пользователь

Send message
Юнит-тестов все-таки получится меньше.


Не соглашусь. Положим мы тестируем белый ящик, тогда множество входов I разбивается на классы эквивалентности, на которых программа ведет себя «одинаково» с точки зрения потребителя. Для гипотетической страницы логина мы все корректные пары логин/пароль записываем к класс 1, валидный логин и неверный пароль в класс 2, валидный логин/пароль при нерабочей базе данных в класс 3 и т.д. до класса N. Так как мы знаем алгоритм (белый ящик), то мы может получить и эти N классов (на самом деле, на практике, мы этого не можем, но представим, что все таки можем). Для покрытия системы нам теперь нужно написать по одному тесту для каждого класса эквивалентности.

Обратите внимание, текст выше не говорит о юнитах или других видах тестов. Вам просто надо написать N тестов на контракт вашей функции и все тут. Если добавить еще и юниты, которые тестируют внутренности, вроде onInterval, то у вас будет N+M тестов вот и все.

Дискуссия unit test vs все остальное во многом искусственная и смысла большого не имеет. Цель же получить рабочий софт, который легче поддерживать. Идеальное решение вообще достигает этой цели без тестов. Все это ведет к более практическим вопросам
— Как выделить классы эквивалентности? Вроде бы ответ это опыт и знание мат. части, например знание того, что БД бывает отваливается, файлы не открываются, юзеры вводят 1Гб текста и т.п. Возможно хитрые системы типов. Хороших решений на горизонте не видно.
— Какие куски системы надо изолировать и мокать, а какие оставить как есть. Тут вообще ничего не ясно, вкусовщина и эвристика. Наука, к сожалению, молчит.

С более сложным примером, где было бы больше веток и больше состояний, нас ждал бы Комбинаторный взрыв.


Кажется, что комбинаторный взрыв никуда не исчезает от того, что протестировали маленькие кусочки большой программы. Нужно будет еще протестировать как «большая» программа реагирует на все возможные выходы из каждого «кусочка». Если же мы мокаем «маленькие кусочки» в тесте «большой» программы, то нужно как-то гарантировать, что наши моки ведут себя равно также как настоящий «маленький кусочек». Все это крайне сложно и на практике не реализуемо. IMHO мокать нужно то, что невозможно или очень сложно контролировать в тесте.

И их количество было бы неустойчиво к рефакторингу.


В одном случае вы тестируете контракт 2х функции (createCounter и cancel), в другом тестируете контракты 3х (createCounter, cancel и onInterval). Рефакторинг с большей вероятностью затронет контракты внутренних функций и приведет к необходимости менять тесты. Если же принять за догму классическое определение рефакторинга, как улучшение кода без изменения контракта (видимо контракта createCounter и cancel, но не контракта onInterval), то тесты только для createCounter/cancel будут максимально устойчивы к такому рефакторингу. То есть я утверждаю обратное — тесты только createCounter/cancel более устойчивы к рефакторигу, чем тесты createCounter/cancel/onInterval.

Добавил одну вилку в любую функцию — нужно удвоить общее количество тестов, что не есть хорошо.


Почему удвоить, а не возвести в квадрат или взять факториал? Мне кажется, что варианты A и B ниже имеют одинаковое количество состояний и оттого, что часть функционала вынесли в функцию Step2 ничего не изменилось.

void A(Input) {
//STEP 1
//STEP 2
}

void B(Input) {
//STEP 1
Step2(Input);
}

void Step2(Input) {
//STEP 2
}
Почему бы не замокать serInterval и clearInterval и не написать тесты на изначальный вариант? Такие тесты прямо бы документировали контракт createCounter и отпала бы необходимость иметь кучу файлов по одной функции и все эти сложности с DI.

Жить стало лучше, жить стало веселее, давайте радоваться! Не смешно, блин :( ни 10 лет назад, ни сейчас.

Поинтересуйтесь статистикой оправдательных приговоров в современной России. Если дело дойдёт до суда, то по статистике его шансы выйти 1/300.

Но это не главная проблема. Главная в том, что на самом деле «регулятор» — тот, у кого сила.


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

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


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

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

Блокчейн гарантирует, что «ценность», в данном случае это голос присяжного, переместиться куда надо и не будет подделана. Далее он же гарантирует, что другая «ценность» в виде квартиры, будет передана как положено, а государство должно гарантировать выполнение решения записанного в блокчейне. Самое главное — минимизируется (но не исключается полностью) человеческий фактор.

Централизованные системы нужны обществу для решения проблемы византийских генералов и только этой проблемы (буду рад увидеть контр примеры). Мы наделяем правом решать, что есть правда, а что ложь специально выбранных людей и строим процессы вокруг них для гарантии их честности. К сожалению любая централизованная система из людей гарантированно коррумпирована. Блокчейн дает другое решение проблемы визайнтийских генералов, этого решения раньше просто не существовало, а теперь оно есть. Значит мы можем пересмотреть подходы к решению старых проблем.
>Поймали трояна и остались без квартиры, которую зарабатывали всю жизнь.

Обратились в суд, собрали доказательства, суд конфисковал квартиру в вашу пользу. И заметьте, это намного лучше, чем текущая ситуация. Как вы сегодня предлагает бороться с гипотетическим полковником ФСБ, который переписал в Росреестре вашу квартиру на себя? Думаете будет вам суд присяжных и разбирательство?
Например так
— Вместе с паспортом гражданин получает адрес в блокчейне
— К этому адресу привязывается информация об уплаченных налогах, недвижимости, автомобилях и т.п. Вся эта информация уже есть и публично доступна, но для ее получения нужно обращаться во всякие реестры.
— Смарт контракт «выборы» раздает на эти адреса токены для голосования.
— Граждане голосуют, переводя токены кандидатам.

Если государство нагенерит мертвых душ, то это будет видно, так как внезапно в стране появится 10 млн граждан, у которых ничего нет и которые налоги никогда не платили. По такому поводу у граждан появится повод сменит власть без всяких выборов, путем «прямого голосования» на улицах. Если граждане не хотят отстаивать свои права, то никакой блокчейн им не поможет.
И на C можно все нормально написать (см. ниже) и композиться будет настолько хорошо, насколько это возможно в C.

Программисту нужно «сохранить состояние вычисления между вызовами функции», итераторы, async/await, infinite streams, всякие prolog-style недетерминированные вычисления это уже приложения этой фичи. В Haskell для этого есть монады и ленивые вычисления, в Smalltalk и Scheme есть continuations, в C# есть yield return и async/await.

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

array.ForEach(x => if (x == 10) return);
foreach(var x in array) if (x == 10) return;


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

Собственно ручной генератор продолжений (continuations) на C в действии.

#include <stdint.h>
#include <time.h>
#include <stdio.h>

typedef struct triple {
	int x;
	int y;
	int z;
} triple;

triple next_pytriple(triple* iter) {	
	for (int z = iter->z; ; ++z) {
		for (int x = iter->x; x <= z; ++x) {
			for (int y = iter->y; y <= z; ++y) {
				if (x*x + y*y == z*z) {
					iter->x = x;
					iter->y = y + 1;
					iter->z = z;
					return (triple){x, y, z};
				}
			}
			iter->y = 1;
		}
		iter->x = 1;
	}
}

void pytriples(size_t n, triple triples[]) {	
	triple iter = {1, 1, 1};
	for (int i = 0; i < n; ++i) {
		triples[i] = next_pytriple(&iter);
	}    
}

int main() {
	clock_t t0 = clock();
	triple triples[100];
	pytriples(100, triples);
	for (int c = 0; c < 100; ++c) {
		printf("(%i,%i,%i)\n", triples[c].x, triples[c].y, triples[c].z);
	}
	clock_t t1 = clock();
	printf("%ims\n", (int)(t1-t0)*1000/CLOCKS_PER_SEC);
	return 0;
}
discovan Как вам такой кейс для реестров земли или иной собственности
— Права собственности лежат в блокчейне.
— Есть identity management, то же в блокчейне, у каждого гражданина есть свой хеш.
— Если землю нужно конфисковать, то суд присяжных должен вынести такое решение, а каждый присяжный должен его подписать своим ключем. Тогда smart контракт переведет землю в собственность государства.
— В остальных случаях, для изменения собственника нужно послать согласованную с собственником сумму в смарт контракт

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

Edit
Выбор присяжных — тоже смарт контракт, который рандомом предлагает кандидатуры.
Не могу согласиться, даже в чистом виде сложный layout быстро теряет предсказуемость и это свойство всех layout систем. Короче я ниасилил, сдался и сделал свой велосипед :) Последней каплей для меня стало прочтение мануала о том, как делать layout внутри scroll view.
Стоит отметить, что далеко не все получится описать линейными уравнениями и, равносильно, ограничениями. Самый удобный layout который я видел это тупейший anchor layout в древнем Delphi образца 1998 года. Далеко с ним не уедешь, зато знаешь чего ждать и когда пора писать код руками. С сложными системами верста постоянно себя чувствую программистом на XML, когда ясную задачу прописываешь на максимально неподходящем для неё языке. В конце концов запили построитель прямоугольников и пишу все в коде, без линейной алгебры и сюрпризов.
Почему никто не отмечает важные отличия игры AlphaStar от игры человека?
— Он лучше строит, почти всегда у AlphaStar больше ресурсов и юнитов.
— Он лучше микрит, stalker с blink у ИИ практически бессмертный. Такая эффективность управления недоступна человеку и ломает баланс. Immortal должен побеждать stalker, но нет, под управлением ИИ stalker побеждает.
— Он может разбивать армию на много кусочков, окружать и «дергать» армию противника со всех сторон. Человек такого не может, просто не внимания.
— Он не учится в процессе игры, прилетел дроп, и AI продолжал гоняться за ним пока не проиграл.

AlphaStar в основном побеждал за счет нечеловечески точного и быстрого управления юнитами. Он не показывал чудеса стратегии или какие-то крутые игровые идеи. Просто он умеет быстро елозить мышкой и делает это в 1000 раз быстрее и точнее человека. Надежда людей в этом противостоянии только на то, что всю дурь из AlphaStar не получится выбить и всегда будут новые стратегии, к которым он не готов, как тот дроп из 2х immortal.

И надо быть справедливым к людям, если бы они играли с AlphaStar несколько дней подряд, то счет был бы 0-10 в пользу людей. Уже в 6 игре Mana нашел фатальный недостаток у ИИ.
Конечно 5 минут это утрированный пример :) Я веду к тому, что предложенным методом можно оценивать только задачи, которые знакомы оценивающим. В то же время, оценки наиболее ценны для областей, которые не знакомы. Для знакомых областей команда часто без всяких формализмов знает сколько времени надо не выполнение, просто из опыта. Мне кажется это и есть самая большая проблема всех методов оценки, они лучше всего работают в знакомых областях, но наиболее востребованы в не знакомых.
Конкретных советов давать не буду, я не знаю вашей системы. А если в общем, то раз ваша система про БД, то и тестировать надо как она работает с БД. То есть проверять, в первую очередь, что система свою работу делает. А дальше уже проверять, если надо, какие-то нюансы.

Мне кажется деление на «юнит» и «не юнит» тесты несколько искусственное. У теста есть характеристики, вроде скорости работы, воспроизводимости, сложности написания, покрытия и т.п. Обычно хочется максимальное покрытие, минимальным количеством, воспроизводимыми и быстрыми тестами и обычно так не выходит. Искусство заключается в выборе удобного компромисса и поддержании его на протяжении жизни системы.
Мне кажется такая стратегия не сработает. Напишем тест для простых классов, а чтобы двигаться дальше придется разломать всю систему, не имея возможности нормально проверить ее работоспособность.

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

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

Начните с интеграционных или e2e тестов для начала. Запустили всю систему, дернули апи и проверили результат, фактически автоматизируете работу тестеров. Таких тестов много не надо, но они ловят очень много багов. Когда есть хоть какая-то уверенность в том, что «тесты прошли, значит система работает», можно уже рефактоить. Если получится сделать хорошую обвязку для интеграционных тестов: in-memory db, фейковые очереди, фейковые платежные системы и email сервисы, то писать юниты может и не нужно совсем, ваши интеграционные тесты будут проверять 90% системы с 10% усилий на написание и поддержку тестов.


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

Druu Мне тоже интересно, как задать топологию на вычислимых функциях.
А мне интересно поспорить. Как вам такой пример — рассмотрим конечные группы ({0, 1, 2, 3}, +) и ({-2, -1, 1, 2}, *), операции + и * определены так
*	 1	-1	 2	-2
 1	 1	-1	 2	-2
-1	-1	 1	-2	 2
 2	 2	-2	 1	-1
-2	-2	 2	-1	 1

и
+	0	1	2	3
0	0	1	2	3
1	1	2	3	0
2	2	3	0	1
3	3	0	1	2


Предлагаю вам найти тут изоморфизм групп. Hint — биекций тут много, но, изоморфизма нет потому что -1*-1 = 1, ничего такого с + сделать не выйдет. В итоге отобразить одно в другое вы можете (биекция), но сохранить свойства группы (изоморфизм) не сможете.
Забудем об эквивалентности/биекции/изоморфизме. Я не понимаю как вывод статьи связан с тезисом о том, что функции определенных сигнатур эквивалентны/изоморфны?

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Date of birth
Registered
Activity