13

I want to cover my code with unit-tests. That's a good thing. But I have a problem - I have a networking code. The code does resolve IPv4 and IPv6 addresses from hostnames, binds to interfaces, listens, connects, etc.

I assume that there exists some C/C++ test framework that can be deployed on almost any workstation or some programming technique that allows me to:

  • set up and tear down custom network interfaces with IPv4 and IPv6 addresses
  • emulate different jamming behavior, like losing packets, timeouting, connection dropping, etc.
  • bind hostnames to interfaces and resolve them.

The main goal is not to interact or mess with a real network interfaces on machine.


What can you advise?

abyss.7
  • 13,882
  • 11
  • 56
  • 100
  • 3
    (not a duplicate, but this may be helpful?) http://stackoverflow.com/questions/2924440/advice-on-mocking-system-calls – IdeaHat Dec 03 '14 at 18:32
  • Is it me, or are the behaviors you're trying to test outside of the scope of unit-testing, and into integration testing? A pedantic point, I suppose, but could change the scope of answers. Those are two very different topics to most developers. – MobA11y Dec 03 '14 at 18:38
  • @ChrisCM In my opinion it's not an integration testing, since I don't want to make a real deploy, or use more than one machine. Also, I don't want to test the whole product, but only some implementation parts like event loop, network service, streaming. And it should be a single binary, that do all the work. Does it look like integration testing in your opinion? – abyss.7 Dec 03 '14 at 18:46
  • I'm not a network specialist, so there might be methodologies out there to do this via unit testing that I'm not aware of. But it seems to me the type of things you want to test, can only be adequately tested with approaches that I would not qualify under unit testing. Sometimes parts of your program have to be tested as parts of the whole, to be tested adequately. That's why the differentiation between unit-tests and integration tests is so important. I'm going to leave my input there, because networking is outside of my area of expertise. I know just enough to get myself into trouble :) – MobA11y Dec 03 '14 at 19:02

2 Answers2

5

On ELF systems you can use elf_hook to temporarily replace the real versions of various functions with your own mocked versions.

It allows you to redirect calls to any function from within a shared library to your own arbitrary function.

  • Create a shared library containing the code under test
  • In your test load the shared library dynamically (dlopen)
  • Redirect the symbols you want to mock to your test functions (elf_hook)
  • Now any calls to the real function within the library (the code under test) will be redirected to your mocked function

A plus for this method is that you can still call the original function when required.

  • If for some tests you want to have the call to, eg getaddrinfo, succeed, you can call the system version.
  • In other tests you can use your own mocked version, eg mocked_getaddrinfo, and have it return whatever you want.
  • You can create as many mocked_getaddrinfo functions as you want, to test multiple scenarios

elf_hook has the following signature:

void* elf_hook(char const* library_filename, 
               void const* library_address, 
               char const* function_name, 
               void const* substitution_address);

You would use it like this:

#include <dlfcn.h>
#include "elf_hook.h"

void do_stuff(); // from the library under test (do_stuff calls getaddrinfo)

// our mocked function which will alter the behaviour inside do_stuff()
int mocked_getaddrinfo(const char* node, 
                       const char* service,
                       const struct addrinfo* hints,
                       struct addrinfo** res)
{
    // return a broken value to test a getaddrinfo failure
    return 42;
}

// another version which actually calls the real function
int real_getaddrinfo(const char* node, 
                     const char* service,
                     const struct addrinfo* hints,
                     struct addrinfo** res)
{
    // the real getaddrinfo is available to us here, we only replace it in the shared lib
    return getaddrinfo(node, service, hints, res);
}

int main()
{
    const char* lib_path = "path/to/library/under/test.so";

    // load the library under test
    void* lib_handle = dlopen(lib_path, RTLD_LAZY);

    // test 1: getraddrinfo is broken
    //--------------------------------
    // replace getaddrinfo with our 'mocked_getaddrinfo' version
    elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), 
             "getaddrinfo", mocked_getaddrinfo);

    // call a function in the library under test where getaddrinfo fails
    do_stuff();

    // test 2: getraddrinfo is the system version
    //--------------------------------
    // replace getaddrinfo with our 'real_getaddrinfo' version 
    elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), 
             "getaddrinfo", real_getaddrinfo);

    // call the same function in the library, now getaddrinfo works
    do_stuff();

    dlclose(lib_handle);
    return 0;
}

Any call to getaddrinfo from within the library under test will now call mocked_getaddrinfo.

A comprehensive article by the elf_hook author, Anthony Shoumikhin, is here.

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
0

You can unit test any code if you are willing to spend the time and energy to do it. Basically with unit testing you are interested in achieving a target percentage of code coverage and in some industries MC/DC coverage. In some cases you will need to write mock code (modules that export functions that look like OS API / socket API, to your unit under test) which will then help drive execution through every nook and cranny in the "unit under test" (a .c/.cpp file) by returning the values you tell it to.

You may need to specify different include paths for your unit under test from the rest of your test application to avoid name conflicts, and you may also have to use pre-processor macros in the test headers to make the mock API appear like the real deal to your "unit" and keep it same as in production code.

You can test hardware drivers and any sort of low level code.

For example, if your code is writing and reading memory mapped registers which you expect say FPGA based logic to change, and you don't have the hardware (or it is tough for you to generate a test condition without actually travelling to mars), then you can write macros/wrapper functions for reading and writing to registers that will return your mocked up values. In the past have used CppUTest in the past which was easy to learn. I suppose a google search will turn up many results.

TankorSmash
  • 12,186
  • 6
  • 68
  • 106
rakeshdn
  • 135
  • 1
  • 7
  • 2
    you should focus on addressing the actual question... this is just a generic description of how to do testing - pretty much any testing – Karoly Horvath Dec 03 '14 at 23:04