C ++ Engineering Practice (6): how to call a mock System in Unit Testing

Source: Internet
Author: User
Document directory
  • System function dependency Injection
  • Link seams)
  • Example: C client library of ZooKeeper
  • Other methods
  • Third-party C ++ Library

Chen Shuo (giantchen_AT_gmail)

Blog.csdn.net/Solstice

Chen Shuo on C ++ engineering practices of a series of articles: http://blog.csdn.net/Solstice/category/802325.aspx

Chen Shuo blog collection download: http://blog.csdn.net/Solstice/archive/2011/02/24/6206154.aspx

This document uses the "Creative Commons signature-non-commercial use-deduction of the 3.0 Unported License Agreement (cc by-nc-nd. Http://creativecommons.org/licenses/by-nc-nd/3.0/

Abstract: This article discusses several mock system calls (and other third-party libraries) when writing unit tests.

This article only considers the Linux x86/amd64 platform.

Chen Shuo in the "distributed program automated regression testing" http://blog.csdn.net/Solstice/archive/2011/04/25/6359748.aspx once talked about the advantages and disadvantages of unit testing in Distributed Program Development (well, the main disadvantage ). However, unit testing is necessary in some cases and is especially important in failure scenarios, for example:

  • When developing a storage system, simulate read (2)/write (2) and return an EIO error (it may be that the disk is full, or the disk has a bad track and cannot read data ).
  • When developing the network library, simulate write (2) and return the EPIPE error (the other party accidentally disconnects ).
  • When developing a network library, simulate self-connection. The network library should use getsockname (2) and getpeername (2) to determine whether it is a self-connection and then disconnect it.
  • When the network library is developed, simulate that the local ephemeral port is used up, and connect (2) returns an EAGAIN temporary error.
  • Let gethostbyname (2) return our preset value to prevent the pressure on the company's DNS server caused by unit testing.

These test cases may be difficult to test with the test harness mentioned above. This unit test is on stage. Now the question is, how do we mock these system functions? Or in other words, how can we inject dependencies on system functions into the tested program?

System function dependency Injection

In section 4.3.2 of Michael Feathers's Art of modifying Code/Working into tively with Legacy Code, the author introduces link seam, which can solve our problem. In addition, a post in Stack Overflow also summarized several ways: http://stackoverflow.com/questions/2924440/advice-on-mocking-system-calls

If the program (Library) takes into account testability During writing, we can solve the problem of dependency injection from the design without using the above hack method. Two ideas are provided here.

First, Uses the traditional object-oriented approach, and uses the late binding during the runtime to implement injection and replacement. Write a System interface by yourself, the open, close, read, write, connect, bind, listen, accept, gethostname, getpeername, and getsockname functions used in the program are encapsulated by virtual functions. Then, do not directly call open () in the code, but call System: instance (). open ().

In this way, the Code takes the initiative to give control to the System interface, and we can start it here. During the write unit test, replace this singleton instance with our mock object to simulate various error codes.

Second, Using late binding during the compilation or link period. Note that in the first method, polymorphism is unnecessary during runtime, because the program uses only one implementation object from birth to death. The virtual function call cost does not seem to be worth this. (In fact, compared with system calls, the overhead of virtual functions is negligible .)

We can write a system namespace header file in which we declare common functions such as read () and write (), and then in. the system functions forwarded to the corresponding system in the cc file: read () and: write.

// SocketsOps.hnamespace sockets{  int connect(int sockfd, const struct sockaddr_in& addr);}// SocketsOps.ccint sockets::connect(int sockfd, const struct sockaddr_in& addr){  return ::connect(sockfd, sockaddr_cast(&addr), sizeof addr);}

The code here comes from the muduo network library

Http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.h
Http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.cc

With such an indirect layer, you can write unit tests and link our stub implementation to achieve the purpose of replacement:

// MockSocketsOps.ccint sockets::connect(int sockfd, const struct sockaddr_in& addr){  errno = EAGAIN;  return -1;}

C ++ a program can only have one main () entry. Therefore, you must first make the program into a library and then link the library with the unit test code. Suppose there is a mynetcat program. To write a C ++ unit test, we split it into two parts: library and main (). The source files are mynetcat. cc and main. cc respectively.

