NUnit-sprzątanie po niepowodzeniu testu

Mamy kilka testów NUnit, które mają dostęp do bazy danych. Gdy jeden z nich zawiedzie, może pozostawić bazę danych w stanie niespójnym - co nie jest problemem, ponieważ odbudowujemy bazę danych dla każdego uruchomienia testowego - ale może spowodować, że inne testy zawiodą w tym samym uruchomieniu.

Czy można wykryć, że jeden z testów nie powiódł się i przeprowadzić jakieś oczyszczenie?

Nie chcemy pisać kodu do czyszczenia w każdym teście, już to robimy. Chciałbym perfromować oczyszczenie w Łezce, ale tylko jeśli test się nie powiedzie, ponieważ sprzątanie może być kosztowne.

Update: dla wyjaśnienia-chciałbym, aby testy były proste i nie zawierały logiki czyszczenia ani obsługi błędów. Nie chcę też resetować bazy danych przy każdym uruchomieniu testowym - tylko jeśli test się nie powiedzie. I ten kod powinien być prawdopodobnie wykonany metodą Teardown, ale nie znam żadnego sposobu, aby uzyskać informacje, czy test, który obecnie zrywamy, nie powiódł się lub zakończył się sukcesem.

Update2 :

        [Test]
        public void MyFailTest()
        {
            throw new InvalidOperationException();
        }

        [Test]
        public void MySuccessTest()
        {
            Assert.That(true, Is.True);
        }

        [TearDown]
        public void CleanUpOnError()
        {
            if (HasLastTestFailed()) CleanUpDatabase();
        }

Szukam do implementacji HasLastTestFailed ()

Author: bh213, 2009-07-15

11 answers

Ten pomysł mnie zainteresował, więc trochę poszperałem. NUnit nie ma tej zdolności po wyjęciu z pudełka, ale istnieje cała struktura rozszerzalności dostarczana z NUnit. Znalazłem świetny artykuł o rozbudowie NUnit - to był dobry punkt wyjścia. Po zabawie z nim, wymyśliłem następujące rozwiązanie: metoda ozdobiona niestandardowym atrybutem CleanupOnError zostanie wywołana, jeśli jeden z testów w urządzeniu nie powiedzie się.

Oto jak wygląda test like:

  [TestFixture]
  public class NUnitAddinTest
  {
    [CleanupOnError]
    public static void CleanupOnError()
    {
      Console.WriteLine("There was an error, cleaning up...");
      // perform cleanup logic
    }

    [Test]
    public void Test1_this_test_passes()
    {
      Console.WriteLine("Hello from Test1");
    }

    [Test]
    public void Test2_this_test_fails()
    {
      throw new Exception("Test2 failed");
    }

    [Test]
    public void Test3_this_test_passes()
    {
      Console.WriteLine("Hello from Test3");
    }
  }

Gdzie atrybutem jest po prostu:

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  public sealed class CleanupOnErrorAttribute : Attribute
  {
  }

A oto jak to jest wykonywane z admina:

public void RunFinished(TestResult result)
{
  if (result.IsFailure)
  {
    if (_CurrentFixture != null)
    {
      MethodInfo[] methods = Reflect.GetMethodsWithAttribute(_CurrentFixture.FixtureType,
                                                             CleanupAttributeFullName, false);
      if (methods == null || methods.Length == 0)
      {
        return;
      }

      Reflect.InvokeMethod(methods[0], _CurrentFixture);
    }
  }
}

Ale tutaj jest najtrudniejsza część: dodatek musi być umieszczony w katalogu addins obok NUnit runnera. Mój został umieszczony obok NUnit runner w TestDriven.NET katalog:

C:\Program Files\TestDriven.NET 2.0\NUnit\addins

(stworzyłem katalog addins, nie było go tam)

EDIT inną rzeczą jest to, że metoda czyszczenia musi być static!

Zhakowałem prosty dodatek, możesz pobrać źródło z mój SkyDrive. Będziesz musiał dodać referencje do nunit.framework.dll, nunit.core.dll i nunit.core.interfaces.dll w odpowiednich miejscach.

Kilka uwag: Klasa atrybutów może być umieszczona w dowolnym miejscu kodu. Nie chciałem umieszczać go w tym samym zestawie co sam dodatek, ponieważ odwołuje się do dwóch zestawów Core NUnit, więc umieściłem go w innym zestawie. Pamiętaj tylko, aby zmienić linię w CleanAddin.cs, jeśli zdecyduj się umieścić go gdzie indziej.

Mam nadzieję, że to pomoże.
 20
Author: Igal Tabachnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 23:55:38

Od wersji 2.5.7, NUnit pozwala Tearddown wykryć, czy ostatni test nie powiódł się. Nowa klasa TestContext umożliwia testom dostęp do informacji o sobie łącznie z Teststautami.

Aby uzyskać więcej informacji, zapoznaj się z http://nunit.org/?p=releaseNotes&r=2.5.7

[TearDown]
public void TearDown()
{
    if (TestContext.CurrentContext.Result.Status == TestStatus.Failed)
    {
        PerformCleanUpFromTest();
    }
}
 69
Author: Ran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 21:26:42

Chociaż możliwe jest zmuszenie do tego nUnit, nie jest to najbardziej rozsądny projekt, zawsze możesz ustawić gdzieś plik tymczasowy i jeśli ten plik istnieje, Uruchom oczyszczanie.

Zalecałbym zmianę kodu tak, aby włączyć transakcje bazodanowe i po zakończeniu testu po prostu przywrócić bazę danych do stanu pierwotnego (np. odrzucić transakcję, która reprezentuje twoje testy jednostkowe).

 2
