.NET Разработчик
6.53K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1727. #Testing
Тестирование на Основе Свойств. Теория
В этой серии рассмотрим, что такое тестирование на основе свойств, почему оно полезно и как оно может помочь писать лучший код.

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

Тестирование на основе свойств было введено в 2000 году Коэном Классеном и Джоном Хьюзом через библиотеку Haskell QuickCheck. Марк Симанн использует следующее определение в своем курсе Pluralsight по тестированию на основе свойств:
Тестирование на основе свойств — это метод автоматического тестирования, при котором вы постепенно концентрируетесь на правильном поведении системы, описывая её свойства или качества в общих чертах, а затем используете случайно сгенерированные тестовые данные для выполнения детерминированных тестов.

Оно довольно запутанное. Давайте разбираться.

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

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

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

Например:
int Add(int x, int y) => x+y;

Чтобы протестировать метод Add, мы можем попытаться придумать репрезентативные примеры, но существует бесконечный список возможных комбинаций. Вместо этого попробуем описать метод Add как связь между входными и выходными данными:
1) Не имеет значения, в каком порядке мы вводим входные данные x и y, выходные данные одинаковы. 1+7 = 7+1
2) Ноль вместо x или y равносилен пустой операции (вывод не меняется). 7+0 = 0+7 = 7.
3) Если вызывать Add несколько раз, порядок вызовов не имеет значения. 7+(4+5) = (7+4)+5

Это прекрасно согласуется с математическим описанием операции сложения.

Пока это всё теория. Завтра рассмотрим тесты на основе свойств в C#.

Продолжение следует…

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-1.html
👍14
День 1728. #Testing
Тестирование на Основе Свойств. Простой пример
Теория

Итак, напишем тесты согласно свойствам, которые мы определили.
int Add(int x, int y) => x+y;

У нас должно получиться 3 теста:
public class AddTwoNumbersTests
{
int Add(int x, int y) => x + y;

[Theory]
public void NoOrderOfParameters(int x, int y)
{
var res1 = Add(x, y);
var res2 = Add(y, x);

Assert.Equal(res1, res2);
}

[Theory]
public void ZeroDoesNothing(int x)
{
var res1 = Add(x, 0);
var res2 = x;

Assert.Equal(res1, res2);
}

[Theory]
public void OrderDoesntMatter(int x, int y, int z)
{
var res1 = Add(Add(x, y), z);
var res2 = Add(x, Add(y, z));

Assert.Equal(res1, res2);
}
}

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

FsCheck — это платформа тестирования на основе свойств, созданная для F#, но её можно использовать и в C#. Программист предоставляет спецификацию программы в виде свойств, которым должны удовлетворять функции, методы или объекты, а FsCheck проверяет, сохраняются ли эти свойства на большом количестве случайно сгенерированных случаев. Вы фактически пишете тестируемую спецификацию вашей программы. Спецификации выражаются на F#, C# или VB с использованием комбинаторов, определённых в библиотеке FsCheck. FsCheck предоставляет комбинаторы для определения свойств, наблюдения за распределением тестовых данных и определения генераторов тестовых данных. При сбое свойства FsCheck автоматически отображает минимальный контрпример.

Есть плагины FsCheck для всех популярных тестовых сред. Всё, что нам нужно, - это установить NuGet пакет и заменить атрибуты [Theory] на [Property].
Тесты по-прежнему будут проходить. По умолчанию FsCheck останавливается после 100 попыток. Если вы хотите знать протестированные значения, задайте свойству Verbose атрибута Property значение true:
[Property(Verbose = true)]

Вот пример вывода:
NoOrderOfParameters
Standard Output:
0:
(0, 0)
1:
(-1, 1)
2:
(1, 1)


Конечно, это очень простой пример. Далее рассмотрим что-то более реалистичное.

Продолжение следует…

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-2.html
👍13
День 1729. #Testing
Тестирование на Основе Свойств. Реальный пример
Теория
Простой пример

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