When compiling a Common Program:

g++ main.cc mynetcat.cc SocketsOps.cc -o mynetcat

Write this when compiling unit tests:

g++ test.cc mynetcat.cc MockSocketsOps.cc -o test

The above is the simplest example. In actual development, stub functions can be more powerful. For example, different errors are returned based on different test cases. In this case, you do not need to use virtual functions, and the code is concise. You only need to use the prefix sockets. For example, write sockets: connect (fd, addr) in the application code ).

Muduo has no unit tests yet, but has reserved these stubs.

The benefit of namespace is that it is not closed. We can open and add new functions to it at any time without modifying the original header file (the control of this file may not be in our hands ). This is also the advantage of using the non-member non-friend function as the interface.

The above two methods also offer the benefit of mock-only part of the code we care about. If the program uses SQLite or Berkeley DB, which will access third-party libraries of the local file System, our system interface or System namespace will not intercept the open (2) of these third-party libraries), close (2), read (2), write (2) and other system calls.

Link seams)

If the program did not consider unit testing at the beginning of encoding, how should we inject mock system calls? The second method above has already provided the answer, that is, using link seam (link-period sharn ).

For example, if you want the mock connect (2) function, we will implement a connect () function in the unit test program. When linking, we will first adopt our own defined function. (This is true for dynamic links. If it is a static link, a multiple definition error is returned. In most cases, libc is dynamically linked .)

typedef int (*connect_func_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);connect_func_t connect_func = dlsym(RTDL_NEXT, "connect");bool mock_connect;int mock_connect_errno;// mock connectextern "C" int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){  if (mock_connect) {    errno = mock_connect_errno;return errno == 0 ? 0 : -1;  } else {    return connect_func(sockfd, addr, addrlen);  }}

What if the program really needs to call connect (2? In our own mock connect (2), we can no longer call connect (), otherwise there will be infinite recursion. To prevent this, we use dlsym (RTDL_NEXT, "connect") to obtain the real address of the connect (2) system function, and then call it through the function pointer connect_func.

Example: C client library of ZooKeeper

The C client library of ZooKeeper uses link seams to write unit tests. For the code, see:

Http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.h
Http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.cc

Other methods

Stack Overflow's post also mentions a practice that can easily replace functions in the dynamic library, that is, using the ld -- wrap parameter,
The document is very clear and I will not go into details here.

       --wrap=symbol           Use a wrapper function for symbol.  Any undefined reference to           symbol will be resolved to "__wrap_symbol".  Any undefined           reference to "__real_symbol" will be resolved to symbol.           This can be used to provide a wrapper for a system function.  The           wrapper function should be called "__wrap_symbol".  If it wishes to           call the system function, it should call "__real_symbol".           Here is a trivial example:                   void *                   __wrap_malloc (size_t c)                   {                     printf ("malloc called with %zu\n", c);                     return __real_malloc (c);                   }           If you link other code with this file using --wrap malloc, then all           calls to "malloc" will call the function "__wrap_malloc" instead.           The call to "__real_malloc" in "__wrap_malloc" will call the real           "malloc" function.           You may wish to provide a "__real_malloc" function as well, so that           links without the --wrap option will succeed.  If you do this, you           should not put the definition of "__real_malloc" in the same file           as "__wrap_malloc"; if you do, the assembler may resolve the call           before the linker has a chance to wrap it to "malloc".
Third-party C ++ Library

Link seam is also applicable to third-party C ++ libraries.

For example, a basic library team in the company provides File class, but this class does not use virtual functions. We cannot implement mock object through sub-classing.

class File : boost::noncopyable{ public:  File(const char* filename);  ~File();    int readn(void* data, int len);  int writen(const void* data, int len);  size_t getSize() const; private:};

If you need to write unit tests for programs using File class, you can define the implementation of its member functions by yourself, so that you can inject any results we want.

// MockFile.ccint File::readn(void* data, int len){  return -1;}

(This method is feasible for the dynamic library, and an error is reported for the static library. We either ask the other party to provide a dynamic library dedicated to unit testing, or use the source code to compile the library by ourselves .)

Java also has a similar practice, replacing our own stub jar file in class path to implement link seam. However, Java has dynamic proxies and seldom uses link seam for dependency injection.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.