Funktionsmocking (zum Testen) in C?

Lesezeit: 6 Minuten

Benutzeravatar von leif
Leif

Ich möchte Tests für eine C-Bibliothek in C schreiben. Ich möchte einige Funktionen für den Test verspotten.

Angenommen, meine Bibliothek wird aus der folgenden Quelle kompiliert:

/* foo.h */
int myfunction(int x, int y);

/* foo.c */
#include "foo.h"

static int square(int x) { return x * x; }

int myfunction(int x, int y) {
    return square(x) + square(y);
}

Ich möchte einen Test wie folgt schreiben:

/* foo_test.c */
#include "foo.h"

static int square(int x) { return x + 1; }

int main(void) {
    assert(myfunction(0, 0) == 2);
    return 0;
}

Kann ich das irgendwie kompilieren? myfunction wird die Definition von verwenden square In foo_test.cstatt der in foo.cnur beim Verknüpfen der ausführbaren Datei foo_test? Das heißt, ich möchte kompilieren foo.c in eine Bibliothek (nennen wir es libfoo.so) und dann kompilieren foo_test.c mit libfoo.so und etwas Magie, damit ich eine ausführbare Datei bekomme foo_test die die unterschiedliche Implementierung von verwendet square.

Es wäre hilfreich, Lösungen für wann zu hören square ist nicht deklariert staticaber die Lösung des obigen Falls wäre noch besser.

EDIT: Es scheint hoffnungslos, aber hier ist eine Idee: Angenommen, ich kompiliere mit -O0 -g das ist also unwahrscheinlich square wird inline und ich sollte Symbole haben, die zeigen, wo der Anruf aufgelöst wurde. Gibt es eine Möglichkeit, sich in die Objektdatei einzuschleichen und die aufgelöste Referenz auszutauschen?

  • LD_PRELOAD kann dir das geben, aber ich hoffe, jemand anderes hat eine bessere Antwort.

    – Sarnold

    22. Januar 2012 um 5:13 Uhr

  • Nicht für statische Funktionen

    – bdonlan

    22. Januar 2012 um 5:14 Uhr

  • Ich denke nicht LD_PRELOAD wird es tun. Ich möchte in der Lage sein, eine Funktion (die möglicherweise statisch ist) in einer bereits kompilierten Bibliothek zu ändern. Wie ich verstehe (und vielleicht ist das falsch), das Symbol square innen myfunction wird bereits gelöst, bevor ich versuche zu verlinken foo_test.

    – Leif

    22. Januar 2012 um 5:15 Uhr

  • Wenn Ihre Funktion statisch ist, lautet die Antwort nein, das können Sie absolut nicht. Statische Funktionen können inliniert und vollständig optimiert werden.

    – nm

    22. Januar 2012 um 6:14 Uhr

  • @nm: Nicht statische Funktionen können auch inliniert und aus der Existenz optimiert werden. Moderne Compiler mit Link-Time-Optimierung werden dies tun, sowohl GCC als auch Clang/LLVM sind Beispiele.

    – Dietrich Ep

    22. Januar 2012 um 7:46 Uhr

Ich hab geschrieben Nachahmeneine Mocking/Stubbing-Bibliothek für C-Funktionen, die sich damit befassen.

Angenommen, dass quadrat weder statisch noch inline ist (weil es sonst an die Kompilierungseinheit und die Funktionen gebunden wird, die es verwenden) und dass Ihre Funktionen in einer gemeinsam genutzten Bibliothek namens “libfoo.so” (oder was auch immer die Namenskonvention Ihrer Plattform ist) kompiliert werden ), das würdest du tun:

#include <stdlib.h>
#include <assert.h>
#include <mimick.h>

/* Define the blueprint of a mock identified by `square_mock`
   that returns an `int` and takes a `int` parameter. */
mmk_mock_define (square_mock, int, int);

static int add_one(int x) { return x + 1; }

int main(void) {
    /* Mock the square function in the foo library using 
       the `square_mock` blueprint. */
    mmk_mock("square@lib:foo", square_mock);

    /* Tell the mock to return x + 1 whatever the given parameter is. */
    mmk_when(square(mmk_any(int)), .then_call = (mmk_fn) add_one);

    /* Alternatively, tell the mock to return 1 if called with 0. */
    mmk_when(square(0), .then_return = &(int) { 1 });

    assert(myfunction(0, 0) == 2);

    mmk_reset(square);
}

Dies ist jedoch eine ausgewachsene spöttische Lösung, und wenn Sie nur stummeln möchten square (und interessieren Sie sich nicht für das Testen von Interaktionen), könnten Sie etwas Ähnliches tun:

#include <stdlib.h>
#include <assert.h>
#include <mimick.h>

static int my_square(int x) { return x + 1; }

int main(void) {
    mmk_stub("square@lib:foo", my_square);

    assert(myfunction(0, 0) == 2);

    mmk_reset(square);
}