Если у нас есть метод CardNumber.IsValid(), проверяющий валидность номера кредитной карты, мы можем добавить тесты, вроде следующих:
[TestCase("12345678910")]
[TestCase("12345621748")]
[TestCase("12345621777")]
[TestCase("Test")]
[TestCase("00000000000")]
[TestCase("99999999999")]
[TestCase("!@#!@%^@^@$^&@$^sdfasdf")]
[TestCase("$^@#^@##$44")]
[TestCase("15435#$%4354dfsg")]
[TestCase("90022742192")]
public void ValidationShouldFail(string num)
{
var result = CardNumber.IsValid(num);
result.Should().BeFalse();
}

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

Используем FsCheck:
[Property]
public bool ValidationShouldFail(string num)
{
return !CardNumber.IsValid(num);
}

Также можно использовать альтернативную форму записи теста:
[Property]
public void ValidationShouldFail()
{
Prop.ForAll<string>(
x => !CardNumber.IsValid(x)
)
.VerboseCheck();
}

Тест проходит, но если посмотреть расшифровку ([Property(Verbose = true)]), мы можем заметить, что проверяются случайные строки, и вряд ли хотя бы одна из них будет валидным номером карты. Мы можем заменить строку на long, но это тоже вряд ли сильно ограничит варианты.

Произвольные значения (Arbitraties)
FsCheck использует комбинацию генераторов (generator) и сокращателей (shrinker) для создания тестовых данных. Генераторы производят случайный набор значений из интервала с равномерным распределением. Сокращатели ограничивают (фильтруют) этот набор по заданному условию. В FsCheck определены произвольные значения по умолчанию для некоторых часто используемых типов:
- NegativeInt
- NonNegativeInt
- NonEmptyString
- IntWithMinMax
- NonEmptyArray
и т.п.

Используем альтернативную форму записи тестового метода с использованием предопределённых произвольных значений:
[Property(Verbose = true)]
public Property ValidationShouldFail()
{
var arb = Arb
.Default
.Int64()
.Filter(x => x > 100);

return Prop.ForAll<Int64>(
arb,
x => !CardNumber.IsValid(x.ToString()));
}

Здесь мы использовали произвольные значения для long и функцию-сокращатель (x > 100). Заметьте, что FsCheck сначала генерирует случайные значения, а затем проверяет их на соответствие условию сокращателя. В примере выше при попытке задать большое значение в функции Filter, тесты могут выполняться очень долго.

Продолжение следует…

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-3.html
👍4
День 1730. #Testing
Тестирование на Основе Свойств. Собственный генератор
Теория
Простой пример
Реальный пример

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

В предыдущем примере мы использовали Filter(), чтобы возвращать только нужные числа.

Для создания генератора нужен публичный статический класс с публичным статическим методом, возвращающим Arbitrary<T>.
public static class CCNumberGenerator
{
public static Arbitrary<Int64> Generate()
{
return Arb.Default
.Int64()
.Filter(x => x > 100);
}
}

Теперь его можно использовать в качестве типа генератора в атрибуте Property:
[Property(Arbitrary = new[] { 
typeof(CCNumberGenerator) },
Verbose = true)]
public bool ValidateShouldFail(long num)
{
return !CardNumber.IsValid(num.ToString());
}

В примере выше мы использовали существующий генератор. Но можно и создать его с нуля. Одной из наиболее часто используемых функций является Gen.Choose(), которая осуществляет случайный выбор значения из интервала с равномерным распределением.

Например, так можно создать генератор месяца в определённом году:
public static class MonthOfYearGenerator
{
public static Gen<MonthOfYear> Generator =
from month in Gen.Choose(1, 12)
from year in Gen.Choose(1982, 2023)
select new MonthOfYear()
{
Month = month,
Year = year
};

public static Arbitrary<MonthOfYear> Generate() =>
Arb.From(Generator);
}

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

В этом же классе можно создать свой сокращатель:
public static IEnumerable<MonthOfYear> 
Shrinker(MonthOfYear moy)
{
yield return new MonthOfYear() {
Month = moy.Month,
Year = moy.Year - 1
};
yield return new MonthOfYear() {
Month = moy.Month,
Year = moy.Year + 1
};
}

А затем использовать перегрузку метода Generate, принимающую и генератор, и сокращатель:
public static Arbitrary<MonthOfYear> Generate() =>
Arb.From(Generator, Shrinker);

Окончание следует…

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-4.html
👍5
День 1731. #Testing
Тестирование на Основе Свойств. Воспроизведение
Теория
Простой пример
Реальный пример
Собственный генератор

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

