Step 2: Writing C++ Classes ########################### .. include:: /header.inc Let's add our first class, for something called the "Machine": Create a new folder in your project. Name this one ``include``. Place this file in this new directory. .. literalinclude:: code/cpu-factory2/include/Machine.h :linenos: :caption: include/Machine.h All classes are named starting with a capital letter. This is a convention, not a law. Writing C++ classes ******************* The C++ compiler does not really care about how you lay out a C++ project. All it cares about is that you tell it where you hid your files. However, it is an extremely bad idea to write code without having a solid style that you follow every time you craft a program file. In this class, all C++ class code will be split into two parts: a header file that lays out the class definition, but contains as little code as possible (ideally none at all). The second file will be the implementation file, with all the real code to make the class complete. That implementation file goes in a folder named ``lib``. The name of the header file is that class name followed by a ``.h``. (In some circles, you might use ``.hpp``. We will not do that in our code.) The implementation file has the same name again, this time with a ``.cpp`` extension. (Occasionally, I see ``.cxx``, but that is rare.) Formally, that header file is a public document, needed by both developers who use your class, and developers who implement that class. Users have no real business looking into how you implement a class. The header file tells them all they need to know to use the class. As long as you honor the "contract" specified in this header, you are free to think up completely new implementations, hopefully that make the class a better piece of code. With this header in hand, let's think through how we might test it. Here is the implementation file: .. literalinclude:: code/cpu-factory2/lib/Machine.cpp :linenos: :caption: lib/Machine.cpp Testing a Class *************** C++ classes define a set of attributes and a set of methods. The attributes represent a kind of "state" that objects created with the class will set up. The methods provide a way to manipulate that state, or examine that state. We definitely need to check to see that each method does what it is supposed to do. Sometimes, that is easy, sometimes, you need to get creative to test things. Class Constructor ================= Every class has a constructor, although you may not have written it. We will provide one and ask that that code set up default values for all attributes defined in the class. For the ``Machine`` class, all we have that we could test is that machine name string we will provide on the constructor. Maybe a simple tests just sets the name and verifies that it is stored correctly. I am going to use a simple pattern for naming methods in this class. Any method that sets an attribute will have a name beginning with the string ``set_``, and any method that fetches an attribute will be named with ``get_``. I am definitely not just allowing anyone to access attributes. The default for all of them is ``private`` unless you have a really good reason for making them public! Writing Our First Test ********************** We are ready to create a test. Wait! We have no code to test. That is perfect. We need to think through when we want to see happen when we write the code, and come up with a way to verify that the code works as expected. At this point, all I have is a constructor. That constructor is going to take a name string as a parameter, and store it away for later reference. The single method we have defined will fetch the recorded name later. What about setting the name. That does not make sense in this situation. We will only create one machine in our simulator. (Hmmm, maybe I could create more if I want to simulate parallel processing. That might be something for later!) We will not let folks change the name once it is set! That leaves the ``accessor`` routine. We can ytest our contructor if we simple return the string, and check that it matches the name we handed to the constructor. The test is a simple logical expression. Sounds easy to write, let's see this in action! Setting up Catch **************** Catch_ is a single header file system that sets up unit testing in a C++ project. All you need to do is add the heder file to your project, then write a few short files. Clone Catch =========== The Catch_ project code is on GitHub_ (naturally!) In your course folder (not in another repo), do this: .. code-block:: bash $ cd cosc2325 $ git clone https://github.com/catchorg/Catch2.git Inside of this project you will find a single file that you need to copy into your project folder: .. code-block:: bash $ cp Catch2/single_include/catch2/catch.hpp cpu-factory/include We need to modify our ``Makefile`` so it will do testing for us. Here is the new version: .. literalinclude:: code/cpu-factory2/Makefile :linenos: :caption: Makefile Configure for Testing ===================== Catch_ is designed in such a way that you create a new application in your project, and this one runs tests. To make all of that happen, we added a few rules to the ``Makefile`` and we need a simple file to create the ``main`` function for the test application. Here is the file you need. Just drop it in the tests folder and forget about it! .. literalinclude:: code/cpu-factory2/tests/test_main.cpp :linenos: :caption: tests/test_main.cpp The contents of this file just tell the header that it needs to add a ``main`` function here. The pattern for a test is shown in the next file. I like adding this to a new project. The only reason for this file is for testing the test system. With no real tests around, at ;east I can see that the test application gets built, and that tests run successfully! Here is the ``sanity`` test: .. literalinclude:: code/cpu-factory2/tests/test_sanity.cpp :linenos: :caption: tests/test_sanity.cpp Look at the line with ``TEST_CASE`` on it. The two strings are used to identify the test, and name a test group that could be used to isolate testing to one area of the code. This is not something we need to worry about now, but it might come in handy later. Let's try this out! .. command-output:: make clean :cwd: code/cpu-factory2 .. command-output:: make :cwd: code/cpu-factory2 .. command-output:: make test :cwd: code/cpu-factory2 That last line above is what we want to see. We had one real (well almost real) test in our test code. The test checked that it worked properly, and it did! We have made some progress, even though we do not have much real code yet. You now have a basic project structure that will survive a good part of our coding, and we have a unit test system running to help test our code. There is one more step I want to take before we launch off and create real code. That step will stop any lingering "smell" in your code! Ready?