Part 1 of Automating Unit Tests with a Base Class provided a brief summary of the problems we run into while writing unit tests for our business objects, and discussed why it's important that the database be included. In this post I'm going to describe the process of writing unit tests for business objects. In part 3 I will detail how we can automate the testing of our CRUD operations via a base unit test class.
Testing Business Objects
CRUD operations are the primary operation we carry out on our business objects. Each of these CRUD operations involves a set of steps that is performed for each type of object. To ensure that the operations work for all of our business objects it is important to have unit tests that verify the expected results are provided when we perform each operation. Before diving into the specifics of how we test our business objects lets first take a look at the interface of our business objects (for the purposes of this article).
1 public abstract class BusinessObject {
2
3 public BusinessObject() {
4 }
5
6 abstract public void Load(string key);
7 abstract public bool Save();
8 abstract public bool Delete();
9 }The BusinessObject class is used as the base class for our business objects. The class provides the common interface for our business objects. We can see that our business objects support saving themselves to the data store (Create, Update) via the Save() method, deleting themselves from the data store (Delete) via the Delete() method, and finally loading themselves from the data store (Read) via the Load() method.
Now that we have overviewed the base BusinessObject class let's take a look at an example Customer object.
1public class Customer : BusinessObject {
2
3 public Customer() : base() {}
4
5 public override bool Delete() {
6 // Create DAL, and remove from the database...
7 return true;
8 }
9
10 public override bool Save() {
11 // Create DAL, and save to the database...
12 return true;
13 }
14
15 public override void Load(string customerID) {
16 // Create DAL, and load from the database...
17 }
18
19 public string CustomerID {
20 get { return _customerID; }
21 set { _customerID = value; }
22 }
23
24 public string CompanyName {
25 get { return _companyName; }
26 set { _companyName = value; }
27 }
28
29 public string ContactName {
30 get { return _contactName; }
31 set { _contactName = value; }
32 }
33
34 public string ContactTitle {
35 get { return _contactTitle; }
36 set { _contactTitle = value; }
37 }
38
39 public string Address {
40 get { return _address; }
41 set { _address = value; }
42 }
43
44 public string City {
45 get { return _city; }
46 set { _city = value; }
47 }
48
49 public string Region {
50 get { return _region; }
51 set { _region = value; }
52 }
53
54 public string PostalCode {
55 get { return _postalCode; }
56 set { _postalCode = value; }
57 }
58
59 public string Country {
60 get { return _country; }
61 set { _country = value; }
62 }
63
64 public string Phone {
65 get { return _phone; }
66 set { _phone = value; }
67 }
68
69 public string Fax {
70 get { return _fax; }
71 set { _fax = value; }
72 }
73
74
75 private string _customerID = String.Empty;
76 private string _companyName = String.Empty;
77 private string _contactName = String.Empty;
78 private string _contactTitle = String.Empty;
79 private string _address = String.Empty;
80 private string _city = String.Empty;
81 private string _region = String.Empty;
82 private string _postalCode = String.Empty;
83 private string _country = String.Empty;
84 private string _phone = String.Empty;
85 private string _fax = String.Empty;
86 }
The Customer class provides a set of properties for managing important information about the customer, as well as an implementation for saving, deleting, and loading customers from the data store. The actual implementation of the save, delete, and load is not the focus of this article, however, the fact that each business object implements the interface for performing these operations via the BusinessObject class is important. By inheriting from the BusinessObject class our objects are agreeing to implement the interface we've defined. When we begin looking at how we can automate the unit tests for the CRUD operations on our objects this common interface will prove itself extremely valuable.
Unit Testing CRUD Operations
Now that we've covered the basics of our business objects lets take a quick look at the details surrounding the unit testing of the CRUD operations for our objects. Below is a list of the four CRUD operations and the process we follow for unit testing each operation. I've included a sample test for each operation that provides one (of many) ways of implementing the tests.
Create -
1. Instantiate an instance of the object.
2. Set the properties of the object with valid values.
3. Call the Save() method on the object, checking that the save succeeds.
1 public void CanBeSaved() {
2
3 Customer customer = new Customer();
4 customer.ContactName = "Steve Eichert";
5 customer.Address = "221 South North West Ave.";
6 customer.City = "Philadelphia";
7 customer.Region = "PA";
8 // ...set other properties
9
10 Assert.IsTrue(customer.Save());
11
12 }
Read -
1. Do Create.
2. Retrieve the object out of the data store and check that the values of the saved object equal the values pulled from the data store.
1 public void CanBeRead() {
2
3 Customer savedCustomer = new Customer();
4 // ...set properties
5
6 Assert.IsTrue(savedCustomer.Save());
7
8 // read the customer
9 Customer readCustomer = new Customer();
10 readCustomer.Load(savedCustomer.CustomerID);
11
12 // check properties of the loaded object against the saved object
13 Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
14 Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
15 Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
16
17 // check remaining properties as necessary...
18 }
Update
1. Do Read
2. Change the properties of the object.
3. Call the Save() method on the object.
4. Retrieve the object out of the data store and check that the values of the updated object equal the values pulled from the data store.
1public void CanBeUpdated() {
2
3 // read the customer
4 Customer updateCustomer = new Customer();
5 updateCustomer.Load("CUST-99");
6 updateCustomer.ContactName = "New Contact Name";
7 // ...change other properties as necessary
8
9 Assert.IsTrue(updateCustomer.Save());
10
11 // read the customer
12 Customer readCustomer = new Customer();
13 readCustomer.Load(updateCustomer.CustomerID);
14
15 // check properties of the loaded object against the saved object
16 Assert.AreEqual(updateCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
17
18 // check remaining properties as necessary...
19
20}
Delete -
1. Do Create
2. Call Delete() on the object.
3. Try reading the object back out of the data store and ensure we get a null object (since its deleted).
1public void CanBeDeleted() {
2
3 Customer customer = new Customer();
4 // ... set properties
5
6 Assert.IsTrue(customer.Save());
7
8 Assert.IsTrue(customer.Delete());
9
10 // read the customer
11 Customer readCustomer = new Customer();
12 readCustomer.Load(customer.CustomerID);
13
14 // ... check that the readCustomer is not valid through some mechanism
15 Assert.AreEqual("-1", customer.CustomerID, "The deleted object still exists!");
16
17}
To ensure each business objects is successfully handling these four CRUD operations we need to write four (at a minimum) unit tests for each business object in our system. On a decent sized project this forces us to write a lot of redundant code.
Rather then writing tests after test for every business object, we can automate the process of testing the basic CRUD operations by creating a base unit test class. With a base test class in place we can focus on testing the pieces of the application that need it the most rather then the rudimentary CRUD operations. By incorporating a base test class into our testing framework we can remove the burden of writing tests for each CRUD operations and instead focus on writing tests for the business logic within our application.
Next up: How do we accomplish our goal of automating our tests?
[Now Playing: The Wallflowers - The Wallflowers - After the Black Bird Sings (04:4
]