2015-11-09 10 views
5

Sono dipendente dall'hardware che potrebbe rispondere o meno. Di conseguenza, finisco spesso per scrivere funzioni con timeout. Il tempo di sistema è una fonte nota per i test di unità fragili, quindi iniettare un tempo controllato e stabile sembra una buona idea per il test.Ci sono strutture in std :: chrono per assistere con l'iniezione system_clock per il test dell'unità

Mi chiedo se ci sono strutture in std :: chrono che aiutano in questo. L'alternativa che vedo è scrivere un wrapper attorno al tempo di sistema e dipendere da quell'adattatore.

Ecco un esempio minimo di come potrebbe apparire un wrapper.

#pragma once 
#include <memory> 
#include <chrono> 
#include <thread> 
#include <iostream> 

using std::chrono::system_clock; 
using std::chrono::milliseconds; 
using std::shared_ptr; 
using std::make_shared; 

class Wrapped_Clock 
{ 
public: 
    virtual system_clock::time_point Now() { return system_clock::now(); } 
    virtual void Sleep(milliseconds ms) { std::this_thread::sleep_for(ms); } 
}; 

class Mock_Clock : public Wrapped_Clock 
{ 
private: 
    system_clock::time_point now; 
public: 
    Mock_Clock() : now(system_clock::now()){} 
    ~Mock_Clock() {} 
    system_clock::time_point Now() { return now; } 
    void Sleep(milliseconds ms) { } 
}; 

class CanTimeOut 
{ 
private: 
    shared_ptr<Wrapped_Clock> sclock; 
public: 
    CanTimeOut(shared_ptr<Wrapped_Clock> sclock = make_shared<Wrapped_Clock>()) : sclock(sclock) {} 
    ~CanTimeOut() {} 

    milliseconds TimeoutAction(milliseconds maxtime) 
    { 
     using std::chrono::duration_cast; 
     int x = 0; 
     system_clock::time_point start = sclock->Now(); 
     system_clock::time_point timeout = sclock->Now() + maxtime; 
     while (timeout > sclock->Now() && x != 2000) 
     { 
      sclock->Sleep(milliseconds(1)); 
      ++x; 
     } 
     milliseconds elapsed = duration_cast<milliseconds>(sclock->Now() - start); 
     return elapsed; 
    } 

}; 

#define EXPECT_GE(left, right, test) \ 
{ if (!(left >= right)) { \ 
    std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \ 
} } 

#define EXPECT_EQ(expected, actual, test) \ 
{ if (!(expected == actual)) { \ 
    std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \ 
} } 

void TestWithSystemClock() 
{ 
    CanTimeOut cto; 
    long long timeout = 1000; 
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout)); 
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock); 
} 

void TestWithMockClock() 
{ 
    CanTimeOut cto(make_shared<Mock_Clock>()); 
    milliseconds actual = cto.TimeoutAction(milliseconds(1000)); 
    EXPECT_EQ(0, actual.count(), TestWithMockClock); 
} 

int main() 
{ 
    TestWithSystemClock(); 
    TestWithMockClock(); 
} 

Quanto di questo può essere sostituito con funzionalità da std :: chrone?

Edit 1:

  • "Che cosa stai testando?" Sto controllando il tempo come condizione di test per modificare il comportamento delle chiamate di metodo che dipendono dal tempo. Il Test illustra come prendersi gioco del tempo e controllare il comportamento come un concetto funziona e mostra la mia comprensione di esso. Il punto dell'esempio minimo è mostrare la mia comprensione del tempo di derisione per rendere più facile mostrare le differenze alle strutture std::.
  • "spendi ~ 10 parole dicendo che cosa devono essere contrastati i test." L'unico test scade sempre. L'altro test non mostra passaggi di tempo. Un terzo test che controlla un passaggio di tempo esatto e non zero non è stato incluso.
  • "Inoltre, il sonno non ha nulla a che fare con l'orologio, non è un crono." Ne avevo bisogno per garantire che l'unico test non effettuasse mai più cicli di una certa quantità prima del timeout, questo simula un'azione che richiede tempo e può scadere. D'altra parte volevo costruire in una scorciatoia così il secondo test non fa perdere tempo ad aspettare. Sarebbe bene non prendere in giro anche Sleep, ma il test richiederebbe 2 secondi. Riconosco il punto che Sleep non è una caratteristica di cronografo e quindi fuorviante.