Если посмотреть на выходные данные неудавшегося теста, можно заметить строку вроде
Falsifiable, after 3 tests (4241 shrinks) (StdGen(318823861,297138967)).

StdGen – это начальные значения, которые FsCheck использовал для генерации входных данных. Если вы хотите использовать в своём тесте точно такие же входные данные, используйте это значение в свойстве Replay атрибута Property:
[Property(Replay="318823861,297138967")]
public bool ValidationShouldFail(long num)
{

}

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

Итого
Тесты на основе свойств полезны, но не являются панацеей. Их может быть сложнее читать другим разработчикам, которые не привыкли проводить тестирование на основе свойств. Когда вы решаете реализовать свои собственные произвольные алгоритмы и ограничиваете возможные произвольные значения, вы рискуете упустить важные случаи из-за излишней строгости, как и при написании обычных тестов. Тесты на основе свойств также выполняются медленнее: по умолчанию они генерируют 100 случайных входных данных и проверяют их. Поскольку вы имеете дело со случайностью, это также означает, что два прогона теста не идентичны. У вас может быть удачный, а затем неудачный прогон. Если второй запуск выявит реальную проблему, то всё в порядке. Следует написать специальный модульный тест для этого случая, а затем исправить проблему. Однако если это связано с тем, что один из ваших генераторов случайно генерирует значения, которые сильно отличаются от ожидаемых входных данных, выявить и устранить проблему может быть сложнее. Это связано с тем, что сначала вам нужно идентифицировать генератор, дающий неожиданные значения, и обернуть его механизмом фильтрации. И, хотя это возможно сделать, это всё же больше работы, чем простое изменение постоянного значения, определённого в модульном тесте.

Тестирование на основе свойств – очень мощный инструмент, о котором часто забывают. Оно действительно может выявить скрытые и неочевидные ошибки в самых сложных участках кода.

Источники:
-
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-5.html
-
https://rasmus-feldthaus.medium.com/supercharge-your-testing-with-property-based-tests-bc3a7b75ca9f
👍4
День 1741. #ЗаметкиНаПолях #Testing
Пишем Тесты с Autofixture
AutoFixture - мощный инструмент для юнит-тестирования повседневных задач. Он помогает писать меньше кода в блоке Arrange. Рассмотрим его использование на примере совместно с библиотекой xUnit.

Сначала надо создать AutoFixture в тестовом классе:

private readonly IFixture _fixture;
public SampleTests()
{
_fixture = new Fixture();
}

AutoFixture использует обобщённый метод Create, генерирующий случайные данные для заданного типа, например:

var name = _fixture.Create<string>();
var age = _fixture.Create<int>();

Однако, с помощью дополнительного пакета AutoFixture.Xunit2, вы можете автоматически создавать целые классы, вообще без блока Arrange с помощью атрибута AutoData:

[Theory, AutoData]
public void TestEmployee(
string name, int age,
string pos, decimal rate)
{
// Act
var emp = new Employee(name, age, pos, rate);
// Assert
emp.Name.Should().Be(name);
emp.Age.Should().Be(age);
emp.Position.Should().Be(pos);
emp.Rate.Should().Be(rate);
}

Либо можно использовать метод Create:

[Fact]
public void TestEmployee()
{
// Arrange
var emp = _fixture.Create<Employee>();
var comp = _fixture.Create<Company>();

}

Здесь все свойства классов будут заданы случайными значениями.

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

var emps = _fixture
.CreateMany<Employee>(5).ToList();
var comp = _fixture.Build<Company>()
.OmitAutoProperties()
.With(x => x.Employees, emps)
.Without(x => x.City)
.Create();

Здесь вызов OmitAutoProperties не будет задавать случайные значения свойствам, у которых есть значения по умолчанию. А Without не будет задавать значение выбранного свойства (в примере выше City останется null).

Вы можете создавать реализации интерфейсов с помощью метода Register:

_fixture.Register<IMyInterface>(() =>
new FakeMyInterface());

Здесь каждый раз, когда Autofixture нужно будет создать класс, зависящий от IMyInterface, будет создаваться экземпляр FakeMyInterface.

