System-Wrapper für Produktion und Test



  • Ich bin kein großer Freund vom Singleton. Zum einen, weil man Abhängigkeiten nicht mehr von außen sieht und eben wegen der Testbarkeit von abhängigem Code.
    Du schreibst zwar, das du keine Lust hast, überall die Objekte mitzuschleppen, aber genau das (aka Dependencie Injection) würde ich wahrscheinlich machen. Kann ja auch überall das selbe Objekt via Referenz sein, was da rum gereicht wird. Dann gibt es auch nur eine Instanz, nur das die nicht global Verfügbar ist.

    Die, von der Zeit abhängigen, Teile bekommen dann eine Referenz auf AbstractSystemTime. Im Produktionscode schiebst du dann RealSystemTime da rein und im Unittest FakeSystemTime.



  • @Schlangenmensch sagte in System-Wrapper für Produktion und Test:

    Die, von der Zeit abhängigen, Teile bekommen dann eine Referenz auf AbstractSystemTime. Im Produktionscode schiebst du dann RealSystemTime da rein und im Unittest FakeSystemTime.

    Das wäre auch aus meiner Sicht die "sauberste" Lösung, aber es sind eben recht viele einzelne Klasse, die einzeln abgetestet werden und es wäre eben ziemlich nervig überall eine Referenz mitzuschleppen. Daher wollte ich eben prüfen ob es Alternativen gibt. Aber grundsätzlich bin ich bei dir: die Abhängigkeiten sollte immer offensichtlich sein.

    @Th69 sagte in System-Wrapper für Produktion und Test:

    Hast du denn in deinem Projekt nicht eine Konfigurationsklasse o.ä., in welcher du dieses AbstractSystemTime-Objekt unterbringen kannst (selbstverständlich als Zeiger bzw. Smartpointer) und dann passend zuweist.

    Ansonsten eine globale Variable in einem eigenen Namensbereich (oder aber alternativ als static in der AbstractSystemTime-Klasse).

    Ein Singleton funktioniert ja direkt nicht, da dieses ja immer über den Klassennamen angesprochen wird.

    Eine Konfigurationsklasse habe ich schon. Nur eben nicht für jede Klasse. Manche Klasse sind so klein, dass sie dann eben nur eine Eigenschaft haben, so dass eine PropertiesKlasse dort totaler Overhead wäre.

    Ich probiere mal den Vorschlag von @firefly.



  • Hier mal der relevanten parts wie das in Qt implementiert ist adaptiert an AbstractSystemTime

    Quelle:
    https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/kernel/qcoreapplication.h
    https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/kernel/qcoreapplication.cpp

    Header

    class AbstractSystemTime
    {
    public:
      AbstractSystemTime();
      ~AbstractSystemTime();
      static AbstractSystemTime* instance(){return self;}
      
    private:
      static AbstractSystemTime *self;
    };
    

    cpp

    AbstractSystemTime* AbstractSystemTime::self = nullptr;
    AbstractSystemTime::AbstractSystemTime()
    {
      assert(!self, "there should only one SystemTime object")
      self = this;
    }
    AbstractSystemTime::~AbstractSystemTime()
    {
      self = nullptr;
    }
    


  • @firefly
    Bei einer ähnlichen Variante bin ich jetzt auch rausgekommen. Die FakeSystemTime musste ich entsprechend dann auch noch "NonCopyAble" machen. Die Initialisierung der static war mir noch nicht ganz klar. Danke dafür 🙂

    Morgen teste ich das mal intensiv und berichte dann noch, wie sich das anfühlt.



  • Sollte es nicht reichen AbstractSystemTime als "NonCopyAble" zu machen?
    Oder soll es auch mal SystemTime varianten geben die klopierbar sein sollen?



  • Nur um noch eine mögliche Alternative in den Raum zu werfen: C++ ist Multi-paradigm. Nicht jedes Problem muss unbedingt mit einer mit einer (polymorphen) Klasse erschlagen werden. FakeSystemTime ist ja schon einiges an Boilerplate um einen einzigen std:uint64_t und auch RealSystemTime kapselt lediglich einen System-Call. Es mag durchaus gute Gründe geben, das in ein Klassenobjekt zu kapseln, aber wenn die eh nur im Singleton-Kontext verwendet wird, dann reicht eventuell auch bereits sowas banales hier:

    namespace
    {
        std::atomic<std::uint64_t> fake_time;
    }
    
    auto get_system_time() -> std::uint64_t;
    auto get_fake_time() -> std::uint64_t;
    void advance_fake_time(std::uint64_t);
    
    auto get_time() -> std::uint64_t
    {
        if (use_fake) // [[unlikely]] (C++20), oder auch 'if constexpr'
            return get_fake_time();
        else
            return get_system_time();
    }
    

    ... nur so als Anregung. Ob das Sinn macht, musst du selbst entscheiden und hängt natürlich stark davon ab, wie die Zeit in deinem Code verwendet wird - z.B. auch ob irgenwelcher (evtl. externer) Code unbedingt solche Zeitobjekte braucht.



  • @Finnegan sagte in System-Wrapper für Produktion und Test:

    ... nur so als Anregung. Ob das Sinn macht, musst du selbst entscheiden und hängt natürlich stark davon ab, wie die Zeit in deinem Code verwendet wird - z.B. auch ob irgenwelcher (evtl. externer) Code unbedingt solche Zeitobjekte braucht.

    An sowas in der Richtung hatte ich auch schon gedacht. Einfach eine Weiche die entweder die reale Zeit liefert oder meine manipulierbare Test-Zeit.



  • @firefly sagte in System-Wrapper für Produktion und Test:

    Sollte es nicht reichen AbstractSystemTime als "NonCopyAble" zu machen?
    Oder soll es auch mal SystemTime varianten geben die klopierbar sein sollen?

    nein, soll es nicht geben. Du hast vollkommen recht.

    Vielen Dank an euch alle für eure guten Vorschläge 🙂



  • Mit dem Vorschlag von @Finnegan hat man die Weiche fest im Produktionscode. Damit kann man vlt. leben, finde ich aber auch nicht so schön 😉
    Über die Ableitung kann man das besser Trennen, der eine Teil im Testcode und der andere in Produktion.



  • @Schlangenmensch sagte in System-Wrapper für Produktion und Test:

    Mit dem Vorschlag von @Finnegan hat man die Weiche fest im Produktionscode. Damit kann man vlt. leben, finde ich aber auch nicht so schön 😉
    Über die Ableitung kann man das besser Trennen, der eine Teil im Testcode und der andere in Produktion.

    Die Weiche ist dann tatsächlich fest im Produktionscode, aber das Risiko ist überschaubar. Ich würde die Weiche dann default auch auf "Produktion" stellen und nur im Test anders.



  • Man könnte das über nen funktionszeiger lösen, wodurch der fake code nicht mehr teil des produktion code wäre.

    Verändern des funktionszeigers wäre dann über eine methode möglich, welche im falle eines nullptrs dann den internen funktionszeiger wieder auf den poduktion version umstellen kann



  • Man kann auch sowas hier machen, wenn man das strikt trennen will:

    get_time.hpp:

    auto get_time() -> std::uint64_t;
    

    get_time_production.cpp:

    auto get_time() -> std::uint64_t
    {
      ... system_time ...
    }
    

    get_time_test.cpp:

    namespace
    {
        std::atomic<std::uint64_t> fake_time;
    }
    
    void advance_fake_time(std::uint64_t ms)
    {
        fake_time.fetch_add(ms);
    }
    
    auto get_time() -> std::uint64_t
    {
        return fake_time.load();
    }
    

    ... und dann für Produktionscode get_time_production.o/.obj und für Test-Code get_time_test.o/.obj linken. Da muss man dann für den Testcode auch nicht die Quellen neu kompileren, die get_time verwenden. Header deklariert get_time und ein nur in Testcode eingebundener anderer Header zusätzlich advance_fake_time (oder man haut die Deklaration direkt in die Test-.cpp).



  • Ich habe derweil ein wenig sinniert über die Sinnhaftigkeit meines uint64_t ... Ist das als Timestamp in der heutigen Zeit von Chrono überhaupt noch sinnvoll? Mit Chrono kann man ja auch Zeitintervalle (std::chrono::duration) sehr gut lesbar ( mit Literalen oder std::chrono::seconds ) im Code verwenden. Würde es da Sinn machen, die komplette Zeitsteuerung in der Applikation ( z.B. Überwachung von TimeOuts ) mit Chrono zu machen? Oder wäre das aus eurer Sicht Overhead?

    Andererseits ist so ein uint64_t natürlich schon einfach und pflegeleicht... Der Nachteil: Wenn man z.B. ein Delay setzen möchte, muss man die im Server verwendete Zeiteinheit kennen. ( also z.B. Millisekunden ). bei Chrono ist sowas eindeutig.

    Man findet irgendwie auf Anhieb nicht sonderlich viel über Chrono im Netz. Die meisten scheinen Chrono für die Laufzeitmessung zu nutzen. now() - now() = duration...



  • Ich kenne die chrono-Implementierungen nicht wirklich im Detail, würde mich aber sehr wundern, wenn das für Zeiträume/Zeitpunkte unter der Haube was großartig anderes als lediglich ein einziger (u)int64_t wäre. Hat halt ein schöneres Interface.

    Ich halte es für unwahrscheinlich, dass es da irgendeinen Overhead gibt. Zumindest solange du damit nicht "mehr" machst, als du es mit dem uint64_t eh schon tust.

    Ich glaube mich hat da immer unterbewusst die Textlänge der voll qualifizierten Typen abgeschreckt. Ich kenne keinen wirklich guten Grund, warum ich selbst chrono auch so selten verwende 😉


Anmelden zum Antworten