Mimick funktioniert, indem es eine gewisse Introspektion der laufenden ausführbaren Datei verwendet und zur Laufzeit die globale Offset-Tabelle vergiftet, um Funktionen auf den Stub unserer Wahl umzuleiten.

Es sieht so aus, als würden Sie GCC verwenden, also können Sie das schwache Attribut verwenden:

Das schwache Attribut bewirkt, dass die Deklaration als schwaches Symbol und nicht als globales ausgegeben wird. Dies ist hauptsächlich nützlich, um Bibliotheksfunktionen zu definieren, die im Benutzercode überschrieben werden können. obwohl es auch mit Nicht-Funktionsdeklarationen verwendet werden kann. Schwache Symbole werden für ELF-Ziele und auch für a.out-Ziele unterstützt, wenn der GNU-Assembler und -Linker verwendet werden.

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

  • Danke. Ich kann es wahrscheinlich von hier aus übernehmen, aber können Sie mir Tipps zum Ändern einer schwachen Referenz geben?

    – Leif

    23. Januar 2012 um 6:56 Uhr

  • Definieren Sie einfach eine Funktion mit demselben Namen, die NICHT schwach ist, dann sollte sie die schwache Funktion in der Bibliothek überschreiben.

    – David Grayson

    23. Januar 2012 um 19:16 Uhr

  • Wenn Sie ein einzelnes Komponententestziel haben, das in einer Datei eine echte Implementierung einer Funktion und dann eine nachgeahmte in einer anderen verwendet, hilft dies überhaupt nicht – Sie würden in beiden Fällen eine nachgeahmte Implementierung erhalten .

    – bartlomiej.n

    10. April 2019 um 11:11 Uhr

Nein, dafür gibt es keine Lösung. Wenn eine Funktion im Gültigkeitsbereich vorhanden ist, deren Name mit einem Funktionsaufruf in einer Quelldatei übereinstimmt, wird diese Funktion verwendet. Kein Deklarationstrick wird den Compiler davon abbringen. Bis der Linker aktiv ist, ist der Namensbezug bereits aufgelöst.

Angesichts dessen square() global deklariert werden kann, können Sie die verwenden Nala Test-Framework, um die Implementierung von zu ersetzen square().

Der gesamte Quellcode, einschließlich eines Makefiles, ist verfügbar Hier.

Die Header-Datei foo.h.

int square(int x);
int myfunction(int x, int y);

Die Quelldatei foo.c.

#include "foo.h"

int square(int x) { return x * x; }

int myfunction(int x, int y) {
    return square(x) + square(y);
}

Die Testdatei test_foo.c, Implementieren des Tests auf zwei alternative Arten; Ersetzen Sie die Implementierung (wie gewünscht) und erwarten Sie die Rückgabe.

#include "foo.h"
#include "nala.h"
#include "nala_mocks.h"

static int my_square(int x) { return x + 1; }

/* Replace the implemenation of square(). */
TEST(implementation)
{
    square_mock_implementation(my_square);

    ASSERT_EQ(myfunction(0, 0), 2);
}

/* Expect square(x=0) and return 1. */
TEST(mock)
{
    square_mock(0, 1);

    ASSERT_EQ(myfunction(0, 0), 2);
}

Sams Benutzeravatar
Sam

In Fällen wie Ihrem verwende ich Typemock Isolator++ API.

Es ermöglicht Ihnen, das ursprüngliche Verhalten der Methode durch Ihre eigene Implementierung zu ersetzen. Aufgrund sprachlicher Besonderheiten sollten Sie jedoch die gleichen Namen für Funktionen unter und für Tests vermeiden.

#include "foo.h"

static int square_test(int x) { return x + 1; }

TEST_CLASS(ArgumentTests)
{
public:
    TEST_METHOD_CLEANUP(TearDown)
    {
        ISOLATOR_CLEANUP();
    }

    TEST_METHOD(TestStaticReplacedStatic)
    {
        PRIVATE_WHEN_CALLED(NULL, square).DoStaticOrGlobalInstead(square_test, NULL);
        Assert::IsTrue(myfunction(0, 0) == 2);
    }
};

Hoffe, es wird für Sie nützlich sein, viel Glück!

  • Die Frage bezieht sich auf C, aber Ihre Antwort bezieht sich auf C++.

    – Simon Kissane

    23. September 2018 um 13:56 Uhr

  • @SimonKissane Eigentlich geht es bei der Antwort um C, aber das Framework ist in C++, also haben Sie teilweise Recht. Das hätte ich erwähnen sollen.

    – Sam

    1. November 2018 um 8:36 Uhr


  • Die Frage bezieht sich auf C, aber Ihre Antwort bezieht sich auf C++.

    – Simon Kissane

    23. September 2018 um 13:56 Uhr

  • @SimonKissane Eigentlich geht es bei der Antwort um C, aber das Framework ist in C++, also haben Sie teilweise Recht. Das hätte ich erwähnen sollen.

    – Sam

    1. November 2018 um 8:36 Uhr


1443770cookie-checkFunktionsmocking (zum Testen) in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy