День 1728. #Testing
Тестирование на Основе Свойств. Простой пример
Теория
Итак, напишем тесты согласно свойствам, которые мы определили.
FsCheck — это платформа тестирования на основе свойств, созданная для F#, но её можно использовать и в C#. Программист предоставляет спецификацию программы в виде свойств, которым должны удовлетворять функции, методы или объекты, а FsCheck проверяет, сохраняются ли эти свойства на большом количестве случайно сгенерированных случаев. Вы фактически пишете тестируемую спецификацию вашей программы. Спецификации выражаются на F#, C# или VB с использованием комбинаторов, определённых в библиотеке FsCheck. FsCheck предоставляет комбинаторы для определения свойств, наблюдения за распределением тестовых данных и определения генераторов тестовых данных. При сбое свойства FsCheck автоматически отображает минимальный контрпример.
Есть плагины FsCheck для всех популярных тестовых сред. Всё, что нам нужно, - это установить NuGet пакет и заменить атрибуты
Тесты по-прежнему будут проходить. По умолчанию FsCheck останавливается после 100 попыток. Если вы хотите знать протестированные значения, задайте свойству Verbose атрибута Property значение true:
Продолжение следует…
Источник: https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-2.html
Тестирование на Основе Свойств. Простой пример
Теория
Итак, напишем тесты согласно свойствам, которые мы определили.
int Add(int x, int y) => x+y;У нас должно получиться 3 теста:
public class AddTwoNumbersTestsПока это не сильно отличается от обычных тестов на основе примеров. Теперь задача состоит в том, чтобы сгенерировать правильные псевдослучайные значения, которые можно использовать в качестве входных данных. В этом нам поможет FsCheck.
{
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 — это платформа тестирования на основе свойств, созданная для 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. Допустим, нам нужно проверить номер кредитной карты.
Если у нас есть метод
Используем FsCheck:
Произвольные значения (Arbitraties)
FsCheck использует комбинацию генераторов (generator) и сокращателей (shrinker) для создания тестовых данных. Генераторы производят случайный набор значений из интервала с равномерным распределением. Сокращатели ограничивают (фильтруют) этот набор по заданному условию. В FsCheck определены произвольные значения по умолчанию для некоторых часто используемых типов:
- NegativeInt
- NonNegativeInt
- NonEmptyString
- IntWithMinMax
- NonEmptyArray
и т.п.
Используем альтернативную форму записи тестового метода с использованием предопределённых произвольных значений:
Продолжение следует…
Источник: https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-3.html
Тестирование на Основе Свойств. Реальный пример
Теория
Простой пример
Сегодня рассмотрим более реалистичный пример и функции, которые предоставляет 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)]Здесь мы использовали произвольные значения для long и функцию-сокращатель (x > 100). Заметьте, что FsCheck сначала генерирует случайные значения, а затем проверяет их на соответствие условию сокращателя. В примере выше при попытке задать большое значение в функции Filter, тесты могут выполняться очень долго.
public Property ValidationShouldFail()
{
var arb = Arb
.Default
.Int64()
.Filter(x => x > 100);
return Prop.ForAll<Int64>(
arb,
x => !CardNumber.IsValid(x.ToString()));
}
Продолжение следует…
Источник: https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-3.html
👍4
  День 1730. #Testing
Тестирование на Основе Свойств. Собственный генератор
Теория
Простой пример
Реальный пример
Сегодня рассмотрим, как писать собственные генераторы.
В предыдущем примере мы использовали Filter(), чтобы возвращать только нужные числа.
Для создания генератора нужен публичный статический класс с публичным статическим методом, возвращающим Arbitrary<T>.
Например, так можно создать генератор месяца в определённом году:
В этом же классе можно создать свой сокращатель:
Источник: https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-4.html
Тестирование на Основе Свойств. Собственный генератор
Теория
Простой пример
Реальный пример
Сегодня рассмотрим, как писать собственные генераторы.
В предыдущем примере мы использовали Filter(), чтобы возвращать только нужные числа.
Для создания генератора нужен публичный статический класс с публичным статическим методом, возвращающим Arbitrary<T>.
public static class CCNumberGeneratorТеперь его можно использовать в качестве типа генератора в атрибуте Property:
{
public static Arbitrary<Int64> Generate()
{
return Arb.Default
.Int64()
.Filter(x => x > 100);
}
}
[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>А затем использовать перегрузку метода Generate, принимающую и генератор, и сокращатель:
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
};
}
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 генерирует случайные входные значения. Эти значения могут быть разными для каждого запуска теста, что означает, что каждый запуск может закончиться разными результатами. Поэтому, если у вас было неправильное входное значение, которое не прошло тест в предыдущий раз, вполне возможно, что при следующем запуске теста вы больше не увидите этот неправильный вход. Для простых входных значений это не должно быть проблемой. Но если вы используете более сложные входные данные, повторить неудачный тест может быть трудно.
Если посмотреть на выходные данные неудавшегося теста, можно заметить строку вроде
Ещё одна область, где могут пригодиться тесты на основе свойств, — это случаи, когда вам нужен своего рода приёмочный тест для какого-то кода. Например, у вас есть некоторый метод, в правильности которого вы уверены, но необходимо провести его рефакторинг, либо он недостаточно быстр. Вы можете написать тест на основе свойств, в котором свойство, которое должно соблюдаться, заключается в том, что для любых входных данных результат изначального метода должен совпадать с результатом нового. Затем увеличьте количество случайных входных данных настолько, чтобы можно было быть уверенным в правильности. И если тесты проходят, значит новый метод работает правильно.
Итого
Тесты на основе свойств полезны, но не являются панацеей. Их может быть сложнее читать другим разработчикам, которые не привыкли проводить тестирование на основе свойств. Когда вы решаете реализовать свои собственные произвольные алгоритмы и ограничиваете возможные произвольные значения, вы рискуете упустить важные случаи из-за излишней строгости, как и при написании обычных тестов. Тесты на основе свойств также выполняются медленнее: по умолчанию они генерируют 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
Тестирование на Основе Свойств. Воспроизведение
Теория
Простой пример
Реальный пример
Собственный генератор
Для каждого тестируемого свойства 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 в тестовом классе:
AutoFixture использует обобщённый метод Create, генерирующий случайные данные для заданного типа, например:
Однако, с помощью дополнительного пакета AutoFixture.Xunit2, вы можете автоматически создавать целые классы, вообще без блока Arrange с помощью атрибута AutoData:
Либо можно использовать метод Create:
Здесь все свойства классов будут заданы случайными значениями.
Вы можете управлять созданием, задавая данные для определённых свойств. Также можно создавать коллекции элементов:
Здесь вызов OmitAutoProperties не будет задавать случайные значения свойствам, у которых есть значения по умолчанию. А Without не будет задавать значение выбранного свойства (в примере выше City останется null).
Вы можете создавать реализации интерфейсов с помощью метода Register:
Здесь каждый раз, когда Autofixture нужно будет создать класс, зависящий от IMyInterface, будет создаваться экземпляр FakeMyInterface.
AutoFixture также можно использовать с пакетами для создания моков, вроде Moq или NSubstitute. Для этого надо настроить создание Autofixture (пример для Moq):
Тогда Autofixture можно будет использовать для создания реализаций интерфейсов:
При этом установка ConfigureMembers в true приводит к тому, что для классов-реализаций интерфейсов Autofixture сгенерирует случайные значения для свойств.
Здесь можно посмотреть множество других примеров использования AutoFixture.
Источник: https://dev.to/serhii_korol_ab7776c50dba/you-write-unit-tests-wrong-5d9f
Пишем Тесты с 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
  