AutoFixture также можно использовать с пакетами для создания моков, вроде Moq или NSubstitute. Для этого надо настроить создание Autofixture (пример для Moq):

var _fixture = new Fixture()
.Customize(new AutoMoqCustomization
{ ConfigureMembers = true });

Тогда Autofixture можно будет использовать для создания реализаций интерфейсов:

var result = _fixture.Create<IMyInterface>();

При этом установка ConfigureMembers в true приводит к тому, что для классов-реализаций интерфейсов Autofixture сгенерирует случайные значения для свойств.

Здесь можно посмотреть множество других примеров использования AutoFixture.

Источник: https://dev.to/serhii_korol_ab7776c50dba/you-write-unit-tests-wrong-5d9f
👍24
День 1756. #Testing
Тестируем Валидацию Модели

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

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

Определим простую модель:
public class User
{
[Required]
[MinLength(3)]
public string FirstName { get; set; }

[Required]
[MinLength(3)]
public string LastName { get; set; }

[Range(18, 100)]
public int Age { get; set; }
}

У нас есть два варианта: мы можем написать интеграционные тесты для отправки запросов в систему, на которой работает сервер, и проверить полученный ответ. Или мы можем использовать внутренний класс Validator, который используется ASP.NET для проверки моделей ввода, и создать быстрые юнит-тесты. Вот вспомогательный метод, который мы можем использовать в тестах:
public static IList<ValidationResult> 
ValidateModel(object model)
{
var results = new List<ValidationResult>();
var context =
new ValidationContext(model, null, null);

Validator.TryValidateObject(
model, context, results, true);

if (model is IValidatableObject vm)
results.AddRange(vm.Validate(context));

return results;
}

Мы создаём контекст проверки без какой-либо внешней зависимости, ориентированный только на модель ввода. Затем мы проверяем все свойства, вызывая TryValidateObject, и сохраняем результаты проверки в списке results. Наконец, если модель реализует интерфейс IValidatableObject, который предоставляет метод Validate, мы вызываем его и добавляем возвращённые ошибки в тот же список results. Таким образом мы можем обрабатывать как атрибуты полей, такие как [Required], так и пользовательскую проверку в методе Validate() класса модели.

Используем этот метод в тестах:
[Test]
public void Pass_WhenModelValid()
{
var model = new User {
FirstName = "Jon",
LastName = "Smith",
Age = 42
};

var result = ValidateModel(model);

Assert.That(result, Is.Empty);
}

[Test]
public void Fail_WhenAgeLessThan18()
{
var model = new User {
FirstName = "Jane",
LastName = "Smith",
Age = 17
};

var result = ValidateModel(model);

Assert.That(result, Is.Not.Empty);
}

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

Источник: https://www.code4it.dev/csharptips/unit-test-model-validation/
👍12
День 1813. #Testing
Практикуем TDD

Люди не пришли к единому мнению относительно определения процесса разработки через тестирование (TDD). Сегодня рассмотрим процесс Cannon TDD, предложенный Кентом Бэком. Если вы делаете что-то по-другому и это вам подходит, отлично!

TDD — это рабочий процесс написания ПО. Программисту необходимо изменить поведение системы (или создать её). TDD призван помочь создать новое состояние системы, в котором:
- Всё, что раньше работало, продолжает работать.
- Новое поведение работает так, как ожидалось.
- Система готова к следующим изменениям.
- Программист и его коллеги уверены в вышеперечисленных пунктах.

Разделение интерфейса/реализации
Первое недопонимание заключается в том, что люди смешивают всю разработку в одну кучу. Есть два сценария:
- Как вызывается конкретная часть поведения.
- Как система реализует такое поведение.

Шаги
Люди — паршивые компьютеры. Следующий процесс похож на алгоритм, но это не так. Он написан так в попытке эффективно общаться с людьми, привыкшими работать с программами. «Попытке», потому что люди склонны говорить: «TDD - отстой! Я сделал <совершенно другое>, и это провалилось».

1. Список тестов
Составьте список всех ожидаемых вариантов нового поведения:
- базовый случай,
- что, если время ожидания истечет,
- что, если ключа нет в БД,
и т.п.
Это поведенческий анализ. Вы думаете обо всех случаях, в которых изменение поведения должно сработать. Если вы думаете, как изменение поведения не должно нарушить существующее поведение, добавьте и это.

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

