Challenges using TDD and BDD while developing and maintaining native
C++ applications
Though native C++ is extremely powerful language, number of
challenges faced by C++ developers. According to TIOBE, C++ is the 4'th most
popular language used by programming community. http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
1) Lack of mechanisms like
reflection that allows easy investigation of format of objects at runtime,
invoking methods, access fields of the objects. As a result very difficult to
find good programmer friendly, easily configurable ( via an xml file), Aspect oriented ( configurable through annotations) productivity tools for programmers, like IOC
containers, Mock libraries, Behaviour driven development tools etc. As a result deep testing of native C++ code is very costly.Component Object Model frameworks like ATL being used in C++ projects adds to the complexity of using TDD in C++. Need to have a good design to isolate business logic from frameworks boiler plate code. Plain C++ objects are easily testable than COM objects.
2) Not as readable and predictable as other
modern languages due to more operators, pointers, multiple inheritance, global variables, memory management issues, weak type system, friend keyword, large private codebase not exposed via public interfaces, runtime library differences between dependendent modules, and other language features.
3) Legacy systems are pretty old
systems with no test cases. Due to hard dependencies that were introduced
through years, difficult to re-factor and make it easily maintainable and
extensible.
4) Isolating functionality and
improving testability on legacy systems would be very difficult to achieve. Very
few choices of tools to isolate classes, mock dependencies and test.
4) Most of the existing legacy systems that were developed
using C++ are matured(Completed number of development/sustenance life cycles)
before the introduction of extreme programming techniques like Test Driven
Development, Behavioural development etc.
6) Legacy systems might have got fragile as layering and
other architectural restrictions got violated during its long existence.
7) Legacy systems might have got large amount of dead code.
8) Logic of algorithms got complicated during its evolution.
Business rules gets scattered with each patch release. Each quick fix to a bug
results in complex algorithms.
9) Sometimes legacy systems might get fragile also due to
bad coding practices.
10) Lack of documentation, code
comments, and unit tests would require C++ programmers to spend more time to
understand the code. Multiple responsibilities to each component. Refactoring
would be difficult as developer is not confident about what each function does.
11) Code duplication of legacy
systems would add to maintenance problem. Though two different work flows have
the same steps,they might be having two different code bases. Resulting in
programmer to modify the similar code at two places. Increasing the chances of
failure due to the change impact.
12) Testability characteristics
of legacy code being low, programmers cannot mock the dependencies(third party
API etc). Forcing the developer to depend on integration testing more, thus
reduces code coverage of tests.
13) Due to the lack of testability characteristics of legacy
code, for quick fixes programmer need to check-in the code without unit
testing, rely on official build from the build server. And build process being
slow, would increase the cost of a bug fix and its verification.
14) Behavioural Driven
Development and Test Driven Development can be done for Native C++ projects by
encapsulating Native C++ code in a C++/CLI (Managed C++). And on that wrapper
can use SpecFlow and MSTest frameworks.
Test Driven
Development
Test Driven Development = Test First Development +
Refactoring
·
In TDD, test code is written before functional
code.
·
We need not write all test cases before writing
the functional code. Focus one test at a time. Incrementally consider the next
expected behaviour of the unit under test.
·
In TDD after each test gets passed, it
represents a new working piece of behaviour is added to the system.
·
But capture your thoughts about upcoming tests
in a test list.
·
After each test, perform refactoring to remove
the code smells and make the code easily maintainable. Ensure business logic is
not scattered, each unit follows the single responsibility principle and all
other OOAD principles.
Setup of TDD Environment for native C++:-
Assuming that the development is
a new development from scratch or legacy code is made testable by refactoring the
code. All hard dependencies are eliminated, factory classes and other
frameworks are used to inject dependencies.
1.
Download Gmock & CppUnit . Gmock can be
integrated with any of the unit testing framework like boost unit testing
framework, google test or CppUTest. But since cppunit is one of the older
frameworks that’s already been used for unit testing in legacy systems, considering
integration of google mock with CPPUNIT.
2.
Open the Visual studio solution present in
google mock folder:- gmock\msvc\2010. And build the project.
3.
While building the google mock solution make
sure that runtime library configuration is set as used by cppunit (
Configuration Properties-> C/C++ -> Code Generation -> Runtime
Library). If cppunit using Multi Threaded DLL , we should set the same for google
mock as well.
4.
If Visual Studio version lesser than 2013 is
being used, configure the project to use the November 2012 CTP. From the
project properties, navigate to Configuration Properties -> General ->
Platform Toolset and select the CTP.
5.
Since Visual Studio 2013 has support for
variadic templates that are used by Google mock, we will not get any
compilation errors while building Google Mock.
6.
Note that after building google mock,
gmock.lib and other libraries will be generated @ appropriate output
directory ($(SolutionDir)$(Configuration)\)
7.
Open the CppUnit Visual studio solution (Build2010.sln),
and build the solution.
8.
Ensure that the Runtime Library setting is
same as that of google mock.
9.
Note that after building cppunit, cppunit
libraries will be generated in appropriate output directory.
10.
Create a new Win32 Console project in Visual
Studio.
11.
Configure the project to use google mock and
cppunit. Add the Additional Include Directories
12.
Configure the Additional Library
Directories,
13.
You can also create environment variables
for the dependent library locations and use them.
14.
In main, include gmock.h and CppUnit header
files for TestRunner.h and TestFactoryRegistry.h.
15.
Add code to initialize the Google Mock and code to run the CppUnit Test Runner.
16.
Define a new CppUnit test fixture class. Add
new test case methods and register them with CppUnit.
17.
Define the member functions of the test
fixture class. Include gmock and gtest header files to use the macros of gmock
and gtest.
18.
Declare a mock class derived from interface,
which you wanted to mock it up.
Consider we have the following interface for database access, and it has multiple implementations for SQLServer , SQLite database, Oracle database access etc. And we wanted to mock that interface as data access layer is always costly and testing the business model with mock data access layer would be the best way to achieve coverage for the Business model objects.
Context
Consider we have the following interface for database access, and it has multiple implementations for SQLServer , SQLite database, Oracle database access etc. And we wanted to mock that interface as data access layer is always costly and testing the business model with mock data access layer would be the best way to achieve coverage for the Business model objects.
First create the mock objects for the above interfaces. Mocking can be done manually using macros or automatically using mock generator.
Automatic Generation of Mock Objects
Gmock provides gmock_gen.py tool in Google Mock's scripts/generator/ directory, If you give it a C++ file and the name of an abstract class defined in it, it will print the definition of the mock class for you. That mock object would be a concrete class that can be instantiated.
This tool requires Python to be installed.
Once we run the tool, concrete mock objects gets created automatically.
19.
Identify the code which have the instances
of the interface injected. We can
pass the real object or the mock object.
20.
Write the test case for the actual code that
you wanted to test. Set the expectations of the mock call that would happen in
the actual method, pass the mock object to the method.
21.
Assert the state of actual object/function when the
dependent object (Mock object) behaves in a particular way.












No comments:
Post a Comment