Skip to content

jhannemann/dbc-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Design by Contract Demo

This code is used in both my undergraduate and graduate classes on software engineering at Kentucky State University.

It demonstrates the design and implementation of a software system around the following concepts:

The Employee Class Hierarchy

The basic setup is a starting point for an assignment in which students will design a class hierarchy based on the classic notion of a general Employee abstract base class, for which pay() is determined differently, depending on whether an employee is salaried or being paid on an hourly basis. A driver program then simulates a payroll processing run by filling a polymorphic std::vector<std::unique_ptr<Employee>> and iterating over it to print out paychecks. Note that the use of std::make_unique() requires a compiler compliant with the C++14 standard.

The Social Security Number Class

To help students get started, the demo code provides a class SSN, which constructs valid social security numbers. The class has an appropriate invariant, and all member functions have pre- and postconditions as neeeded. The constructor in particular accepts a std::string in a variety of formats. As required by DbC, the format can be matched via a publicly available input regular expression, and the resulting SSN string representation, obtained via the SSN::str() member function, will match a corresponding output regular expression.

A Note on C++11 Regular Expressions as Static Data Members

Regular expressions have been added to the language with the C++11 standard. The input and output regular expressions are wrapped in static member functions. This avoids an initialization of static data member may throw an exception that cannot be caught warning, which can occur if the regular expression would simply be stored as a static data member. This is a case of the static initialization order problem for static data members.

To demonstrate how much more compact assertions can be when using regular expressions for pattern matching, the SSN:str() member function contains 11 (!) additional postconditions, which together achieve the same result as the single match to the output regex.

The input regular expression is

^(?!000)(?!666)(?!9)\d{3}([- ]?)(?!00)\d{2}\1(?!0000)\d{4}$

and the output regular expression is

^(?!000)(?!666)(?!9)\d{3}-(?!00)\d{2}-(?!0000)\d{4}$

I.e. the input regular expression matches all valid combinations of digits, with a dash or space as separator or no separator at all, whereas the output regular expression requires the sparators to be dashes only. There is no deeper reason behind this particular set of requirements other than it provides for a richer set of pre- and postconditions for demonstration purposes.

The input and output regular expressions can be checked and are nicely explained at https://www.regexpal.com.

Assertions and Unit Testing

The C++ standard assert() macro, provided in the <cassert> header, calls std::abort() to terminate the program, unless the NDEBUG macro is defined. Specifically, assert() does not accommodate throwing an exception, which is appropriate in a DbC context, as one cannot recover from a failed assertion.

In a unit testing context, however, it is important to check the expected behavior of failed preconditions, especially in the context of constructors. The demo code therefore relies on the Boost.Assert library, which provides a way to register handler functions to assertions, which in turn can throw exceptions (in this case a AssertExeption). This capability is provided in the assertexcept subdirectory of the project. The unit tests can then use Qt's QVERIFY_EXCEPTION_THROWN macro to generate expected failures without aborting the ongoing execution of the unit test. Since release builds define the NDEBUG macro, these tests can be skipped via the QSKIP macro to avoid generating unexpected failures when testing a release build.

Building the Software and Running the Tests

The software can be configured and built using CMake's GUI or the console-based ccmake program. The CMakeLists.txt file provides for an option to enable or disable testing. If testing is disabled, Qt is not needed as a dependency at all. If testing is enabled, tests can be run by the make test command (if makefiles have been chosen as CMake's backend generator) or by running CMake's ctest program.

Student Assignments

Students at the undergraduate level will be expected to design and implement (possibly abstract) classes Employee, SalariedEmployee, and HourlyEmployee. Special attention should be paid to correctly formulate preconditions, invariants, and postconditions in the light of the given inheritance hierarchy.

The Python program generate_data.py in the payroll directory can be used to generate a file with 100 names, pseudo-randomly assigned, valid Social Security Numbers, and either salaried or hourly pay data (50% chance for either one). Students can then read the payroll file and process the payroll by iterating over a polymorphic container, printing each employee's name and pay. Advanced assignments can ask the students to generate the objects by using a design pattern such as a Factory Method or Abstract Factory.

In addition, students at the graduate level are then expected to also create data-driven unit tests and demonstrate that all tests are passed for all classes and their assertions, possibly by creating a pipeline for a contiuous integration tool like Jenkins.

Credits

Names have been created using https://homepage.net/name_generator/

About

Design by Contract Demo

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published