Testando e depurando sequências observáveis
Testando seu aplicativo Rx
Se você tiver uma sequência observável que publica valores durante um longo período de tempo, testá-lo em tempo real pode ser um alongamento. A biblioteca de Extensão Reativa fornece o tipo TestScheduler para ajudar a testar esse tipo de código dependente de tempo sem realmente esperar o tempo passar. O TestScheduler herda VirtualScheduler e permite que você crie, publique e assine sequências em tempo emulado. Por exemplo, você pode compactar uma publicação que leva 5 dias para ser concluída em uma execução de 2 minutos, mantendo a escala correta. Você também pode pegar uma sequência que realmente aconteceu no passado (por exemplo, uma sequência de tiques de estoque para um ano anterior) e calcular ou assinar como se estivesse enviando novos valores em tempo real.
O método de fábrica Start executa todas as tarefas agendadas até que a fila esteja vazia ou você possa especificar um tempo para que as tarefas enfileiradas sejam executadas apenas no horário especificado.
O exemplo a seguir cria uma sequência observável frequente com as notificações OnNext especificadas. Em seguida, ele inicia o agendador de teste e especifica quando assinar e descartar a sequência observável frequente. O método Start retorna uma instância do ITestableObserver, que contém uma propriedade Messages que registra todas as notificações em uma lista.
Depois que a sequência for concluída, usaremos o método ReactiveAssert.AreElementEqual para comparar a propriedade Messages , juntamente com uma lista de valores esperados para ver se ambos são idênticos (com o mesmo número de itens e os itens são iguais e na mesma ordem). Ao fazer isso, podemos confirmar que realmente recebemos as notificações que esperamos. Em nosso exemplo, como começamos a assinar apenas em 150, perderemos o valor abc
. No entanto, quando comparamos os valores que recebemos até agora em 400, percebemos que de fato recebemos todos os valores publicados depois que assinamos a sequência. E também verificamos se a OnCompleted
notificação foi disparada no momento certo em 500. Além disso, as informações de assinatura também são capturadas pelo tipo ITestableObservable retornado pelo método CreateHotObservable.
Da mesma forma, você pode usar ReactiveAssert.AreElementsEqual para confirmar que as assinaturas realmente aconteceram nos horários esperados.
using System;
using System.Reactive;
using System.Reactive.Linq;
using Microsoft.Reactive.Testing;
class Program : ReactiveTest
{
static void Main(string[] args)
{
var scheduler = new TestScheduler();
var input = scheduler.CreateHotObservable(
OnNext(100, "abc"),
OnNext(200, "def"),
OnNext(250, "ghi"),
OnNext(300, "pqr"),
OnNext(450, "xyz"),
OnCompleted<string>(500)
);
var results = scheduler.Start(
() => input.Buffer(() => input.Throttle(TimeSpan.FromTicks(100), scheduler))
.Select(b => string.Join(",", b)),
created: 50,
subscribed: 150,
disposed: 600);
ReactiveAssert.AreElementsEqual(results.Messages, new Recorded<Notification<string>>[] {
OnNext(400, "def,ghi,pqr"),
OnNext(500, "xyz"),
OnCompleted<string>(500)
});
ReactiveAssert.AreElementsEqual(input.Subscriptions, new Subscription[] {
Subscribe(150, 500),
Subscribe(150, 400),
Subscribe(400, 500)
});
}
}
Depurando seu aplicativo Rx
Você pode usar o operador Do para depurar seu aplicativo Rx. O operador Do permite que você especifique várias ações a serem executadas para cada item de sequência observável (por exemplo, imprimir ou registrar o item, etc.). Isso é especialmente útil quando você está encadeando muitos operadores e deseja saber quais valores são produzidos em cada nível.
No exemplo a seguir, vamos reutilizar o exemplo de Buffer que gera inteiros a cada segundo, colocando-os em buffers que podem conter 5 itens cada. Em nosso exemplo original no tópico Consultando sequências observáveis usando operadores LINQ , assinamos somente a sequência observável final (IList<>) quando o buffer estiver cheio (e antes de ser esvaziado). Neste exemplo, no entanto, usaremos o operador Do para imprimir os valores quando eles estiverem sendo enviados pela sequência original (um inteiro a cada segundo). Quando o buffer estiver cheio, usaremos o operador Do para imprimir o status, antes de entregar tudo isso como a sequência final para o observador assinar.
var seq1 = Observable.Interval(TimeSpan.FromSeconds(1))
.Do(x => Console.WriteLine(x.ToString()))
.Buffer(5)
.Do(x => Console.WriteLine("buffer is full"))
.Subscribe(x => Console.WriteLine("Sum of the buffer is " + x.Sum()));
Console.ReadKey();
Como você pode ver neste exemplo, uma assinatura está no final do destinatário de uma série de sequências observáveis encadeadas. No início, criamos uma sequência observável de inteiros separados por um segundo usando o operador Interval. Em seguida, colocamos 5 itens em um buffer usando o operador Buffer e os enviamos como outra sequência somente quando o buffer estiver cheio. Por fim, isso é entregue ao operador Subscribe. Os dados propagam todas essas sequências intermediárias até que sejam enviadas por push para o observador. Da mesma forma, as assinaturas são propagadas na direção inversa para a sequência de origem. Ao inserir o operador Do no meio dessas propagações, você pode "espionar" esse fluxo de dados, assim como você usa Console.WriteLine no .NET ou printf() em C para executar a depuração.
Você também pode usar o operador Timestamp para verificar a hora em que um item é enviado por uma sequência observável. Isso pode ajudá-lo a solucionar problemas de operações baseadas em tempo para garantir a precisão. Lembre-se do exemplo a seguir do tópico Criando e Assinando para Sequências Observáveis Simples , no qual encadeamos o operador Timestamp à consulta para que cada valor enviado pela sequência de origem seja acrescentado pelo momento em que ele for publicado. Ao fazer isso, quando assinamos essa sequência de origem, podemos receber seu valor e carimbo de data/hora.
Console.WriteLine(“Current Time: “ + DateTime.Now);
var source = Observable.Timer(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1))
.Timestamp();
using (source.Subscribe(x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey();
A saída será semelhante a:
Current Time: 5/31/2011 5:35:08 PM
Press any key to unsubscribe
0: 5/31/2011 5:35:13 PM -07:00
1: 5/31/2011 5:35:14 PM -07:00
2: 5/31/2011 5:35:15 PM -07:00
Usando o operador Timestamp, verificamos que o primeiro item é realmente enviado por push 5 segundos após a sequência e cada item é publicado 1 segundo depois.
Além disso, você também pode definir pontos de interrupção dentro de expressões lambda para ajudar na depuração. Normalmente, você só pode definir um ponto de interrupção para toda a consulta sem distribuir um valor específico para procurá-la. Para solucionar essa limitação, você pode inserir o operador Select no meio da consulta e definir um ponto de interrupção e, na instrução Select, projetar o valor idêntico como sua origem usando uma instrução return em sua própria linha. Em seguida, você pode definir um ponto de interrupção na linha de return
instrução e examinar os valores à medida que eles percorrem a consulta.
var seq = Observable.Interval(TimeSpan.FromSeconds(1))
.Do(x => Console.WriteLine(x.ToString()))
.Buffer(5)
.Select(y => {
return y; }) // set a breakpoint at this line
.Do(x => Console.WriteLine("buffer is full"))
.Subscribe(x => Console.WriteLine("Sum of the buffer is " + x.Sum()));
Console.ReadKey();
Neste exemplo, o ponto de interrupção é definido na return y
linha . Quando você depura no programa, a y
variável aparece na janela Locais e você pode examinar sua contagem (5)
total . Se você expandir y
, também poderá examinar cada item na lista, incluindo seu valor e tipo.
Como alternativa, você pode converter uma expressão lambda em uma expressão lambda de instrução, formatar código para que uma instrução esteja em sua própria linha e, em seguida, definir um ponto de interrupção.
Você pode remover as chamadas Fazer e Selecionar depois de concluir a depuração.
Consulte Também
Conceitos
Criando e assinando sequências observáveis simples
Consultando sequências observáveis usando operadores LINQ
Usando agendadores