Category Archives: Uncategorized

EventWaiter – testowanie zdarzeń

Dzisiaj chciałbym zaprezentować klasę EventWaiter. Znalazłem ją w repozytorium Roslyn na GitHub i w niektórych testach okazała się przydatna. Załóżmy, że mamy klasę eksponującą jakieś zdarzenie:

    class Sut
    {
        public event EventHandler Click;

        public void RaiseClickEvent()
        {
            Click?.Invoke(this,EventArgs.Empty);
        }
    }

Test zdarzenia mógłby wyglądać następująco:

        [Test]
        [Timeout(2000)]
        public void Test1()
        {
            // arrange
            var manualResetEvent = new ManualResetEvent(false);
            var sut = new Sut();

            sut.Click += (s, e) => { manualResetEvent.Set(); };

            // act
            sut.RaiseClickEvent();

            // assert
            manualResetEvent.WaitOne();
            Assert.Pass();
        }

Źródło klasy EventWaiter można znaleźć np. tutaj:
https://github.com/dotnet/roslyn/blob/master/src/Test/Utilities/Shared/FX/EventWaiter.cs

Kod:

  public sealed class EventWaiter : IDisposable
    {
        private readonly ManualResetEvent _eventSignal = new ManualResetEvent(false);
        private Exception _capturedException;

        ///
<summary>
        /// Returns the lambda given with method calls to this class inserted of the form:
        /// 
        /// try
        ///     execute given lambda.
        ///     
        /// catch
        ///     capture exception.
        ///     
        /// finally
        ///     signal async operation has completed.
        /// </summary>

        /// <typeparam name="T">Type of delegate to return.</typeparam>
        /// <param name="input">lambda or delegate expression.</param>
        /// <returns>The lambda given with method calls to this class inserted.</returns>
        public EventHandler<T> Wrap<T>(EventHandler<T> input)
        {
            return (sender, args) =>
            {
                try
                {
                    input(sender, args);
                }
                catch (Exception ex)
                {
                    _capturedException = ex;
                }
                finally
                {
                    _eventSignal.Set();
                }
            };
        }

        ///
<summary>
        /// Use this method to block the test until the operation enclosed in the Wrap method completes
        /// </summary>

        /// <param name="timeout"></param>
        /// <returns></returns>
        public bool WaitForEventToFire(TimeSpan timeout)
        {
            var result = _eventSignal.WaitOne(timeout);
            _eventSignal.Reset();
            return result;
        }

        ///
<summary>
        /// Use this method to block the test until the operation enclosed in the Wrap method completes
        /// </summary>

        /// <param name="timeout"></param>
        /// <returns></returns>
        public void WaitForEventToFire()
        {
            _eventSignal.WaitOne();
            _eventSignal.Reset();
            return;
        }

        ///
<summary>
        /// IDisposable Implementation.  Note that this is where we throw our captured exceptions.
        /// </summary>

        public void Dispose()
        {
            _eventSignal.Dispose();
            if (_capturedException != null)
            {
                throw _capturedException;
            }
        }
    }

Dzięki temu wrapperowi, nasz kod może wyglądać teraz tak:


        [Test]
        [Timeout(2000)]
        public void Test1a()
        {
            // arrange
            var waiter=new EventWaiter();            
            var sut = new Sut();
            sut.Click += waiter.Wrap<EventArgs>((sender, args) => { Assert.Pass()});

            // act
            sut.RaiseClickEvent();

            // assert
            waiter.WaitForEventToFire();
        }

Powyższy przykład jest prosty. W praktyce musimy wciąć pod uwagę różne scenariusze – np. gdy wyjątek jest wyrzucany albo musimy sprawdzić konkretne parametry zdarzenia. W takich sytuacjach, powyższy wrapper jest przydatny.

Aktualziacja statystyk w SQL Server

O Sql Statistics pisałem już tutaj.
Wiemy, że dzięki takim statystykom dobierane są indeksy oraz plany wykonania. Na przykład jeśli zagęszczenie danych jest bardzo niskie, wtedy nawet warto zrezygnować z indeksów.

Utrzymanie statystyk nie jest łatwym zadaniem. Generalnie SQL Server jest odpowiedzialny za ich przeliczanie. Oczywiście przeliczanie od nowa statystyk za każdym razem, gdy dane są zmieniane, byłoby zbyt czasochłonne. Z tego wynika fakt, że istnieje ryzyko, że statystyki nie odzwierciedlają dokładnie danych.  SQL Server aktualizuje je w następujący sposób:

  1. Gdy nie ma żadnych danych, wtedy przeliczane są one w momencie dodania pierwszego wiersza.
  2.  Gdy jest mniej niż 500 wierszy, wtedy przeliczane są one w momencie osiągnięcia progu 500.
  3.  Gdy jest więcej niż 500 wierszy, każda zmiana 20% powoduje ponowne przeliczenie.

Jeśli mamy kilka milionów danych, przy odrobinie pecha, może zdarzyć się, że mniej niż 20% danych zostanie zmodyfikowanych, ale w taki sposób, że zaburzają one aktualnie wyliczoną dystrybucję danych.

W dowolnym momencie możemy sprawdzić, kiedy ostatnio statystyki były przeliczanie. Wystarczy użyć następującej komendy:

DBCC SHOW_STATISTICS('dbo.Articles',PRICE_INDEX)

W wyniku dostaniemy m.in. kolumnę Updated:

Można również ręcznie zaktualizować statystyki za pomocą Update Statistics:

UPDATE STATISTICS Sales.SalesOrderDetail

Możliwe jest również przeliczenie wszystkich statystyk w bazie:

EXEC sp_updatestats

Ostatnio pisałem o fragmentacji indeksów oraz potrzebie ich przebudowania. Dobrą wiadomością jest fakt, że gdy przebudowujemy indeks, również statystyki są przeliczanie. Nie ma zatem potrzeby wykonywania tych dwóch operacji jednocześnie.

Niestety nie ma łatwego sposobu na sprawdzenie, czy należy przebudować statystyki. Zwykle nie ma takiej potrzeby, ale czasami dane są modyfikowane w tabelach w taki sposób, że potrzebna jest ingerencja. Objawia się to wtedy źle dobranymi planami wykonania.