Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
Microsoft.Playwright.MSTest. Вот тестовый класс:
[TestClass]
public class MyUnitTests : PageTest
{
[TestMethod]
public async Task TestGoogleSearch()
{
//Учитывая, что я на https://www.google.com
await Page.GotoAsync("https://www.google.com");
//Когда я набираю "netdeveloperdiary" в поле поиска
await Page.FillAsync("[title=\"Поиск\"]", "netdeveloperdiary");
//И нажимаю кнопку "Поиск в Google"
await Page.ClickAsync("[value=\"Поиск в Google\"] >> nth=1");
//Тогда первым результатом будет https://t.me › NetDeveloperDiary
var firstResult = await Page.Locator("//cite >> nth=0").InnerTextAsync();
Assert.AreEqual("https://t.me › NetDeveloperDiary", firstResult);
}
}
Вот на что стоит обратить внимание.
1. Тестовый класс наследует от PageTest
Пакет PlayWright.MSTest содержит код настройки объекта браузера, а также поддерживает параллельные тесты. Без использования этого пакета мы могли бы настроить объект браузера сами, и получить большую свободу:
IPage Page;
[TestInitialize]
public async Task TestInitialize()
{
var plwrt = await Playwright.CreateAsync();
var brw = await plwrt.Chromium.LaunchAsync(
new BrowserTypeLaunchOptions
{
Headless = false
});
Page = await brw.NewPageAsync();
}
2. Мы не используем таймауты и все методы асинхронные.
Нам не нужно ожидать запуска браузера и момента, пока элементы отобразятся на экране. Всё это берёт на себя Playwright. Например, когда вы хотите заполнить поле для ввода, Playwright предполагает, что оно должно появиться на странице, и ждёт его появления (по умолчанию 30 секунд). Помимо этого, то, что всё происходит асинхронно означает, что вы не столкнётесь с замиранием экрана во время ожидания следующей операции.
В остальном тесты должны быть понятны. Мы заполняем поле для ввода, нажимаем на кнопку поиска и проверяем первый результат.
Примечание. При первом использовании Playwright на вашей машине, нужно выполнить следующую команду из вашего проекта для установки движков браузеров:
bin\Debug\net6.0\playwright.ps1 installPlaywright содержит множество методов управления браузером и анализа результатов, а также может выполнять и более сложные действия, вроде снимка экрана, который можно сохранить для последующего анализа или сравнения. Источник: dotnetcoretutorials.com/2022/05…th-c-net
I и производят выходные данные O. Нечистые функции принимают входные данные в форме (I, S), где I — входные параметры, а S представляет внешнее состояние, подобное глобальным переменным. Нечистые функции также производят результат O, однако при этом O может влиять на S (и даже на I).
Как S меняет ситуацию? Мы можем утверждать, что S представляет состояние приложения, которое является неявными входными данными для функции. Имея это в виду, как чистые, так и нечистые функции возвращают один и тот же результат O для полученных входных данных. Однако чистые функции используют (I, null) в качестве входных данных, а нечистые используют (I, S). С этой точки зрения оба типа функций на самом деле чистые. В чём же разница?
Разница в том, что отследить I намного проще, чем S. Обычно нужно просто проанализировать место вызова и место, где I было задано значение. Нам не нужно смотреть на остальной код. Аналогично мы можем отследить входные данные вызывающей функции, и т.д. вверх по стеку вызовов.
Однако мы не можем так просто отследить S. Чтобы доказать что-либо об S, нам нужно не только показать, как было задано значение S, но также и доказать, что оно не было изменено ничем за пределами стека вызовов. Другими словами, чтобы что-то доказать гипотезу об I, нам нужно просто показать пример, подтверждающий нашу гипотезу. Например, что Math.Sqrt(4) = 2. Однако, чтобы доказать что-то об S, нам нужно показать, что нет никаких «контраргументов» (кода, изменяющего состояние S неожиданным образом), опровергающих нашу гипотезу. Последнее, как правило, намного сложнее, так как нам нужно проанализировать гораздо больше кода, чтобы доказать это. Кроме того, поиск такого кода может быть нетривиальным, так как это может быть рефлексия, параллельно выполняемый код и т.п. Или код может быть вообще недоступен, как какой-то динамически загружаемый плагин.
Однако до тех пор, пока мы можем поддерживать I и S небольшими по размеру, всё остальное не имеет значения. Дело не в том, чистая функция или нет. Всё дело в способности отследить входные данные (I, S). Это зависит от ваших когнитивных навыков, опыта и навыков программирования в целом. Некоторые функции могут быть ужасны и с тремя входными параметрами и без глобального состояния, тогда как другие функции могут отлично работать без параметров, но с пятью глобальными переменными.
А что насчёт изменчивости? В большинстве случаев это не имеет значения. Давайте рассмотрим пример функции double.TryParse. По определению она нечистая, так как 1) изменяет входной параметр и 2) использует глобальное состояние (символ десятичного разделителя). Однако является ли эта функция «неправильной» или «плохой»? Нет, потому что её «радиус поражения» обычно сильно ограничен.
Примечание: на самом деле все функции принимают ещё один параметр E. Параметр представляет собой среду, такую как память, используемую операционную систему, тип процессора, электрические помехи или даже космические лучи. Разница между S и E заключается в том, что мы контролируем. Мы можем контролировать S, поскольку это состояние выполняемого нами приложения, но мы не можем контролировать E. Вы можете косвенно повлиять на память, используемую операционной системой, но, скорее всего, вы не сможете повлиять на космический луч.
Источник: blog.adamfurmanek.pl/2022/07…-part-14Order, которая имеет коллекцию сущностей OrderItem. Order — корень:
public class Order
{
// прочие свойства и методы опущены
public IEnumerable<OrderItem> OrderItems { get; private set;}
}
public class OrderItem
{
// прочие свойства и методы опущены
public int ProductId { get; set; }
public int Quantity { get; set; }
}
Некоторые типичные операции агрегата:
- создание нового заказа,
- добавление OrderItem в заказ,
- изменение количества в OrderItem.
Некоторые бизнес-правила:
- валидный заказ имеет хотя бы один OrderItem,
- валидная коллекция OrderItems не содержит повторяющихся ProductId,
- валидный OrderItem имеет положительное количество.
Очевидно, что Order здесь отвечает за первые 2 бизнес-правила, а вот за валидацию количества (и, возможно, других своих свойств) может (и должен) отвечать OrderItem. Можно, конечно, пойти дальше и использовать для свойства Quantity тип, не допускающий отрицательных значений, но сегодня не об этом.
Order, отвечающий за валидность всего агрегата, может при этом использовать поведение OrderItem. Ему не нужно проверять OrderItem извне, если он может быть уверен, что дочерний объект валиден. Вам же не нужно проверять, что свойство Month типа DateTime меньше 13. Тип делает эту проверку за вас, поэтому вы можете быть уверены, что месяц будет в допустимом диапазоне, если у вас есть экземпляр типа.
Многие разработчики также считают, что правило агрегата, гарантирующее, что весь агрегат является «действительным», требует метода для проверки достоверности. Иногда это полезно, но лучший дизайн — это тот, который изначально не допускает существования недопустимых значений. Снова рассмотрим тип DateTime. У него нет метода IsValid(), который вы вызываете после создания экземпляра, указав год, месяц и день как 2022, 50 и 50. Тип просто не позволит вам установить свои месяц или день в значение 50. Если вам удастся создать экземпляр DateTime, вы можете быть уверены, что он валиден, а также вы не сможете использовать ни один из его методов, чтобы поместить его в недопустимое состояние. Этот же подход нужно использовать со своими агрегатами и сущностями.
Итого
Корень агрегата отвечает за то, чтобы агрегат находился в допустимом состоянии. Но это не означает, что каждая операция, выполняемая над любым элементом агрегата, должна находиться в корне агрегата. Это также не означает, что агрегат должен иметь метод, выполняющий проверку. Использование надлежащего объектно-ориентированного дизайна и обеспечение того, чтобы ваши объекты инкапсулировали и управляли своим собственным состоянием, обеспечивает более чистый дизайн.
См. также Сущности и Объекты-Значения
Источник: ardalis.com/aggrega…y-designvar queue = Channel.CreateBounded<int>(1); var writer = queue.Writer; // Эта запись завершается немедленно. await writer.WriteAsync(7); // Эта запись (асинхронно) ожидает удаления 7 // перед тем как вставить в очередь 13. await writer.WriteAsync(13); writer.Complete();Тип
BufferBlock<T> также имеет встроенную поддержку регулировки, следует задать параметр BoundedCapacity:
var queue = new BufferBlock<int>(
new DataflowBlockOptions
{
BoundedCapacity = 1
});
// Эта отправка завершается немедленно.
await queue.SendAsync(7);
// Эта отправка (асинхронно) ожидает удаления 7
// перед тем как ставить в очередь 13.
await queue.SendAsync(13);
queue.Complete();
Производитель в этом фрагменте кода использует асинхронный метод SendAsync.
Блокирующие очереди «производитель/потребитель» также поддерживают регулировку. Вы можете использовать тип BlockingCollection<T> для регулировки количества элементов, для чего при создании передается соответствующее значение:
var queue = new BlockingCollection<int>(boundedCapacity: 1); // Это добавление завершается немедленно. queue.Add(7); // Это добавление ожидает удаления 7 // перед тем, как добавлять 13. queue.Add(13); queue.CompleteAdding();Итого Регулировка необходима в том случае, если производители работают быстрее потребителей. Один из сценариев, которые необходимо рассмотреть: могут ли производители работать быстрее потребителей, если ваше приложение будет работать на другом оборудовании? Обычно некоторая регулировка потребуется для того, чтобы гарантировать нормальную работу на будущем оборудовании и/или облачных платформах, которые нередко более ограничены в ресурсах, чем машины разработчиков. Регулировка замедляет работу производителей, блокируя их, чтобы потребители гарантированно могли обработать все элементы без излишних затрат памяти. Если обрабатывать каждый элемент не обязательно, можно использовать выборку вместо регулировки (об этом в будущих постах). См. также - Асинхронные очереди - Блокирующие очереди Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.
var scriptURL = $"https://{Request.Host}/script.js";
13. Открытые перенаправления
Веб-сайты часто должны автоматически перенаправлять своих пользователей на другие страницы. Например, такой сценарий возникает, когда не прошедшие авторизацию пользователи пытаются получить доступ к странице, требующей входа в систему. Веб-сайт обычно перенаправляет их на страницу входа, а затем возвращает в исходное местоположение после авторизации.
Во время атаки с открытым перенаправлением злоумышленник обманом заставляет пользователя посетить внешний сайт, предоставляя ему URL-адрес законного сайта, который перенаправляет куда-то еще. Это может заставить пользователей поверить, что они все ещё находятся на исходном сайте, и помочь мошенникам создать более правдоподобную фишинговую страницу (см. подробнее).
Чтобы предотвратить открытые перенаправления, вам необходимо убедиться, что приложение не перенаправляет пользователей на вредоносные места. Например, вы можете полностью запретить перенаправления за пределы сайта, проверяя URL-адреса перенаправления. Есть много других способов предотвращения открытых перенаправлений, таких как проверка реферера запросов или использование индексов страниц для перенаправлений. Однако из-за сложности проверки URL-адресов открытые перенаправления остаются распространенной проблемой в современных веб-приложениях.
Продолжение следует…
Источник: dzone.com/article…eatsheetWriteAsync, а когда завершают производство элементов, один из них вызывает Complete для уведомления канала о том, что в дальнейшем элементов больше не будет:
var queue = Channel.CreateUnbounded<int>(); // Код-производитель var writer = queue.Writer; await writer.WriteAsync(7); await writer.WriteAsync(13); writer.Complete(); // Код-потребитель // Выводит "7", затем "13". var reader = queue.Reader; await foreach (int value in reader.ReadAllAsync()) Console.WriteLine(value);Код-потребитель использует асинхронные потоки. 2. System.Threading.Tasks.Dataflow
BufferBlock<T> из библиотеки TPL Dataflow имеет много общего с каналом:
var queue = new BufferBlock<int>(); // Код-производитель await queue.SendAsync(7); await queue.SendAsync(13); queue.Complete(); // Код-потребитель. // Выводит "7", затем "13". while (await queue.OutputAvailableAsync()) Console.WriteLine(await queue.ReceiveAsync());Код-потребитель использует метод
OutputAvailableAsync, который на самом деле полезен только с одним потребителем. Если потребителей несколько, может случиться, что OutputAvailableAsync вернет true для нескольких потребителей, хотя элемент только один. Если очередь завершена, то ReceiveAsync выдаст исключение InvalidOperationException. Таким образом, для нескольких потребителей код будет выглядеть так:
while (true)
{
int item;
try
{
item = await queue.ReceiveAsync();
}
catch (InvalidOperationException)
{
break;
}
Console.WriteLine(item);
}
Библиотека Channels больше подходит для асинхронных очередей «производитель/потребитель» там, где это возможно. Помимо регулировки для случаев, когда производители работают быстрее потребителей, поддерживаются несколько режимов выборки (об этом в следующих постах). Однако, если логика вашего приложения может быть выражена в виде «конвейера», через который проходят данные, TPL Dataflow может быть более логичным кандидатом.
См. также
- очереди «производитель/потребитель» с блокирующей семантикой вместо асинхронной.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.