Unit Testing Classes
When we test classes we are still using the individual functions
(methods) of our classes as the unit that we are testing. But we
may have to structure our tests differently than before.
Let's use an example - download the Invoice.h
and Invoice.cpp as this will be the class we are
unit testing. Browse the header file and read the comments to
understand how the functions will behave.
Invoice.h
#ifndef INVOICE_H
#define INVOICE_H
#include
#include
/**
* An invoice holds the amount due for a service.
* An invoice starts off empty and costs of services are added to
it.
*/
class Invoice {
public:
/**
* Construct a new Invoice with no dollars owed with given valid
invoiceCode.
* It is an error to give a bad invoiceCode.
*/
Invoice(std::string invoiceCode);
std::string getInvoiceCode();
int getDollarsOwed();
/**
* Set a four character, uppercase identifier code for this
invoice.
* If the code is invalid, false is returned and the stored code is
not
changed.
* Returns true if it is a valid code or false otherwise.
*/
bool trySetInvoiceCode(std::string invoiceCode);
/**
* Adds the cost of a service to the current invoice in
dollars.
* Use only positive dollar amounts.
*/
void addServiceCost(int costDollars);
/**
* Applies a discount to the Current owed amount, changing its
value
immediately.
* Reduces the amount by percent argument with values 0.0->1.0.
Values must not
be outside this range.
* If dollars owed would be 20, then a discount percent of 0.25
would have
dollars owed become 15
*/
void applyDiscount(float percent);
/**
* Returns the tax amount owed given the current dollars owed of the
invoice.
* Tax is fixed at 10% of dollars owed.
*/
int computeTax();
private:
/**
* Returns true if the given invoiceCode is a 4 character, uppercase
string,
false otherwise.
*/
bool isValidInvoiceCode(std::string invoiceCode);
std::string invoiceCode; // Four uppercase characters unique to the
invoice
int dollarsOwed; // Our invoices only work with whole dollars
};
#endif
Invoice.cpp
#include "Invoice.h"
Invoice::Invoice(std::string invoiceCode) :
invoiceCode(invoiceCode),
dollarsOwed(0) {
if (isValidInvoiceCode(invoiceCode) == false) {
std::cout << "Bad invoice code! " << invoiceCode
<< std::endl;
}
}
std::string Invoice::getInvoiceCode() {
return invoiceCode;
}
int Invoice::getDollarsOwed() {
return dollarsOwed;
}
bool Invoice::trySetInvoiceCode(std::string invoiceCode) {
if (isValidInvoiceCode(invoiceCode) == false) {
return false;
}
this->invoiceCode = invoiceCode;
return true;
}
void Invoice::addServiceCost(int costDollars) {
dollarsOwed += costDollars;
}
void Invoice::applyDiscount(float percent) {
dollarsOwed = dollarsOwed - dollarsOwed * percent;
}
int Invoice::computeTax() {
return dollarsOwed * 0.1f;
}
bool Invoice::isValidInvoiceCode(std::string invoiceCode) {
// Check it is correct length
if (invoiceCode.length() != 4) {
return false;
}
// Check it is all uppercase characters
for (int i=0; i if (std::isupper(invoiceCode) == 0) {
return false;
}
}
// Otherwise good!
return true;
}
We should also organise our tests so we can be sure that we have
covered testing all our classes - we are going to write a main
function to test each class individually. So let's also create a
file called InvoiceTest.cpp that will have our
main and unit tests just for the Invoice
class.
Let's focus on one function to test at the moment:
We need to design a set of test values that will cover all the
uses of this function. We have to take care to not write tests that
break the program - so if we read the comment for the
addServiceCost function we see that the argument
costDollars is not allowed to be zero or negative.
This is an important concept in testing! We only test the parts of
our program that work. If a function says that it cannot take a
certain type of value, we should not write a test that gives this
erroneous value. The only time you should write tests for functions
which give bad argument values is if that function somehow handles
that error (and then you need to test that error handling too).
Let's give one example of a test of
addServiceCost:
We need to set up an object to test, call the function we are
testing, and then verify that the result is what we expect. This is
an example of testing for positive integers. Other
tests would could try are large numbers and
numbers that are on the boundary of what is
allowed - in this case, the value 1:
A rule we have not covered for unit testing is that each test
should be independent of the other tests that we run - they should
not depend on any state of other tests. This means we will need to
set up fresh state (create an object of our class) for every single
test. Below is an example of the previous two tests where we do
this:
Note that we create all the state fresh for each tests without
reusing the previous. Also notice we are using some curly-braces
around each test - we are allowed to do this and it means that the
names of variables used in a test are only inside that scope, and
can be reused in later tests. This saves some typing.
Exercise:
In the main of your InvoiceTest.cpp, write full
test coverage of all the public functions of the Invoice class,
including the constructor. This will be many tests.
If you have finished this, try writing a
makefile that can build and run these tests in an
automated way.
Unit testing Main-1-1.cpp
Unit Testing Classes When we test classes we are still using the individual functions (methods) of our classes as the un
-
answerhappygod
- Site Admin
- Posts: 899604
- Joined: Mon Aug 02, 2021 8:13 am
Unit Testing Classes When we test classes we are still using the individual functions (methods) of our classes as the un
Join a community of subject matter experts. Register for FREE to view solutions, replies, and use search function. Request answer by replying!