Комментарии 15
С тестами самая большая жесть ето проверка полиморфизма данных. И не знаю никаких "хаков" как это обойти.
Когда сервис пишется с нуля, то тогда просто - тесты ростут так же постепеннно. В случае с базой и тд создается виртуальное подключение и постепенно с добавлением методов добавляются и "емуляции" для разных ситуаций.
Но когда есть старый проект, который перекатывают на новые рельсы и его надо покрыть тестами....
Иногда проще получить структуру легаси-проекта и переписать его заново с тестами, по времени выходит иногда быстрее чем просто покрыть тестами существующий
Прежде чем переписывать, нужно описать поведение существуещего, а тут как раз чаще всего и кроется основная проблема - никто не знает как оНо работает и требований нет. Начинаешь разбираться, описывать выявлять кучу скрытых багов и т.п. Вроде все собрал, а потом оказывается, что система закрыта - типа, черный ящик - по правилам. Но, система неотзывчива. Какие состояния?! О чем вы?! Никакой четкой логики, все зависит от ретраградного меркурия, но это не точно. Что делать?
Обвязываешь всю эту кашу и делаешь каркас из состояний через наблюдателей (переодически опрашиваешь, например, какие-то показатели в базе), т.к. внутрь залезть нельзя. Наподобие, как выращиваешь рецепторы. И получаешь обратную связь. Потом уже можно это тестировать и рефакторить.
Вот потому и переписывается со старта.
Отлавливаются у этого "коричненого яшика" входные/выходные состояния, нежно придерживаешь за горло текуших и прошлых пользователей для получения более обьемной картины и с полученного ТЗ ваяешь нормально .
Потом можно тестить по подобию - загнал данные в свое и в "поделие" и сравниваешь что на выходе отдало.
Зачастую у таких поделий 30% это кричиный функционал ради которого его собсвенно и сохраняют и 60% того что редко использвется или больше "сахар". И 10% того о чем знал только разраб..
Это если есть возможно взять и переписать. Например, мне нужно месяц, чтоб переписать проект с нуля, но чем будет заниматься команда? Это, конечно, вопрос к манагеру, почему он джунов набрал туда, где мидлу тяжко, но это факт. Вот и приходится занимать всю команду и идти шаг за шагом, чтоб и манагеру показать прогресс, и все заняты и учатся.
Ем, IT это не пионерлагерь и тимлид не вожатый (ну почти)
Если задача стоит что бы занять джунов то постановка в целом очень странная.
.
Но вообще как раз толпой джунов топово писать новое, обучая в процессе как раз.
Один пишет один кусок, второй пишет второй, а тимлид руководит и проверяет.
Если задача настолько сложная что даже декомпозируя она не для джуна (в чем я очень сильно сомневаюсь), то тогда тем более в одну каску его нет смысла трогать
Ой, не то, что пионерлагерь, так и цырк с конями в одном флаконе! Понабирали подешевле, видимо, чтоб потом можно было заменить ИИ безболененно.
Ему говоришь, что нужно идти по спецификации, что написано, то и делать. Если какой-то случай не покрыт или что-то некрасиво, поднимаешь вопрос, описываем и реализуем. А он все равно педалит как "видит", а видит мало ввиду отсутствия опыта. Чтоб декомпозицию делать, нужно понимать, что неправильно и что нужно в данном случае делать. Для много шагового рефакторинга нужно держать всю картину в голове. А там в голове своя вселенная. У меня бы и собес многие не прошли, т.к. нет даже базовых знаний.
Технологии и методики должны соответствовать уровню команды. Если команда в среднем чем-то не владеет, то внедрение сделает только хуже - у них появится новый источник ошибок.
Когда такая необходимость все же возникает, то обучение нужно явным образом планировать и закладывать в оценки и косты (да, оно ни когда не бывает бесплатным). Либо менять людей.
юнит тесты довольно бесполезная шляпа
а иногда даже вредная: многие думают что могут достичь 100% покрытия всех кейсов, но обычно это не так, количество кейсов практически бесконечно, если только перечень возможных состояний вы не перечислили явно и исчерпывающе.
Поэтому, используйте ФП + явный стейт
И когда вы это сделаете окажется что юнит тесты не нужны
using System;
using CSharpFunctionalExtensions;
public static class PositiveNumberExtensions
{
public static Result<PositiveNumber<T>, string> ToPositiveNumber<T>(this T value) where T : struct, IComparable<T> =>
value.CompareTo(default(T)) <= 0
? Result.Failure<PositiveNumber<T>, string>("Value must be positive")
: Result.Success<PositiveNumber<T>, string>(new PositiveNumber<T>(value));
public static Result<double, string> SquareRoot<T>(this Result<PositiveNumber<T>, string> result) where T : struct, IComparable<T> =>
result.Map(positiveNumber => Math.Sqrt(Convert.ToDouble(positiveNumber.Value)));
}
public record PositiveNumber<T>(T Value) where T : struct;
// example
var result = 16.ToPositiveNumber()
.SquareRoot()
.Match(
success: squareRoot => Console.WriteLine(squareRoot),
failure: error => Console.WriteLine(error)
);
ну и куда тут автотесты сувать?
если автотесты получаются в 5 раз больше чем код, то они бесполезны, т.к. вероятней что ошибка будет в тестах а не в коде
Говорят, что осмотрительные люди делятся на два типа: тех кто делает бекапы, и тех кто делает бекапы и потом проверяет.
Код без требований это ни о чём. Тесты это запись части требований в виде исполняемого кода, с помощью которого реализацию можно быстро проверить.
Например, квадратный корень определён для 0, а вы сделали только для положительных. Баг или фича, как понять?
Если копать в суть, то в отношении кода тайпчекинг это верификация, а тесты - валидация. Они дополняют друг друга, а не подменяют. Поэтому ФП хорошо, а с тестами - лучше.
Точно!
И верификация и валидация нужны.
Нужно не впустить внутрь домена невалидные данные. Нужно, чтобы операции внутри бизнес логики не падали из-за невалидных данных.
И одними тестами тут не отделаться.
Я тоже критически отношусь к тестам, особенно к из бездумному использованию. В большинстве случаев мы попадаем в абсурдную ситуацию, когда тестовая кодовая база на порядок больше самого бизнес кода, а сложность создания прекондиций и моков превосходит логику тестируемого кода.
В случае граничных условий типа корня из отрицательного числа, поможет контрактное программирование. Добавляете аннотацию к методу она же является спецификацией параметра. Тест в этом случае становится рудиментарным.
Контрактное программирование помогает избежать части логических ошибок. Но это только один из классов. Например, у нас недавно была ситуация, когда приложение, будучи запущенным на процессорах Intel и на AMD, в математике с плавающей точкой выдавало разные результаты. Проблема оказалась в сторонней библиотеке, где авторы перемудрили с ассемблерными оптимизациями. В общем полезно и то и другое - нужно лишь найти правильный баланс.
Ну, эта проблема выходит далеко за рамки юнит тестирования и не решается оными. Тем более вашей непосредственной ответственности по части тестирования здесь нет -- это создатели библиотеки должны были оттестировать поведение. Вы же заложились на корректность реализации и соответствие заявленной спецификации, что вобщем-то нормально. Тестировать подключенный сторонний код в своем проекте -- это ненормально. Так можно опуститься и до тестов своей операционнрй системы.
вы точно будете использовать такие тесты которые в 5 раз длиннее кода? оно вам надо?
using System;
using CSharpFunctionalExtensions;
using NUnit.Framework;
public static class PositiveNumberExtensions
{
public static Result<PositiveNumber<T>, string> ToPositiveNumber<T>(this T value) where T : struct, IComparable<T> =>
value.CompareTo(default) <= 0
? Result.Failure<PositiveNumber<T>, string>("Value must be positive")
: Result.Success<PositiveNumber<T>, string>(new PositiveNumber<T>(value));
public static Result<double, string> SquareRoot<T>(this Result<PositiveNumber<T>, string> result) where T : struct, IComparable<T> =>
(typeof(T) == typeof(double) || typeof(T) == typeof(float) || typeof(T) == typeof(decimal) || typeof(T) == typeof(int))
? result.Map(positiveNumber => Math.Sqrt(Convert.ToDouble(positiveNumber.Value)))
: Result.Failure<double, string>("Square root operation is not supported for the specified type");
}
public record PositiveNumber<T>(T Value) where T : struct, IComparable<T>;
public static class Program
{
public static void Main()
{
// example
var result = (-16).ToPositiveNumber().SquareRoot();
Console.WriteLine(result);
}
}
[TestFixture]
public class PositiveNumberExtensionsTests
{
[Test]
public void SquareRoot_ShouldReturnSuccess_WhenValidPositiveNumber()
{
// Arrange
var number = 16.ToPositiveNumber();
// Act
var result = number.SquareRoot();
// Assert
Assert.IsTrue(result.IsSuccess);
}
[Test]
public void SquareRoot_ShouldReturnFailure_WhenUnsupportedType()
{
// Arrange
var number = 'A'.ToPositiveNumber(); // Char is not supported
// Act
var result = number.SquareRoot();
// Assert
Assert.IsTrue(result.IsFailure);
}
[Test]
public void SquareRoot_ShouldReturnFailure_WhenNegativeNumber()
{
// Arrange
var number = (-16).ToPositiveNumber();
// Act
var result = number.SquareRoot();
// Assert
Assert.IsTrue(result.IsFailure);
}
}
Тестирование без моков: язык паттернов. Часть 1