2. Напишите тест
Один. По-настоящему автоматизированный тест с настройкой, вызовом и утверждениями (совет: попробуйте начинать с утверждений). Именно сейчас вы начнёте принимать проектные решения, в первую очередь решения по интерфейсу. Некоторые решения по реализации могут просочиться, но со временем вы научитесь избегать этого.

Ошибки:
1) Писать тесты без утверждений только для того, чтобы было покрытие.
2) Писать сразу все тесты для списка, а затем заставлять их проходить по одному. Что будет, если проход 1го теста заставит вас пересмотреть решение, которое повлияет на остальные тесты? Переписывание всего, депрессия и/или скука. Выбор следующего теста — важный навык, который приходит только с опытом. Порядок тестов может существенно повлиять как на опыт программирования, так и на конечный результат.

3. Заставьте тест пройти
Измените систему так, чтобы тест прошёл успешно.
Ошибки:
1) Удалять утверждения, чтобы тест казался пройденным.
2) Копировать фактически вычисленные значения в ожидаемые значения теста. Это исключает двойную проверку, которая создает большую часть ценности TDD.
3) Смешивать рефакторинг с прохождением теста. Заставьте тест работать, а затем исправьте его. Ваш мозг (в конце концов) скажет вам спасибо.

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

4. Рефакторинг
При необходимости.
Ошибки:
1) Переписывать больше, чем нужно.
2) Вводить абстракции слишком рано. Дублирование кода — это подсказка, а не приказ к действию.

5. Если список тестов не пуст, перейти к п. 2.
Продолжайте тестировать и писать код, пока ваш страх перед поведением кода не превратится в скуку.

Источник: https://tidyfirst.substack.com/p/canon-tdd
👍12👎5
Шпаргалка к предыдущему посту.
👍13
День 1847. #Testing
Компромиссы при Написании Тестов

Многие компании стремятся иметь 100% покрытия кода. Даже делают частью процесса CI/CD проверки, чтобы гарантировать, что покрытие тестами всегда увеличивается. Это имеет несколько последствий.

Запланированный эффект - разработчики пишут больше тестов. Незапланированный - иногда они пишут плохие тесты, либо просто костыли, чтобы «обмануть» проверку. Например, если вы провели рефакторинг хорошо протестированного кода, уменьшив его объём, покрытие кода уменьшится, но кодовая база улучшится.

Стремиться к 100% покрытию кода — плохая идея, но где провести черту?

Зачем мы пишем тесты?
В конечном счёте тесты обслуживают код, который мы пишем, и предназначены для решения проблемы. Если добавление теста не помогло решить проблему, это лишь трата времени и денег. Тесты снижают риск, позволяют проверить вашу работу и убедиться, что она скорее всего верна. Каждый тест даёт вам немного больше уверенности в тестируемом коде. Ценность тестового кода не прямая. Она в предотвращении потерь, как с точки зрения реального ущерба от ошибок (потеря дохода, нарушение конфиденциальности, неверные результаты), так и с точки зрения времени на обнаружение и исправление этих ошибок. Нам не нравится платить эту цену, поэтому вместо этого мы платим за тесты. Это страховка.

Какие риски вы хотите покрыть?
Как страховые полисы имеют разное покрытие, лимиты и доп. услуги за дополнительную цену, так с количеством тестов. Мы не можем позволить себе покрыть все риски. Нужно выбрать, сколько платить в виде «страховой премии» и сколько - в случае аварии. При 100% покрытии кода вы хотите избежать риска любой ошибки. А если тестов нет, значит даже серьёзные ошибки с максимальной стоимостью вас не беспокоят.

Как выбрать, какой риск мы хотим покрыть при тестировании? Часто это неявное решение: кто-то считает, что «больше покрытия кода, это хорошо», а затем люди начинают писать больше тестов, потому что «это наша культура, чувак»! Лучший способ – обдумать решение.

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

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

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

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

Как вы решаете это в своей команде? У вас есть цель обеспечить покрытие кода?

Источник: https://ntietz.com/blog/too-much-of-a-good-thing-the-cost-of-excess-testing/
👍2