+0

Che cosa stai testando? Le condizioni sono quasi incomprensibili per me. Forse potresti spendere ~ 10 parole per dire che cosa dovrebbero contraddire i test. Inoltre, 'dormire' non ha niente a che fare con l'orologio. Non è una funzione 'chrono' – sehe

+0

Nota che se usi parti mobili (come un timer), in realtà non è più un test unitario. – erip

+0

@erip sarebbe un test di unità se il timer è deriso? Puoi condividere un link con una fonte che sottolinea perché è così? – Johannes

risposta

5

Sembra, invece, che stai prendendo in giro std::this_thread::sleep.

Questo è un po 'più complicato, perché è uno spazio dei nomi con solo funzioni libere. È difficile "iniettare" uno spazio dei nomi a scopo di test. Quindi, dovresti, in effetti, racchiudere le funzioni da quel namespace con il tuo tipo.

userei l'iniezione di dipendenza statica, à la C++:

Live On Coliru

#include <memory> 
#include <chrono> 
#include <thread> 
#include <iostream> 

using std::chrono::system_clock; 
using std::chrono::milliseconds; 

struct production { 
    using clock = std::chrono::system_clock; 

    struct this_thread { 
     template<typename... A> static auto sleep_for(A&&... a) { return std::this_thread::sleep_for(std::forward<A>(a)...); } 
     template<typename... A> static auto sleep_until(A&&... a) { return std::this_thread::sleep_until(std::forward<A>(a)...); } 
    }; 
}; 

struct mock { 
    struct clock : std::chrono::system_clock { 
     using base_type = std::chrono::system_clock; 
     static time_point now() { static auto onetime = base_type::now(); return onetime; } 
    }; 

    struct this_thread { 
     template<typename... A> static auto sleep_for(A&&... a) {} 
     template<typename... A> static auto sleep_until(A&&... a) {} 
    }; 
}; 

template <typename services = production, 
     typename clock = typename services::clock, 
     typename this_thread = typename services::this_thread> 
class CanTimeOut 
{ 
public: 
    milliseconds TimeoutAction(milliseconds maxtime) 
    { 
     using std::chrono::duration_cast; 

     int x = 0; 
     auto start = clock::now(); 
     auto timeout = clock::now() + maxtime; 
     while (timeout > clock::now() && x != 2000) 
     { 
      this_thread::sleep_for(milliseconds(1)); 
      ++x; 
     } 
     milliseconds elapsed = duration_cast<milliseconds>(clock::now() - start); 
     return elapsed; 
    } 

}; 

#define EXPECT_GE(left, right, test) \ 
{ if (!(left >= right)) { \ 
    std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \ 
} } 

#define EXPECT_EQ(expected, actual, test) \ 
{ if (!(expected == actual)) { \ 
    std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \ 
} } 

void TestWithSystemClock() 
{ 
    CanTimeOut<> cto; 
    long long timeout = 1000; 
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout)); 
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock); 
} 

void TestWithMockClock() 
{ 
    CanTimeOut<mock> cto; 
    milliseconds actual = cto.TimeoutAction(milliseconds(1000)); 
    EXPECT_EQ(0, actual.count(), TestWithMockClock); 
} 

int main() 
{ 
    TestWithSystemClock(); 
    TestWithMockClock(); 
} 
+0

Ho aggiornato la demo perché ho capito che volevi prendere in giro anche 'ora()'. – sehe

+0

Ottengo 4 x 'errore C3551: previsto un tipo di ritorno finale' il mio IDE è VS Express 2013, ... il compilatore è quello che viene fornito con quell'IDE.Le 4 posizioni si trovano nei 4 modelli per 'struct this_thread' – Johannes

+0

Ho aggiornato i tipi di ritorno automatico per annullare, penso che vs2013 abbia un problema con questo. Grazie per aver mostrato questo tipo di iniezione. C'è un buon libro su questo? – Johannes