{"id":33341,"date":"2021-08-31T15:30:00","date_gmt":"2021-08-31T13:30:00","guid":{"rendered":"https:\/\/blexin.com\/?p=33341"},"modified":"2021-08-13T17:34:09","modified_gmt":"2021-08-13T15:34:09","slug":"testare-la-concorrenza-con-coyote","status":"publish","type":"post","link":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/","title":{"rendered":"Testare la concorrenza con Coyote"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"608\" data-attachment-id=\"33342\" data-permalink=\"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/attachment\/13_ita_1105x656_blog-coyote-a\/\" data-orig-file=\"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&amp;ssl=1\" data-orig-size=\"1105,656\" data-comments-opened=\"0\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"13_ITA_1105x656_blog coyote A\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1024%2C608&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?resize=1024%2C608&#038;ssl=1\" alt=\"actormodel_coyote\" class=\"wp-image-33342\" srcset=\"https:\/\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A-1024x608.png 1024w, https:\/\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A-980x582.png 980w, https:\/\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A-480x285.png 480w\" sizes=\"auto, (min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Nell<a href=\"https:\/\/blexin.com\/it\/blog\/scalabilita-e-resilienza-con-gli-akka-actors\/\" target=\"_blank\" rel=\"noreferrer noopener\">&#8216;ultimo articolo sull\u2019Actor Model<\/a>, abbiamo mostrato come implementare questo pattern usando gli Akka Actors.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Oggi, invece, vedremo come usare Coyote, un framework che implementa l\u2019Actor Model ma che viene utilizzato principalmente per i test.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I programmi deterministici sono facili da testare, perch\u00e9 sai che il programma viene eseguito allo stesso modo se viene fornito lo stesso input. Ma quando non lo sono potrebbe essere pi\u00f9 difficile, e forse un semplice test non pu\u00f2 ottenere tutti i casi di esecuzione possibili al fine di trovare qualche errore o magari un risultato imprevisto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nel nostro scenario, stiamo sviluppando un sottosistema per gestire le thumbnails dei prodotti su una piattaforma di e-commerce e il nostro <em>ProductsController <\/em>contiene il seguente metodo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n &#x5B;HttpPut] \n\n        public async Task&lt;ActionResult&lt;Image&gt;&gt; CreateOrUpdateImageAsync(string productId, Image image) \n\n        { \n\n            var imageItem = image.ToItem(); \n\n            await this.BlobContainer.CreateOrUpdateBlobAsync(\u201cproducts\u201d, productId, image.Content); \n\n  \n\n            imageItem = await this.ImageContainer.UpsertItem(imageItem); \n\n  \n\n            await this.MessagingClient.SubmitMessage(new GenerateProductThumbnailMessage() \n\n            { \n\n                ProductId = productId, \n\n                ImageStorageName = image.Name \n\n            }); \n\n  \n\n            return this.Ok(imageItem.ToImage()); \n\n        } \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In questo metodo, in primo luogo, carichiamo il contenuto dell&#8217;immagine in un Azure Storage, usando <em>this.BlobContainer.CreateOrUpdateBlobAsync<\/em>. Quindi scriviamo i metadati dell&#8217;immagine in un database Azure Cosmos DB, usando <em>this.CosmosContainer.UpsertItem<\/em> e, infine, viene inviato un messaggio di <em>GenerateProductThumbnailMessage<\/em> ad un Azure Service Bus, tramite <em>this.MessagingClient.SubmitMessage<\/em> che restituisce uno status code<em> HTTP 200 (OK)<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La generazione di thumbnails potrebbe richiedere del tempo, quindi preferiamo ottenerla in modo asincrono, con un worker che viene eseguito in background.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Accodiamo una richiesta di generazione di un thumbnail in una coda di messaggi dell\u2019Azure Service Bus, che viene quindi gestita in maniera asincrona dal worker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>MessagingClient<\/em> \u00e8 l&#8217;interfaccia della coda di messaggi del\u2019Azure Service Bus che consente ai client di inviare messaggi asincroni alla coda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Scriviamo quindi un unit test per verificare che il metodo funzioni correttamente:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n&#x5B;Test] \n\n        public static async Task TestProductImageUpdate() \n\n        { \n\n            var cosmosContainer = new CosmosContainerMock(); \n\n            var blobContainer = new BlobContainerMock(); \n\n  \n\n            var productsControllerClient = new TestProductsControllerClient(cosmosContainer, blobContainer); \n\n  \n\n            var productId = Guid.NewGuid().ToString(); \n\n            var image = new Image() \n\n            { \n\n                Name = &quot;image.jpg&quot;, \n\n                ImageType = &quot;JPEG&quot;, \n\n                Content = new byte&#x5B;] { 0, 1, 2, 3, 4 } \n\n            }; \n\n            var result = await productsControllerClient.CreateOrUpdateImageAsync(productId, image); \n\n  \n\n            if (result.StatusCode == HttpStatusCode.OK) \n\n            { \n\n                var imageResponse = await productsControllerClient.GetImageContentsAsync(productId); \n\n                Assert.IsTrue(imageResponse.StatusCode == HttpStatusCode.OK); \n\n            } \n\n        } \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Il test \u00e8 formalmente corretto e dar\u00e0 sempre esito positivo, ma basta per testare il nostro metodo?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le domande principali sono: cosa succede se gli operatori aggiornano l&#8217;immagine del prodotto due o pi\u00f9 volte? Qual \u00e8 il risultato finale? Come siamo in grado di gestire la concorrenza nella nostra piattaforma?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per rispondere a queste domande possiamo ricorrere a <strong>Coyote<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Coyote \u00e8 uno strumento di integrazione, utile durante lo sviluppo di software asincrono, che ti aiuta sia con i test che infine con lo sviluppo, fornendo astrazioni di programmazione di alto livello, supporto per scrivere specifiche di sistema dettagliate e uno strumento di test molto efficace.<br>Troviamo i test con Coyote molto utili, perch\u00e9 aiutano a trovare bug di concorrenza che altrimenti non potrebbero essere riprodotti.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tale strumento di test pu\u00f2 anche essere integrato con il framework di unit test standard fornito da .NET.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;installazione del framework Coyote \u00e8 facile perch\u00e9 abbiamo solo bisogno di eseguire due comandi tramite la cli di .NET:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndotnet install Microsoft.Coyote \n\ndotnet install Microsoft.Coyote.Test \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Possiamo anche installare il tool dotnet coyote eseguendo il comando:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ndotnet tool install --global Microsoft.Coyote.CLI \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In questo modo, possiamo utilizzare lo strumento Coyote per eseguire la riscrittura e il test della DLL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quindi, partendo dal test base scritto precedentemente, scriviamo un test di concorrenza, utilizzando Coyote, per esercitare lo scenario in cui due richieste vengono presentate sull&#8217;aggiornamento dell\u2019immagine dello stesso prodotto:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n&#x5B;TestMethod] \n\n        public async Task TestConcurrentProductImageUpdate() \n\n        { \n\n            var cosmosState = new MockCosmosState(); \n\n            var database = new MockCosmosDatabase(cosmosState); \n\n            var imageContainer = (MockCosmosContainer)await database.CreateContainerAsync(Constants.ImageContainerName); \n\n            var blobContainer = new MockBlobContainerProvider(); \n\n            var messagingClient = new MockMessagingClient(blobContainer); \n\n  \n\n            var productsControllerClient = new TestProductsControllerClient(imageContainer, blobContainer, messagingClient); \n\n  \n\n            var productId = Guid.NewGuid().ToString(); \n\n            var image1 = new Image() \n\n            { \n\n                Name = &quot;image1.jpg&quot;, \n\n                ImageType = &quot;JPEG&quot;, \n\n                Content = new byte&#x5B;] { 0, 1, 2, 3, 4 } \n\n            }; \n\n  \n\n            var image2 = new Image() \n\n            { \n\n                Name = &quot;image2.jpg&quot;, \n\n                ImageType = &quot;JPEG&quot;, \n\n                Content = new byte&#x5B;] { 5, 6, 7, 8, 9 } \n\n            }; \n\n  \n\n            var task1 = productsControllerClient.CreateOrUpdateImageAsync(productId, image1); \n\n            var task2 = productsControllerClient.CreateOrUpdateImageAsync(productId, image2); \n\n            await Task.WhenAll(task1, task2); \n\n  \n\n            Assert.IsTrue(task1.Result.StatusCode == HttpStatusCode.OK); \n\n            Assert.IsTrue(task1.Result.StatusCode == HttpStatusCode.OK); \n\n  \n\n            var imageResult = await productsControllerClient.GetImageAsync(productId); \n\n            Assert.IsTrue(imageResult.StatusCode == HttpStatusCode.OK); \n\n            byte&#x5B;] image = imageResult.Resource; \n\n  \n\n            byte&#x5B;] thumbnail; \n\n            while (true) \n\n            { \n\n                var thumbnailResult = await productsControllerClient.GetImageThumbnailAsync(productId); \n\n                if (thumbnailResult.StatusCode == HttpStatusCode.OK) \n\n                { \n\n                    thumbnail = thumbnailResult.Resource; \n\n                    break; \n\n                } \n\n            } \n\n  \n\n            Assert.IsTrue(image.SequenceEqual(thumbnail)); \n\n        } \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Se si tenta di eseguire questo test di concorrenza, \u00e8 molto probabile che l&#8217;asserzione abbia esito negativo. Il fallimento del test non \u00e8 sempre sempre garantito, perch\u00e9 ci sono alcuni task interconnessi dove passa correttamente ed altri dove invece fallisce.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">E\u2019 molto importante, durante la scrittura dei nostri test, configurare correttamente I mock, dal momento che devono simulare il comportamento reale e allo stesso tempo devono aiutare a scovare I bug relativi alla concorrenza.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ad esempio, il nostro MockBlobContainer, introdotto nel test precedente, contiene un dictionary di containers e un metodo di utility per aggiungerli ed eliminarli:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\ninternal class MockBlobContainerProvider : IBlobContainer \n\n    { \n\n        private readonly ConcurrentDictionary&lt;string, ConcurrentDictionary&lt;string, byte&#x5B;]&gt;&gt; Containers; \n\n  \n\n        internal MockBlobContainerProvider() \n\n        { \n\n            this.Containers = new ConcurrentDictionary&lt;string, ConcurrentDictionary&lt;string, byte&#x5B;]&gt;&gt;(); \n\n        } \n\n  \n\n        public Task CreateContainerAsync(string containerName) \n\n        { \n\n            return Task.Run(() =&gt; \n\n            { \n\n                this.Containers.TryAdd(containerName, new ConcurrentDictionary&lt;string, byte&#x5B;]&gt;()); \n\n            }); \n\n        } \n\n  \n\n\/\/\u2026 \n\n  \n\n        public Task DeleteBlobAsync(string containerName, string blobName) \n\n        { \n\n            return Task.Run(() =&gt; \n\n            { \n\n                this.Containers&#x5B;containerName].TryRemove(blobName, out byte&#x5B;] _); \n\n            }); \n\n        } \n\n  \n\n    } \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Quindi il test precedente \u00e8 corretto ma probabilmente inutile, poich\u00e9 riesce a trovare bug solamente in circostanze fortuite. Se modifichiamo leggermente il test aggiungendo un ritardo di un millisecondo tra le due chiamate al metodo <em>CreateOrUpdateImageAsync:<\/em>, ed eseguendo questo test cento volte, probabilmente non fallir\u00e0 mai:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nvar task1 = productsControllerClient.CreateOrUpdateImageAsync(productId, image1); \n\n            await Task.Delay(1);  \n\n            var task2 = productsControllerClient.CreateOrUpdateImageAsync(productId, image2); \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Questo nostro unit test \u00e8 inefficace nel catturare le condizioni che potrebbero portare ad un errore o a un risultato diverso da quello atteso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Gli sviluppatori spesso scrivono stress test, in cui il sistema \u00e8 bombardato da migliaia di richieste simultanee per scoprire bug non deterministici, ma lo stress test pu\u00f2 essere complesso da configurare e non sempre trova i bug pi\u00f9 complicati.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Utilizzare Coyote nel tuo programma basato su task \u00e8 molto facile nella maggior parte dei casi. Tutto quello che devi fare \u00e8 eseguire il tool coyote per riscrivere l&#8217;assembly in modo che Coyote possa iniettare una logica che gli consenta di assumere il controllo dei task C#.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quindi, \u00e8 possibile richiamare lo strumento di test coyote che esplora sistematicamente le interconnessioni tra le attivit\u00e0 per scoprire il bug di concorrenza. Ci\u00f2 che \u00e8 ancora meglio \u00e8 che, se un bug viene scoperto, Coyote ti consente di riprodurlo deterministicamente ogni volta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ora possiamo eseguire il test sotto il controllo di Coyote, ma, come primo passo, dobbiamo riscrivere la DLL. La riscrittura \u00e8 un processo che carica uno o pi\u00f9 assembly e li riscrive per il test. Il codice riscritto mantiene la stessa semantica della versione originale, ma ha diversi stub e hooks iniettati che consentono a Coyote di prendere il controllo della concorrenza e dei costrutti non deterministici all\u2019interno del programma.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Quindi, per riscrivere la DLL, dobbiamo eseguire:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\ncoyote rewrite .\\ProductManager.dll\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Questo comando inietta la logica che consente a Coyote di assumere il pieno controllo dei task C# e di altre parti non deterministiche nel programma.<br>Infine possiamo invocare lo strumento <em>coyote<\/em> per testare il nostro programma esplorando sistematicamente le diverse interconnessioni di attivit\u00e0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nel nostro caso testiamo il metodo <em>TestConcurrentProductImageUpdate<\/em> per 100 iterazioni.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\ntest coyote .\\ ProductManager.dll -m TestConcurrentProductImageUpdate -i 100  \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Coyote trova quasi immediatamente le condizioni che potrebbero generare problemi di concorrenza nel metodo, esattamente in 3 iterazioni di test.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n... Elapsed 2.3226862 sec.19 \n... Testing statistics:20 \n..... Found 1 bug21 \n\n... Scheduling statistics:22 \n\n..... Explored 3 schedules: 3 fair and 0 unfair.23 \n\n..... Found 100.00% buggy schedules24 \n\n..... Number of scheduling points in fair terminating schedules 34 (min), 34(avg), 34 (max).25 \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">In ogni iterazione di test, Coyote eseguir\u00e0 lo unit test dall&#8217;inizio alla fine pi\u00f9 volte, esplorando ogni volta interconnessioni di task potenzialmente differenti e altre scelte non deterministiche, alla ricerca di bug.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cosa ancora pi\u00f9 importante, genera un file <em>.schedule<\/em>, che ci consente di riprodurre esattamente lo stesso percorso che ha portato al bug. Dopo aver riprodotto il bug, possiamo cercare di risolverlo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nel nostro caso, modifichiamo il metodo <em>CreateOrUpdateImageAsync<\/em> in questo modo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n &#x5B;HttpPut] \n\n        public async Task&lt;ActionResult&lt;Image&gt;&gt; CreateOrUpdateImageAsync (string productId, Image image) \n\n        { \n\n            var imageItem = image.ToItem(); \n\n  \n\n            var uniqueId = Guid.NewGuid().ToString(); \n\n            imageItem.StorageName = uniqueId; \n\n  \n\n            await this.BlobContainer.CreateOrUpdateBlobAsync(&quot;products&quot;, imageItem.StorageName, image.Content); \n\n  \n\n            imageItem = await this.ImageContainer.UpsertItem(imageItem); \n\n  \n\n            await this.MessagingClient.SubmitMessage(new GenerateProductThumbnailMessage() \n\n            { \n\n                ProductId = productId, \n\n                ImageStorageName = imageItem.StorageName \n\n            }); \n\n  \n\n            return this.Ok(imageItem.ToImage()); \n\n        } \n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Per correggere questo bug, possiamo generare nel controller un GUID univoco per ogni richiesta. L&#8217;immagine e l&#8217;anteprima utilizzano questo GUID come chiave quando vengono archiviati nel blob container dell\u2019Azure Storage, anzich\u00e9 utilizzare il nome fornito dall&#8217;utente.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Questo approccio garantisce che due handler <em>CreateOrUpdateImageAsync<\/em> concorrenti non interferiscano mai tra loro.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusioni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In questo articolo, ti abbiamo mostrato come usare Coyote per testare la concorrenza e<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">speriamo di aver suscitato il tuo interesse per l&#8217;argomento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il progetto di esempio con il codice utilizzato in questo articolo \u00e8 <a href=\"https:\/\/github.com\/enricobencivenga\/Coyote\" target=\"_blank\" rel=\"noreferrer noopener\">disponibile qui<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Al prossimo articolo!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Usiamo Coyote per testare i nostri metodi asincroni<\/p>\n","protected":false},"author":196716247,"featured_media":33342,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"off","_et_pb_old_content":"","_et_gb_content_width":"","_coblocks_attr":"Asap,","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","_crdt_document":"","inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"jetpack_post_was_ever_published":false},"categories":[688637374],"tags":[688637382,688637409],"class_list":["post-33341","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-c","tag-testing"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Testare la concorrenza con Coyote - Blexin<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/\" \/>\n<meta property=\"og:locale\" content=\"it_IT\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Testare la concorrenza con Coyote - Blexin\" \/>\n<meta property=\"og:description\" content=\"Usiamo Coyote per testare i nostri metodi asincroni\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/\" \/>\n<meta property=\"og:site_name\" content=\"Blexin\" \/>\n<meta property=\"article:published_time\" content=\"2021-08-31T13:30:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1105\" \/>\n\t<meta property=\"og:image:height\" content=\"656\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Enrico Bencivenga\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Scritto da\" \/>\n\t<meta name=\"twitter:data1\" content=\"Enrico Bencivenga\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tempo di lettura stimato\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minuti\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/\"},\"author\":{\"name\":\"Enrico Bencivenga\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/#\\\/schema\\\/person\\\/74e4443d1d7ad12d5b4a8db7f63f0194\"},\"headline\":\"Testare la concorrenza con Coyote\",\"datePublished\":\"2021-08-31T13:30:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/\"},\"wordCount\":1087,\"image\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/blexin.com\\\/wp-content\\\/uploads\\\/2021\\\/08\\\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1\",\"keywords\":[\"C#\",\"Testing\"],\"articleSection\":[\"Blog\"],\"inLanguage\":\"it-IT\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/\",\"url\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/\",\"name\":\"Testare la concorrenza con Coyote - Blexin\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/i0.wp.com\\\/blexin.com\\\/wp-content\\\/uploads\\\/2021\\\/08\\\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1\",\"datePublished\":\"2021-08-31T13:30:00+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/#\\\/schema\\\/person\\\/74e4443d1d7ad12d5b4a8db7f63f0194\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#breadcrumb\"},\"inLanguage\":\"it-IT\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"it-IT\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#primaryimage\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/blexin.com\\\/wp-content\\\/uploads\\\/2021\\\/08\\\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/blexin.com\\\/wp-content\\\/uploads\\\/2021\\\/08\\\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1\",\"width\":1105,\"height\":656,\"caption\":\"actormodel_coyote\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/blog\\\/testare-la-concorrenza-con-coyote\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/blexin.com\\\/it\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Testare la concorrenza con Coyote\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/#website\",\"url\":\"https:\\\/\\\/blexin.com\\\/it\\\/\",\"name\":\"Blexin\",\"description\":\"Con noi \u00e8 semplice\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/blexin.com\\\/it\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"it-IT\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/blexin.com\\\/it\\\/#\\\/schema\\\/person\\\/74e4443d1d7ad12d5b4a8db7f63f0194\",\"name\":\"Enrico Bencivenga\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"it-IT\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g\",\"caption\":\"Enrico Bencivenga\"},\"url\":\"https:\\\/\\\/blexin.com\\\/it\\\/author\\\/enrico-bencivengablexin-com\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Testare la concorrenza con Coyote - Blexin","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/","og_locale":"it_IT","og_type":"article","og_title":"Testare la concorrenza con Coyote - Blexin","og_description":"Usiamo Coyote per testare i nostri metodi asincroni","og_url":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/","og_site_name":"Blexin","article_published_time":"2021-08-31T13:30:00+00:00","og_image":[{"width":1105,"height":656,"url":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","type":"image\/png"}],"author":"Enrico Bencivenga","twitter_card":"summary_large_image","twitter_misc":{"Scritto da":"Enrico Bencivenga","Tempo di lettura stimato":"7 minuti"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#article","isPartOf":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/"},"author":{"name":"Enrico Bencivenga","@id":"https:\/\/blexin.com\/it\/#\/schema\/person\/74e4443d1d7ad12d5b4a8db7f63f0194"},"headline":"Testare la concorrenza con Coyote","datePublished":"2021-08-31T13:30:00+00:00","mainEntityOfPage":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/"},"wordCount":1087,"image":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","keywords":["C#","Testing"],"articleSection":["Blog"],"inLanguage":"it-IT"},{"@type":"WebPage","@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/","url":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/","name":"Testare la concorrenza con Coyote - Blexin","isPartOf":{"@id":"https:\/\/blexin.com\/it\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#primaryimage"},"image":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","datePublished":"2021-08-31T13:30:00+00:00","author":{"@id":"https:\/\/blexin.com\/it\/#\/schema\/person\/74e4443d1d7ad12d5b4a8db7f63f0194"},"breadcrumb":{"@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#breadcrumb"},"inLanguage":"it-IT","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/"]}]},{"@type":"ImageObject","inLanguage":"it-IT","@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#primaryimage","url":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","contentUrl":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","width":1105,"height":656,"caption":"actormodel_coyote"},{"@type":"BreadcrumbList","@id":"https:\/\/blexin.com\/it\/blog\/testare-la-concorrenza-con-coyote\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blexin.com\/it\/"},{"@type":"ListItem","position":2,"name":"Testare la concorrenza con Coyote"}]},{"@type":"WebSite","@id":"https:\/\/blexin.com\/it\/#website","url":"https:\/\/blexin.com\/it\/","name":"Blexin","description":"Con noi \u00e8 semplice","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blexin.com\/it\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"it-IT"},{"@type":"Person","@id":"https:\/\/blexin.com\/it\/#\/schema\/person\/74e4443d1d7ad12d5b4a8db7f63f0194","name":"Enrico Bencivenga","image":{"@type":"ImageObject","inLanguage":"it-IT","@id":"https:\/\/secure.gravatar.com\/avatar\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4ea7187309674789d6f02c6b757e1f21c8cf800abb2419b4edaa8b09d4c99548?s=96&d=identicon&r=g","caption":"Enrico Bencivenga"},"url":"https:\/\/blexin.com\/it\/author\/enrico-bencivengablexin-com\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/blexin.com\/wp-content\/uploads\/2021\/08\/13_ITA_1105x656_blog-coyote-A.png?fit=1105%2C656&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/pcyUBx-8FL","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/posts\/33341","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/users\/196716247"}],"replies":[{"embeddable":true,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/comments?post=33341"}],"version-history":[{"count":19,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/posts\/33341\/revisions"}],"predecessor-version":[{"id":33369,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/posts\/33341\/revisions\/33369"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/media\/33342"}],"wp:attachment":[{"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/media?parent=33341"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/categories?post=33341"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blexin.com\/it\/wp-json\/wp\/v2\/tags?post=33341"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}