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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 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