FitNesse


FitNesse to framework służący do przygotowywania, przeprowadzania i zarządzania testami akceptacyjnymi. Działa on jako strona www typu wiki. Na stronie www określamy co ma być testowane - co chcemy podawać na wejściu aplikacji, a czego oczekujemy na wyjściu. Dla każdej strony trzeba napisać klasę, która będzie łączyć test zdefiniowany na stronie z kodem testowanej aplikacji.
FitNesse można pobrać ze strony http://www.fitnesse.org, na stronie tej znajduje się również obszerna dokumentacja pozwalająca opanować zarówno sposób konstruowania testów akceptacyjnych, jak i poznać znaczenie tych testów w procesie wytwarzania oprogramowania.
Na zajęciach z przedmiotu Inzynieria Oprogramowania odbylo się laboratorium mające na celu zaprezentowanie sposobu konstruowania testów akceptacyjnych w FitNesse. Instrukcja do tego laboratorium znajduje się tu.

Testowanie aplikacji internetowych


Testowanie zależności czasowych

Proste rozwiązanie
TimedActionFixture umożliwia wyświetlanie informacji o godzinie wywołania i długości trwania dla każdej z testowanych metod. Nie umożliwia to przetestowania zależności czasowych, a jedynie przygotowanie raportu z informacjami na ich temat. Przykład zastosowania:
!|fit.TimedActionFixture|
|start|fixtures.TimeTestFixture|
|press|randomDelay|
|press|randomDelay| public class TimeTestFixture extends Fixture
{

    public void randomDelay()
    {
        try {
            Thread.sleep((long) (10000*Math.random()));
        } catch (InterruptedException ex) {
            //Logger.getLogger(TimeTestFixture.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
fit.TimedActionFixture time split
start fixtures.TimeTestFixture 04:19:17  
press randomDelay 04:19:17 9.062
press randomDelay 04:19:26 6.781

Zaawansowane rozwiązanie
Aby zwiększyć widoczność faktu, że test służy do testowania zależności czasowych należy zaimplementować w klasie fixture następujący interfejs:
/**
 * Interfejs majacy za zadanie ustandaryzowanie testowania zdarzen czasowych.
 * Wszystkie testy FitNessa testujace zdarzenia czasowe powinny go implementowac.
 */
public interface TestResponseTime
{

    /** Uaktywnia zdarzenie czasowe
     * @param maxDuration Gorna granica czasu trwania zdarzenia czasowego.
     */
    public void startTimeEvent(double maxDuration);

    /** Sprawdza, czy czas trwania zdarzenia czasowego byl akceptowalny. */
    public boolean timeEventDuration();
}

Poniżej przedstawiony jest przykład testowania zależności czasowych z wykorzystaniem interfejsu TestResponseTime:
!|fit.TimedActionFixture|
|start|fixtures.TestResponseTimeExampleFixture|
|enter|startTimeEvent|10000|
|enter|maxExpectedTime|6000|
|check|timeEventDuration|true|
|enter|startTimeEvent|10000|
|enter|maxExpectedTime|3000|
|check|timeEventDuration|false|
|enter|startTimeEvent|4000|
|enter|maxExpectedTime|3000|
|check|timeEventDuration|false| import fit.Fixture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *Przykladowa klasa testujaca zdarzenia czasowe.
 */
public class TestResponseTimeExampleFixture extends Fixture implements TestResponseTime
{  
    public void startTimeEvent(double maxDuration)
    {
        mMaxTime = maxDuration;
        mStartThread = false;
        mExec = Executors.newSingleThreadExecutor();
        mExec.execute( new Runnable() { //testowana metoda jest wykonywana w osobnym watku
            public void run()
            {
                synchronized(mTK)
                {
                    while( mStartThread == false )
                    {
                        try {
                            mTK.wait();
                        } catch (InterruptedException ex) {
                            Logger.getLogger(TestResponseTimeExampleFixture.class.getName()).log(Level.SEVERE, null, ex);
                        }   //testowana metoda zostanie uruchomiona dopiero po wywołaniu timeEventDuration()
                    }       //czyli gdy mStartedThread != false
                    mTK.veryComplicatedMethod();
                    mTK.notifyAll();
                }
            }
        } );
        mExec.shutdown();
    }   
   
    public boolean timeEventDuration()
    {
        long start=0, end=0;
        boolean res=true;
        synchronized(mTK) 
        {          
            mStartThread = true; 
            start = System.currentTimeMillis(); //czas wystartowania testowanej metody
            mTK.notifyAll();
        }//dzieki tej synchronizacji mamy kontrole nad czasem wystartowania testowanej metody
        try {
            //czekamy na zakonczenie testowanej metody, ale nie dluzej niz mMaxTime
            res = mExec.awaitTermination( (long)mMaxTime, TimeUnit.MILLISECONDS );               
        } catch (InterruptedException ex) {
            throw new RuntimeException("Obliczenia zostaly przerwane!");
        }
        end = System.currentTimeMillis(); //czas zakonczenia testowanej metody
        if( res==false ) //jezeli testowana metoda nie zdazyla sie zakonczyc, to...
        {
            mExec.shutdownNow();
            return false;
        }       
        if( mMaxExpectedTime > end-start ) //sprawdzenie, czy metoda byla wystarczajaco szybka
            return true;
        else
            return false;
    }
   
    /** Metoda ustawia czas, którego przekroczenie spowoduje niezaliczenie testu. */
    public void maxExpectedTime( long t )
    {
        mMaxExpectedTime = t;
    }
   
    private boolean mStartThread;
   
    /** Czas w którym trzeba sie zmiescic, aby zaliczyc test. */
    private long mMaxExpectedTime;
   
    /** Czas po jakim zaostanie zakonczona testowana metoda (chyba, ze sama zakonczy sie szybciej) */
    private double mMaxTime;
   
    private ExecutorService mExec;
   
    /** Metoda veryComplicatedMethod, z tej wlasnie klasy, jest testowana pod katem czasu wykonywania sie. */
    private TestowanaKlasa mTK = new TestowanaKlasa();
}

class TestowanaKlasa
{
    void veryComplicatedMethod()
    {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            Logger.getLogger(TestowanaKlasa.class.getName()).log(Level.INFO, "Metoda zostala przerwana!");
        }
    }
}
fit.TimedActionFixture time split
start fixtures.TestResponseTimeExampleFixture 04:45:05  
enter startTimeEvent 10000 04:45:05  
enter maxExpectedTime 6000 04:45:05  
check timeEventDuration true 04:45:05 5.0
enter startTimeEvent 10000 04:45:10  
enter maxExpectedTime 4000 04:45:10  
check timeEventDuration true expected
false actual
04:45:10 5.0
enter startTimeEvent 4000 04:45:15  
enter maxExpectedTime 3000 04:45:15  
check timeEventDuration false 04:45:15 4.016