Author: Ray Hayes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 14:45:14

Tak, jest. Możesz użyć teardown atrybut , który będzie teardown po każdym teście. Chciałbyś zastosować ten skrypt "reset" bazy danych, który masz i oddzielić i ponownie skonfigurować przed i po każdym teście.

Ten atrybut jest używany wewnątrz TestFixture, aby zapewnić wspólny zestaw funkcje, które są wykonywane po każda metoda badania jest uruchamiana.

Update : na podstawie komentarzy i aktualizacji do pytania, powiedziałbym, że można użyć atrybut teardown i użyj zmiennych prywatnych, aby wskazać, czy zawartość metody powinna zostać uruchomiona.

Chociaż zauważyłem również, że nie chcesz żadnej skomplikowanej logiki lub kodu obsługi błędów.

Biorąc to pod uwagę, myślę, że standardowa konfiguracja / Teardown będzie dla Ciebie najlepsza. Nie ma znaczenia, czy jest błąd i nie musisz mieć żadnego kodu obsługi błędów.

Jeśli potrzebujesz specjalnego czyszczenia, ponieważ kolejne testy zależą od pomyślnego ukończenia obecny test, sugerowałbym ponowne sprawdzenie Twoich testów. prawdopodobnie nie powinny od siebie zależeć.

 2
Author: Frank V,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 14:49:58

A co powiesz na użycie bloku Try-Catch, ponowne rozważenie złapanego wyjątku?

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}
 1
Author: Dan McClain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 14:33:43

Chciałbym zrobić jak phsr sugeruje teraz i kiedy można sobie na to pozwolić, refaktor testów tak, że nigdy nie muszą polegać na tych samych danych, że inny test potrzebuje lub nawet lepiej abstrakcyjne warstwy dostępu do danych i wyśmiewają wyniki pochodzące z tej bazy danych. Wygląda na to, że twoje testy są dość drogie i powinieneś wykonać całą logikę zapytań w bazie danych i logikę biznesową w swoim zestawieniu nie obchodzi cię, jakie są wyniki, które są zwracane.

Będziesz także mógł przetestuj swoją wyjątkowość o wiele lepiej.

 1
Author: AutomatedTester,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 14:49:41

Inną opcją jest posiadanie specjalnej funkcji, która wyrzuci wyjątki, która ustawia przełącznik w ustawieniu testowym, który mówi o wystąpieniu wyjątku.

public abstract class CleanOnErrorFixture
{
     protected bool threwException = false;

     protected void ThrowException(Exception someException)
     {
         threwException = true;
         throw someException;
     }

     protected bool HasTestFailed()
     {
          if(threwException)
          {
               threwException = false; //So that this is reset after each teardown
               return true;
          }
          return false;
     }
}

Następnie użyj swojego przykładu:

[TestFixture]
public class SomeFixture : CleanOnErrorFixture
{
    [Test]
    public void MyFailTest()
    {
        ThrowException(new InvalidOperationException());
    }

    [Test]
    public void MySuccessTest()
    {
        Assert.That(true, Is.True);
    }

    [TearDown]
    public void CleanUpOnError()
    {
        if (HasLastTestFailed()) CleanUpDatabase();
    }
}

Jedynym problemem jest to, że ślad stosu doprowadzi do CleanOnErrorFixture

 1
Author: Dan McClain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 15:00:18

Jedną z opcji, o której dotąd nie wspomniano, jest zawinięcie testu w obiekt TransactionScope, więc nie ma znaczenia, co się stanie, ponieważ test nigdy niczego nie zobowiązuje do DB.

Oto kilka szczegółów na temat techniki . Prawdopodobnie możesz znaleźć więcej, jeśli wykonasz wyszukiwanie w testach jednostkowych i transactionscope(chociaż naprawdę robisz testy integracyjne, jeśli trafisz na DB). Używałem go z powodzeniem w przeszłości.

Takie podejście jest proste, nie wymaga żadnego czyszczenia i zapewnia te testy są izolowane.

Edit-właśnie zauważyłem, że odpowiedź Raya Hayesa jest również podobna do mojej.

 1
Author: RichardOD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 15:18:17

Jak to się nie udaje? Czy można umieścić go w try ( do test) / catch (fix broken db) / finally block?

LUB możesz wywołać prywatną metodę, aby ją naprawić, gdy sprawdzisz swój stan fail.

 0
Author: NikolaiDante,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-15 14:35:20

Nie mówię, że to świetny pomysł, ale powinien zadziałać. Pamiętaj, że awarie twierdzeń są tylko wyjątkami. Nie zapominaj również, że istnieje również atrybut [TestFixtureTearDown], który uruchamia się tylko raz po uruchomieniu wszystkich testów w urządzeniu.

Używając tych dwóch faktów można napisać coś w rodzaju ustawienia flagi, jeśli testy nie powiodły się i sprawdzenia wartości flagi w urządzeniu testowym.

Nie polecam tego, ale to by zadziałało. Nie używasz NUnit jako zamierzone, ale możesz to zrobić.

[TestFixture]
public class Tests {
     private bool testsFailed = false;

     [Test]
     public void ATest() {
         try {
             DoSomething();
             Assert.AreEqual(....);
         } catch {
            testFailed = true;
         }
     }

     [TestFixtureTearDown]
     public void CleanUp() {
          if (testsFailed) {
              DoCleanup();
          }
     }
}
 0
Author: Mike Two,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-16 00:01:42

Możesz dodać metodę [TearDown] z
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
jakiś kod do wykonania w przypadku niepowodzenia testu.

 0
Author: Andrey Zakharov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-09 17:02:38