Použití Debug.Assert() v unit test projektech

Metoda Debug.Assert() slouží k vyhodnocení podmínky (pokud je nastaven conditional DEBUG) a v případě hodnoty false zapíše informaci do logu a zobrazí dialog s popisem chyby a kompletním stackem. Zobrazení dialogu lze nastavit v konfiguračním souboru v elementu assert atributem assertuienabled. Zobrazení dialogu ovšem není příliš výhodné v unit testech, kde bychom spíše očekávali, že bude v takovém případě vyvolána nějaká vyjímka a daný test skončí neůspěchem. Jak to ale vypadá v praxi ? Vytvoříme si unit test na následující jenoduchou třídu:

[code:c#]

public class DemoClass
{ 

  public string SomeMethod(string param1)
  {
    Debug.Assert(param1 != null, "Parameter param1 is null");
    return param1;
  }
}

[/code]

Test bude velmi jednoduchý:

[code:c#]

[TestMethod]
[Description("SomeMethod null input parameter test")]
public void TestMethod1()
{
  DemoClass d = new DemoClass();
  d.SomeMethod(null);
}

[/code]

Po spustění testu se zobrazí Assertion Failed dialog, bohužel se ani nepřepne jeho focus na popředí, takže si jej všimneme jen na taskbaru:

 Dialog nabízí tři tlačítka s možnostmi Abort / Retry / Ignore. Pokud zvolíme Retry, dopadne to následovně:

Tohle opravdu není použitelné chování, zvláště v případě kdy spouštíme unit testy command-line hostem MSTest.exe, třeba v rámci nějaké úlohy pomocí CruiseControl.NET.

 

Jak se zbavit onoho dialogu a nahradit jej vlastní vyjímkou ?

Za zobrazení dialogu je zodpovědný výchozí trace listener DefaultTraceListener, kde je v případě assertion volána jeho metoda Fail() která dle nastavení v konfiguraci případně zobrazí daný dialog. Abychom toto chování změnili na vyvolání nějaké vyjímky, je třeba nahradit tento trace listener odvozeným listenerem, kde bude přepsána metoda Fail(). Vzhledem k tomu, že metoda Fail(String) nakonec volá Fail(String, String) stačí přepsat pouze tuto druhou variantu. Odvozená třída UnitTestAssertTraceListener může vypadat takto, vyvolá vyjímku AssertionException

[code:c#]

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace PetrVones.Samples.UnitTestTools
{
  /// <summary>
  /// Exception thrown for Debug.Assert
  /// </summary>
  public class AssertionException : SystemException
  {
    public AssertionException(string message)
      : base(message)
    {
    }
  }

  /// <summary>
  /// Modified DefaultTraceListener throwing exception on Debug.Assert instead of displaying a dialog
  /// </summary>
  public class UnitTestAssertTraceListener : DefaultTraceListener
  {
    public UnitTestAssertTraceListener()
    {
      AssertUiEnabled = false;
    }

    public override void Fail(string message, string detailMessage)
    {
      throw new AssertionException(String.Format("{0} {1}", message, detailMessage));
    }

    /// <summary>
    /// Removes existing DefaultTraceListener and adds new UnitTestAssertTraceListener
    /// </summary>
    public static void Initialize()
    {
      TraceListener defaultListener = Debug.Listeners.OfType<DefaultTraceListener>().FirstOrDefault(item => item.GetType() == typeof(DefaultTraceListener));
      if (defaultListener != null)
        Debug.Listeners.Remove(defaultListener);
      if (!Debug.Listeners.OfType<UnitTestAssertTraceListener>().Any())
        Debug.Listeners.Add(new UnitTestAssertTraceListener());
    }
  }
}

[/code]

 

Nahrazení původního DefaultTraceListener

Tento UnitTestAssertTraceListener je třeba vložit do seznamu listenerů Debug.Listeners (nahradit jím původní DefaultTraceListener). To lze provést dvěma způsoby:

1. v kódu voláním metody UnitTestAssertTraceListener.Initialize() v rámci inicializace unit test třídy. V případě více unit test tříd v jednom Test Projectu by bylo toto nutné volat v každé třídě s unit testy, aby inicializace proběhla před spuštěním prvního testu ze kterékoli třídy obsahující unit testy, tedy označené atributem TestClass.

[code:c#]

[TestClass]
public class DemoClassTest
{
  [ClassInitialize]
  public static void TestClassInitialize(TestContext testContext)
  {
    UnitTestAssertTraceListener.Initialize();
  }

}

[/code]

2. v konfiguračním souboru unit testu

[code:xml]

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <trace>
      <listeners>
        <clear />
        <add name="UnitTest" type="PetrVones.Samples.UnitTestTools.UnitTestAssertTraceListener, PetrVones.Samples.DemoLibrary.UnitTest" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

[/code]

S tímto upraveným listenerem již testy skončí dle očekavání s chybou

Stejně tak pokud testy spustíme command-line hostem MSTest.exe

Microsoft (R) Test Execution Command Line Tool Version 10.0.30319.1
Copyright (c) Microsoft Corporation. All rights reserved.

Loading bin\Debug\PetrVones.Samples.DemoLibrary.UnitTest.dll...
Starting execution...

Results               Top Level Tests
-------               ---------------
Failed                PetrVones.Samples.DemoLibrary.UnitTest.DemoClassTest.TestMethod1
0/1 test(s) Passed, 1 Failed

Summary
-------
Test Run Failed.
  Failed  1
  ---------
  Total   1

 

Kompletní příklad ke stažení pro Visual Studio 2010 zde: DemoLibraryUnitTest.zip (7,47 kb)

Komentáře jsou uzavřeny