88
Visual Studio 2010 Testing Labs Page 1 of 88 Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com Printing or duplication is prohibited without author’s expressed written permission. Visual Studio 2010 Testing By Benjamin Day

Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 1 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Visual Studio 2010 Testing By Benjamin Day

Page 2: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 2 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Contents Lab 01: A Deck of Cards using Test-Driven Development ......................................................................... 4

Card Class: Write Unit Test for the Constructor ....................................................................................... 8

Card Class: Compile the Code ................................................................................................................... 8

Card Class: Write Enough Code to Make It Compile ................................................................................. 9

Card Class: Run the Test .......................................................................................................................... 11

Card Class: Write Enough Code to Make the Test Pass .......................................................................... 13

Deck Class: Write the Tests ..................................................................................................................... 14

Deck Class: Enough Code to Compile ...................................................................................................... 16

Deck Class: Run the Tests ........................................................................................................................ 17

Deck Class: Implement Enough Code to Make the Tests Pass................................................................ 19

Lab 02: Unit Test the Adapter Pattern..................................................................................................... 24

Overview of the Solution ........................................................................................................................ 24

Person to DataRow: Write the Tests ....................................................................................................... 27

Person to DataRow: Enough Code to Compile and Run the Test ........................................................... 28

Person to DataRow: Enough Code to Pass the Test ................................................................................ 29

DataRow to Person: Write the Tests ....................................................................................................... 31

DataRow to Person: Enough Code to Compile and Run the Test ........................................................... 32

DataRow to Person: Enough Code to Pass the Test ................................................................................ 33

Lab 03: Unit Test the Repository Pattern ................................................................................................ 34

Prerequisite ............................................................................................................................................. 34

Overview of the Solution ........................................................................................................................ 34

Requirements for the Repository ........................................................................................................... 37

Create the Database ............................................................................................................................... 37

Brainstorming the Tests .......................................................................................................................... 39

Save: Write the Test ................................................................................................................................ 40

Save: Write Enough Code to Compile ..................................................................................................... 42

Save: Write Enough Code to Make the Tests Pass.................................................................................. 44

Get By Id: Write & Run the Test .............................................................................................................. 47

Get By Id: Enough Code to Make the Test Pass ...................................................................................... 48

Get List: Write the Test and Run It .......................................................................................................... 50

Page 3: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 3 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Modify Person: Write & Run the Test ..................................................................................................... 51

Lab 04: Test Logic in Isolation Using Mock Objects ................................................................................. 54

Introduction ............................................................................................................................................ 54

Lab Overview .......................................................................................................................................... 54

Brainstorming the Tests .......................................................................................................................... 55

Create a Mock ICustomerRepository ...................................................................................................... 55

Create Suggested Account Code: Write the Test and Implementation .................................................. 57

Create Account Code: Write the Test ..................................................................................................... 59

Create Account Code: Write Enough Code to Compile .......................................................................... 60

Create Account Code: Write Enough to Make the Test Pass .................................................................. 62

Retry Logic: Write the Test ..................................................................................................................... 63

Retry Logic: Implement the Changes to MockCustomerRepository ....................................................... 65

Retry Logic: Write Enough to Make the Test Pass .................................................................................. 67

Multiple Retry Logic: Write the Test ....................................................................................................... 69

Multiple Retry Logic: Write Enough to Make the Test Pass ................................................................... 72

Lab 05: Unit Test a User Interface Using the Model-View-Presenter Pattern ........................................ 74

Prerequisite ............................................................................................................................................. 74

Introduction ............................................................................................................................................ 74

Requirements .......................................................................................................................................... 75

The Unit Tests & Code Structure ............................................................................................................ 76

Write the Tests ........................................................................................................................................ 78

Implement the CustomerSearchPresenter Code .................................................................................... 81

Run the Tests .......................................................................................................................................... 83

Implement the User Interface ................................................................................................................ 83

Run the Web Application ........................................................................................................................ 86

Page 4: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 4 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Lab 01: A Deck of Cards using Test-Driven Development

This lab will walk you through the process of creating a class library representing a deck of playing cards

using “test first”, test-driven development.

Remember the process for doing “test first” described in William Wake’s “Extreme Programming

Explored” (Addison-Wesley):

1. Write the test code 2. Compile the test code Fails because there’s no implementation 3. Implement just enough to compile 4. Run the test Fails 5. Implement enough code to make the test pass 6. Run the test Pass 7. Refactor for clarity and to eliminate duplication 8. Repeat

There is an implied “step 0” which is to understand the requirements and decide what the basic tests

should be. The goal is to avoid the “just write code” mindset and always have a plan for what you need

to do, how you’re going to do it, and how to know when you’re done.

What are some of the rules, attributes, and requirements for a deck of cards? A deck of playing cards

has 4 “suits” – Clubs, Diamonds, Hearts, and Spades – and 13 possible “rank” values – Ace, 2, 3, 4, 5, 6,

7, 8, 9, 10, Jack, Queen, King. A valid deck is made up of 52 cards and each card is unique in the deck.

Thinking about those requirements, it sounds like we have two classes: Card and Deck. Deck is a

collection of Card objects. The Card object has two properties: Suit and Rank. Suit and Rank both have

well defined values – those will probably end being enums. We’re going to need to know how many

cards are in a deck. We’ll also need to know if one card is equal to another.

Page 5: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 5 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Now that we have a good understanding of the requirements, we’ll start by creating a unit test in our

project.

- Open DeckOfCards.sln in Visual Studio 2010

- Right-click the DeckOfCards.UnitTests project, expand the Add menu, and choose Unit Test…

Page 6: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 6 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

You should now see the Add New Test dialog. This dialog lets you choose the type of test that you want

to create. In this case, you’ll be creating a Basic Unit Test.

- Choose Basic Unit Test

- In the Test Name box, Enter CardFixture

- Click the OK button

Page 7: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 7 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

You should now see new CardFixture.cs file with a method inside named, TestMethod1. This is the

basic template that Visual Studio creates for you but we won’t be using the TestMethod1 method so you

can delete it.

- Delete the TestMethod1() method including its [TestMethod] attribute.

Page 8: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 8 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Card Class: Write Unit Test for the Constructor

Since we’ll be creating Card objects with specific Suit and Rank values, we need to write a test that

verifies that the new objects have been initialized properly.

[TestMethod]

public void CardConstructorInitializesSuitAndRankFields()

{

var card = new Card(Suit.Diamond, Rank.Two);

Assert.AreEqual<Suit>(Suit.Diamond,

card.Suit,

"Wrong suit.");

Assert.AreEqual<Rank>(Rank.Two,

card.Rank,

"Wrong rank.");

}

- Add a method to the CardFixture.cs file with the code shown above

Card Class: Compile the Code If you’re following the Test-Driven Development process, the next step is to attempt to compile this

code.

- From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests

It probably isn’t a surprise that the code doesn’t compile and that there are lots of errors because there

isn’t any implementation yet.

Page 9: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 9 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Card Class: Write Enough Code to Make It Compile

The next step is to create enough implementation so that the code will compile.

First, create an enumeration for Rank.

- Create a Rank enumeration in the DeckOfCards.UnitTests namespace as shown above

Next, create an enumeration for Suit.

- Create a Suit enumeration in the DeckOfCards.UnitTests namespace as shown above

Page 10: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 10 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The last thing we need in order to make the code compile is to create a Card class.

- Create a Card class in the DeckOfCards.UnitTests namespace as shown above

Now when you recompile the code, the build should succeed.

- Compile the code and verify that there are no errors

Page 11: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 11 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Card Class: Run the Test

Now that the code compiles, you’re ready to run the unit test. You can do this from the Test View

window.

- Go to the Test menu, go to Windows, and choose Test View

Page 12: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 12 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

You should now see the Test View dialog. This dialog allows you to run your unit tests.

- Verify that the CardConstructorInitializesSuitAndRankFields test is selected

- Click the Run Selection button as shown in the screenshot above

After the test run completes, you should see that the test has failed.

Page 13: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 13 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

To see what failed, you can either look at the Error Message column on the Test Results window or

double-click on the failed test to bring up the execution details viewer. The current error should be

something like “Assert.AreEqual failed. Expected:<Diamond>. Actual:<Club>. Wrong suit.”

This is telling us that, although the code compiles, we still need to write more code to make the test

pass.

Card Class: Write Enough Code to Make the Test Pass

Pretty much all that is missing is a few lines of code in the constructor on the Card class.

- Go to the Card class and add the two missing lines as shown above

Now re-run the code and verify that the test passes.

- Go to the Test View window and re-run the test

Page 14: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 14 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The test should now pass.

Deck Class: Write the Tests

Now that we have the Card class basically written, we’ll start working on the Deck class.

Remember the rules:

1. The deck cannot have more than 52 cards

2. All of the cards must be unique

3. A complete deck will have 52 cards

So we’ll need to Add cards to a Deck class and then let’s also say that the Deck has an option to

completely initialize itself and create a complete deck of cards.

This implies a handful of new unit tests for the Deck class:

1. Test to make sure that the Deck is empty

2. We’ll need a way to add cards to the deck, so there will be a unit test to Add cards

3. A unit test to ensure that the Deck rejects duplicate cards

4. A test to verify that the Deck has initialized itself

Page 15: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 15 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

[TestMethod]

public void AnEmptyDeckHasZeroCards()

{

var deck = new Deck();

Assert.AreEqual<int>(0,

deck.CardCount,

"Card count should be 0.");

}

[TestMethod]

public void AddingUniqueCardIncrementsTheCardCount()

{

var deck = new Deck();

deck.Add(new Card(Suit.Diamond, Rank.Two));

Assert.AreEqual<int>(1,

deck.CardCount,

"Card count was wrong.");

}

[TestMethod]

[ExpectedException(typeof(InvalidOperationException))]

public void AddingDuplicateCardThrowsException()

{

var deck = new Deck();

deck.Add(new Card(Suit.Diamond, Rank.Two));

// add a duplicate card

deck.Add(new Card(Suit.Diamond, Rank.Two));

}

[TestMethod]

public void InitializeCreates52Cards()

{

var deck = new Deck();

deck.Initialize();

Assert.AreEqual<int>(52,

deck.CardCount,

"Card count was wrong.");

}

- Create the new tests in DeckFixture.cs as shown above

Page 16: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 16 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Deck Class: Enough Code to Compile

For the next step you should try to compile the code. Once again, it won’t compile because you don’t

have any implementation for Deck but once you’ve verified that it doesn’t compile, then you should

write enough code to make it compile.

public class Deck

{

public void Add(Card card)

{

}

public int CardCount { get; set; }

public void Initialize()

{

}

}

- Create a Deck class as shown above

- Compile the code

Page 17: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 17 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Deck Class: Run the Tests

At this point the code should compile and it’s time to run the tests.

- Go to the Test View window

- Select all the tests

- Click Run Selection

Page 18: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 18 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

When the test completes, you should have a number of failing tests.

Page 19: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 19 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Deck Class: Implement Enough Code to Make the Tests Pass

Let’s work on these tests one at a time starting with the AddingUniqueCardToIncrementsTheCardCount

test.

public class Deck

{

public void Add(Card card)

{

Cards.Add(card);

}

public int CardCount

{

get

{

return Cards.Count;

}

}

private List<Card> m_Cards;

public List<Card> Cards

{

get

{

if (m_Cards == null)

{

m_Cards = new List<Card>();

}

return m_Cards;

}

}

public void Initialize()

{

}

}

- Add the highlighted to the Deck class as shown above

- Re-run all the unit tests

Since we’ve implemented the Add() method, the AddingUniqueCardToIncrementsTheCardCount unit

test should be passing.

Page 20: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 20 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Next let’s work on the AddingDuplicateCardThrowsException unit test. This test verifies that we can’t

add duplicate cards to the Deck by attempting to add a duplicate card and expecting an

InvalidOperationException. If the test completes without getting that InvalidOperationException, the

test should fail.

public void Add(Card card)

{

if (IsUnique(card) == false)

{

throw new InvalidOperationException(

"Duplicate card.");

}

else

{

Cards.Add(card);

}

}

private bool IsUnique(Card card)

{

var result = (from item in Cards

where

item.Rank == card.Rank &&

item.Suit == card.Suit

select item).FirstOrDefault();

if (result == null)

{

return true;

}

else

{

return false;

}

}

- Add the highlighted code to the Deck class

- Re-run all the tests

Page 21: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 21 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The AddingDuplicateCardThrowsException test should now be passing.

Page 22: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 22 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Now let’s fix the last test – InitializeCreates52Cards.

public void Initialize()

{

Initialize(Suit.Club);

Initialize(Suit.Diamond);

Initialize(Suit.Heart);

Initialize(Suit.Spade);

}

private void Add(Suit suit, Rank rank)

{

Add(new Card(suit, rank));

}

private void Initialize(Suit suit)

{

Add(suit, Rank.Ace);

Add(suit, Rank.Two);

Add(suit, Rank.Three);

Add(suit, Rank.Four);

Add(suit, Rank.Five);

Add(suit, Rank.Six);

Add(suit, Rank.Seven);

Add(suit, Rank.Eight);

Add(suit, Rank.Nine);

Add(suit, Rank.Ten);

Add(suit, Rank.Jack);

Add(suit, Rank.Queen);

Add(suit, Rank.King);

}

- Add the highlighted code as shown above

- Re-run all the tests

Page 23: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 23 of 88

Copyright © 2013 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Once the test run has completed, you should see that all 5 unit tests are passing.

You’ve just created a Card and Deck class using Test-Driven Development.

Page 24: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 24 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Lab 02: Unit Test the Adapter Pattern

The Adapter Pattern is a design pattern that is helpful for converting one type of object to another. It’s

especially useful when used as part of data access logic. If you want to organize your data access logic

for unit testability, you’ll probably use the Repository Pattern to encapsulate the logic for calling your

database or web service and you’ll use the Adapter Pattern to encapsulate the logic for taking your data

access results and converting them to your Domain Model object (aka. Business objects).

This design not only helps testability but also ensures that your architecture follows the Single

Responsibility Principle (SRP).

Overview of the Solution

Begin by opening the solution.

- Open Visual Studio 2010

- Load DataAccessTesting_AdapterPattern.sln

Page 25: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 25 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

When the solution is loaded, you’ll see that there are 4 projects in the solution.

The Business project contains our Person Domain Model object. The DataAccess project contains the

beginnings of our data access logic. The Interfaces project contains an interface representation of our

Person object. And finally, the Tests project will hold our unit tests.

Page 26: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 26 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

In the DataAccess project, there is a PersonDataSet.xsd typed dataset that has a single DataTable in it

for Person. This is the ADO.NET class that we’ll (eventually) use to talk to the database for saves and

retrieves.

In the Interfaces project, there is an interface called IPerson that is the abstraction of our Person

business object.

The coding and testing goal of this lab is to implement and test the logic that will go into the

PersonToDataSetAdapter class in the DataAccess project that will adapt IPerson to Person DataTable

and vice versa.

Page 27: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 27 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Person to DataRow: Write the Tests

For the first tests, let’s focus on the logic to turn an IPerson instance’s data into a PersonDataRow.

[TestMethod]

public void AdaptFromPersonToDataRow()

{

IPerson fromValue = new Person();

fromValue.Id = 1234;

fromValue.FirstName = "Skip";

fromValue.LastName = "Rosenwinkle";

fromValue.EmailAddress = "[email protected]";

var toValue = new PersonDataSet().Person.NewPersonRow();

var adapter = new PersonToDataSetAdapter();

adapter.Adapt(fromValue, toValue);

AssertAreEqual(fromValue, toValue);

}

private void AssertAreEqual(

IPerson expected, PersonDataSet.PersonRow actual)

{

if (expected == null)

throw new ArgumentNullException("expected", "expected is null.");

if (actual == null)

throw new ArgumentNullException("actual", "actual is null.");

Assert.AreEqual<int>(expected.Id, actual.Id, "Id");

Assert.AreEqual<string>(expected.FirstName,

actual.FirstName, "FirstName");

Assert.AreEqual<string>(expected.LastName,

actual.LastName, "LastName");

Assert.AreEqual<string>(expected.EmailAddress,

actual.EmailAddress, "EmailAddress");

}

- Go to the DataAccessTesting.Tests project

- Open the PersonToDataSetAdapterFixture class

- Add the highlighted code as shown above

So, looking at this code, it’s not trying to do anything that’s all that fancy or exotic. It’s pretty boring

logic, actually. This is important though because it’s usually the boring and tedious code where the

errors live. If you write good unit tests for the code, you practically never have to worry about it again.

Page 28: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 28 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Person to DataRow: Enough Code to Compile and Run the Test

If you try to compile this code, it’ll fail because there’s no implementation for the Adapt() method on

PersonToDataSetAdapter.

namespace DataAccessTesting.DataAccess

{

public class PersonToDataSetAdapter

{

public void Adapt(IPerson fromValue, PersonDataSet.PersonRow toValue)

{

}

}

}

- Open PersonToDataSetAdapter.cs

- Add the highlighted code as shown above

- Compile the Solution

- Run the unit tests

The AdaptFromPersonToDataRow unit test will execute but fail.

Page 29: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 29 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The error message will be complaining that the Id property on the DataRow does not match the

expected value.

Person to DataRow: Enough Code to Pass the Test

Next, you’ll implement the adapter code to make this test actually pass.

namespace DataAccessTesting.DataAccess

{

public class PersonToDataSetAdapter

{

public void Adapt(IPerson fromValue, PersonDataSet.PersonRow toValue)

{

if (fromValue == null)

throw new ArgumentNullException("fromValue",

"fromValue is null.");

if (toValue == null)

throw new ArgumentNullException("toValue", "toValue is null.");

toValue.Id = fromValue.Id;

toValue.FirstName = fromValue.FirstName;

toValue.LastName = fromValue.LastName;

toValue.EmailAddress = fromValue.EmailAddress;

}

}

}

- Open PersonToDataSetAdapter.cs

- Add the highlighted code as shown above

- Compile the code

- Run the unit tests

Page 30: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 30 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

This time the unit test for AdaptFromPersonToDataRow should pass.

You now have working logic to take a Person object and turn it into a DataRow so that you can write it

into a database.

Page 31: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 31 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

DataRow to Person: Write the Tests

Next you’ll start working on the implementation to go in the other direction – logic to take a DataRow

and turn it into a Person. This path through the application supports the retrieval of data from a

database so that it can be consumed by the application code.

You’ll find that this next block of code is almost exactly the same as the other block of code. Boring,

isn’t it? Makes you want to fall asleep and stop paying attention, huh? Well, that’s why you need to

test this code. You’re going to make mistakes and considering how much other code is going to be built

on top of this logic, it needs to work perfectly. The unit tests will give you a high degree of confidence

that the code is working properly.

[TestMethod]

public void AdaptFromDataRowToPerson()

{

var fromValue = new PersonDataSet().Person.NewPersonRow();

fromValue.Id = 1234;

fromValue.FirstName = "Skip";

fromValue.LastName = "Rosenwinkle";

fromValue.EmailAddress = "[email protected]";

IPerson toValue = new Person();

var adapter = new PersonToDataSetAdapter();

adapter.Adapt(fromValue, toValue);

AssertAreEqual(fromValue, toValue);

}

private void AssertAreEqual(

PersonDataSet.PersonRow expected, IPerson actual)

{

if (expected == null)

throw new ArgumentNullException("expected", "expected is null.");

if (actual == null)

throw new ArgumentNullException("actual", "actual is null.");

Assert.AreEqual<int>(expected.Id, actual.Id, "Id");

Assert.AreEqual<string>(expected.FirstName,

actual.FirstName, "FirstName");

Assert.AreEqual<string>(expected.LastName,

actual.LastName, "LastName");

Assert.AreEqual<string>(expected.EmailAddress,

actual.EmailAddress, "EmailAddress");

}

- Go to PersonToDataSetAdapterFixture.cs

- Add the highlighted code as shown above

- Try to compile the code

Page 32: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 32 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

DataRow to Person: Enough Code to Compile and Run the Test

public class PersonToDataSetAdapter

{

public void Adapt(IPerson fromValue, PersonDataSet.PersonRow toValue)

{

if (fromValue == null)

throw new ArgumentNullException("fromValue", "fromValue is null.");

if (toValue == null)

throw new ArgumentNullException("toValue", "toValue is null.");

toValue.Id = fromValue.Id;

toValue.FirstName = fromValue.FirstName;

toValue.LastName = fromValue.LastName;

toValue.EmailAddress = fromValue.EmailAddress;

}

public void Adapt(PersonDataSet.PersonRow fromValue, IPerson toValue)

{

}

}

- Go to PersonToDataSetAdapter

- Add the highlighted code as shown above

- Compile the code

- Run the unit tests

The unit tests are failing because there’s no implementation.

Page 33: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 33 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

DataRow to Person: Enough Code to Pass the Test

You’re almost done. All you need is a little bit of implementation.

public void Adapt(PersonDataSet.PersonRow fromValue, IPerson toValue)

{

if (fromValue == null)

throw new ArgumentNullException("fromValue", "fromValue is null.");

if (toValue == null)

throw new ArgumentNullException("toValue", "toValue is null.");

toValue.Id = fromValue.Id;

toValue.FirstName = fromValue.FirstName;

toValue.LastName = fromValue.LastName;

toValue.EmailAddress = fromValue.EmailAddress;

}

- Go to PersonToDataSetAdapter.cs

- Add the highlighted code as shown above

- Run the unit tests

This time the unit tests pass.

You’ve implemented the basic structure of the Adapter pattern for use in your data access logic.

Page 34: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 34 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Lab 03: Unit Test the Repository Pattern

The Repository Pattern helps you to encapsulate data access logic for accessing persistent storage such

as a database or accessing a web service. In an application that uses the Domain Model Pattern (aka.

“business” objects), the Repository is responsible for 1) saving Domain Model objects to persistent

storage and 2) returning populated instances of Domain Model objects based on data retrieved from

persistent storage.

If you want to organize your data access logic for unit testability, you’ll frequently use Adapter Pattern

objects to handle the conversion of data access results into Domain Model objects and vice versa. In the

case of a database application, the Repository classes know how to generate and run SQL queries

(INSERTs, UPDATEs, DELETEs) and the Adapter pattern knows how to take ADO.NET results and convert

them to/from Domain Model classes.

This design not only helps testability but also ensures that your architecture follows the Single

Responsibility Principle (SRP).

Prerequisite

This lab assumes that you have installed SQL Server Express on your local machine and that it is available

as “(local)\SQLEXPRESS” using your current Windows login credentials.

Overview of the Solution

Begin by opening the solution.

- Open Visual Studio 2010

- Load DataAccessTesting_RepositoryPattern.sln

Page 35: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 35 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

When the solution is loaded, you’ll see that there are 4 projects in the solution.

The Business project contains our Person Domain Model object. The DataAccess project contains the

beginnings of our data access logic. The Interfaces project contains an interface representation of our

Person object. And finally, the Tests project will hold our unit tests.

Page 36: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 36 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

In the DataAccess project, there is a PersonDataSet.xsd typed dataset that has a single DataTable in it

for Person as well as a PersonTableAdapter that we’ll use for talking to the database.

In the Interfaces project, there is an interface called IPerson that is the abstraction of our Person

business object.

The coding and testing goal of this lab is to implement and test the logic that will go into the

SqlPersonRepository class in the DataAccess project. (For information about how to implement the

Adapter logic, see the “Unit Test the Adapter Pattern” lab.)

Page 37: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 37 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Requirements for the Repository

The Repository that we’re going to write and test is for loading and saving objects that implement

IPerson. The Person repository is going to need to:

1) Retrieve all person records as a list of IPerson objects

2) Get a single person record by id and return an IPerson object

3) Save an IPerson object to the database

Create the Database

This lab will be writing to a database so first you’ll need to run the initialization script.

- Under Solution Items, double-click VS2010TestinDatabaseCreate.sql to open the database

initialization script

Page 38: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 38 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The initialization script should be visible in a Visual Studio editor window similar to the one below.

- Click the Execute SQL button in the toolbar

You should now see a Connect to Database Engine dialog that will let you choose which database to run

the script against.

- In the Server name textbox, type (local)\SQLEXPRESS

- Click the Connect button

Page 39: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 39 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

If the query execution was successful, you should see a message saying that the Command(s) completed

successfully.

Brainstorming the Tests

In order to implement the requirements, we’re going to need to test at least 4 different things.

1) When we save an IPerson to the database, does the Id property on IPerson get updated with the

new @@IDENTITY value from the database

2) Can we load an IPerson by Id

3) Can we load a list of all IPerson objects

4) Making modifications to an instance of IPerson and then updating an existing record in the

database

4 unit tests:

1) SavedPersonDoesNotHaveZeroAsAnIdValue() – write a record to the database and make sure

that the Id property is updated.

2) GetPersonById() – retrieve a single IPerson by id value. This test is also essential for being able

to verify that our Save logic works because we’ll use this logic to reload saved instances from the

database and then check against what we saved.

3) GetAllPersons() – retrieve a list of all person records in the database as IPerson.

4) ModifyPerson() – write a record to the database, modify it, and then verify that the

modifications went in as a database UPDATE rather than a database INSERT.

Page 40: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 40 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Save: Write the Test

Let’s start with the SavedPersonDoesNotHaveZeroAsAnIdValue() test. This test will create an instance of

IPerson, write it to the database using the SqlPersonRepository class, and then verify that the Id value is

updated on the instance of IPerson.

- Open SqlPersonRepositoryFixture.cs

Page 41: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 41 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

[TestClass]

public class SqlPersonRepositoryFixture

{

[TestMethod]

public void SavedPersonDoesNotHaveZeroAsAnIdValue()

{

var instance = CreateAndSavePerson();

Assert.AreNotEqual<int>(0, instance.Id,

"Person Id value should not be zero.");

}

[TestInitialize]

public void Initialize()

{

m_RepositoryInstance = null;

}

private IPersonRepository m_RepositoryInstance;

public IPersonRepository RepositoryInstance

{

get

{

if (m_RepositoryInstance == null)

{

m_RepositoryInstance = new SqlPersonRepository<Person>();

}

return m_RepositoryInstance;

}

}

public IPerson CreateAndSavePerson()

{

IPerson instance = new Person();

instance.FirstName = UnitTestUtility.GetUniqueString();

instance.LastName = UnitTestUtility.GetUniqueString();

instance.EmailAddress = UnitTestUtility.GetUniqueString();

RepositoryInstance.Save(instance);

return instance;

}

[TestMethod]

public void GetPersonById()

{

Assert.Inconclusive();

}

- Add the highlighted code to SqlPersonRepositoryFixture.cs as shown above

- Compile the Solution

Page 42: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 42 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Save: Write Enough Code to Compile

At the moment, the compilation should fail because there isn’t a class named

SqlPersonRepositoryFixture.

- Add a class named SqlPersonRepository to the DataAccessTesting.DataAccess project

You should now have a SqlPersonRepository.cs class in your project as shown below.

Page 43: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 43 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Now you’ll need to create enough implementation code to make it 1) implement the IPersonRepository

interface and 2) compile.

using System;

using System.Collections.Generic;

using System.Linq;

using DataAccessTesting.Interfaces;

namespace DataAccessTesting.DataAccess

{

public class SqlPersonRepository<T> : IPersonRepository

where T : IPerson, new()

{

public IList<IPerson> GetAll()

{

throw new NotImplementedException();

}

public IPerson GetById(int id)

{

throw new NotImplementedException();

}

public void Save(IPerson saveThis)

{

}

}

}

- Add the highlighted code to SqlPersonRepository as shown above

- Compile the solution

Page 44: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 44 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Save: Write Enough Code to Make the Tests Pass

The code should compile successfully now so it’s time to run the tests.

- Run all the tests from the Test View window

The SavedPersonDoesNotHaveZeroAsAnIdValue test should be failing.

The error message from the test should be complain that “Person Id value should not be zero.”

Page 45: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 45 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

To fix this, you’ll add an implementation for the Save() method.

public void Save(IPerson saveThis)

{

if (saveThis == null)

throw new ArgumentNullException("saveThis", "saveThis is null.");

Insert(saveThis);

}

private void Insert(IPerson saveThis)

{

var dataset = new PersonDataSet();

var toValue = dataset.Person.NewPersonRow();

var adapter = new PersonToDataSetAdapter();

adapter.Adapt(saveThis, toValue);

dataset.Person.AddPersonRow(toValue);

Save(dataset);

adapter.Adapt(toValue, saveThis);

}

private void Save(PersonDataSet dataset)

{

var tableAdapter =

new PersonDataSetTableAdapters.PersonTableAdapter();

tableAdapter.Update(dataset.Person);

}

- Add the highlighted code shown above to SqlPersonRepository

- Compile the Solution

- Run all the tests

Page 46: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 46 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The test for SavedPersonDoesNotHaveZeroAsAnIdValue is now passing but the rest of the repository

tests are failing with Inconclusive.

Page 47: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 47 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Get By Id: Write & Run the Test

Next up, let’s work on the GetById() logic.

[TestMethod]

public void GetPersonById()

{

var instance = CreateAndSavePerson();

// reload the person

IPerson reloadedPerson =

RepositoryInstance.GetById(instance.Id);

AssertAreEqual(instance, reloadedPerson);

}

private void AssertAreEqual(IPerson expected, IPerson actual)

{

Assert.AreEqual<int>(expected.Id,

actual.Id,

"Id");

Assert.AreEqual<string>(expected.FirstName,

actual.FirstName,

"FirstName");

Assert.AreEqual<string>(expected.LastName,

actual.LastName,

"LastName");

Assert.AreEqual<string>(expected.EmailAddress,

actual.EmailAddress,

"EmailAddress");

}

- Go to SqlPersonRepositoryFixture.cs

- Add the highlighted test code as shown above

- Run the tests

The unit test fails because there’s no implementation.

Page 48: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 48 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Get By Id: Enough Code to Make the Test Pass

Next step, provide the implementation for GetById() in the SqlPersonRepository class.

public IPerson GetById(int id)

{

var tableAdapter =

new PersonDataSetTableAdapters.PersonTableAdapter();

var results = tableAdapter.GetDataById(id);

if (results == null || results.Count == 0)

{

return null;

}

else

{

var adapter = new PersonToDataSetAdapter();

IPerson toValue = new T();

adapter.Adapt(results[0], toValue);

return toValue;

}

}

- Go to SqlPersonRepository.cs

- Add the highlighted code as shown above

- Compile the solution

- Run the tests

Page 49: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 49 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The GetPersonById() test method is now passing.

Page 50: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 50 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Get List: Write the Test and Run It

Next up, implement the GetAllPerson() test.

[TestMethod]

public void GetAllPersons()

{

// make sure there are at least 2 person records in the database

CreateAndSavePerson();

CreateAndSavePerson();

var allPersons = RepositoryInstance.GetAll();

Assert.IsNotNull(allPersons, "Got back a null result.");

Assert.IsTrue(allPersons.Count >= 2,

"Expected at least 2 person records but there were only '{0}'.",

allPersons.Count);

}

- Go to SqlPersonRepositoryFixture.cs

- Add the highlighted code as shown above

- Compile the solution

It doesn’t compile yet so next add the implementation.

public IList<IPerson> GetAll()

{

var tableAdapter =

new PersonDataSetTableAdapters.PersonTableAdapter();

var results = tableAdapter.GetData();

if (results == null || results.Count == 0)

{

return null;

}

else

{

var adapter = new PersonToDataSetAdapter();

var toValues = new List<IPerson>();

adapter.Adapt<T>(results, toValues);

return toValues;

}

}

- Go to SqlPersonRepository.cs

- Add the highlighted code as shown above

- Run the unit tests

Page 51: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 51 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The GetAllPersons test should now be passing.

Modify Person: Write & Run the Test Just one test left and we’ll have a complete Repository! This one will implement the case to modify an

existing record in the database.

[TestMethod]

public void ModifyPerson()

{

var instance = CreateAndSavePerson();

// reload the person to get a copy

IPerson clone =

RepositoryInstance.GetById(instance.Id);

// modify the original

instance.FirstName = UnitTestUtility.GetUniqueString();

instance.LastName = UnitTestUtility.GetUniqueString();

instance.EmailAddress = UnitTestUtility.GetUniqueString();

RepositoryInstance.Save(instance);

Assert.AreEqual<int>(clone.Id,

instance.Id,

"Id value should not change after saving a modification.");

AssertAreEqual(instance, RepositoryInstance.GetById(instance.Id));

}

- Go to SqlPersonRepositoryFixture.cs

- Add the highlighted code as shown above

Page 52: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 52 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Next, add the implementation.

public void Save(IPerson saveThis)

{

if (saveThis == null)

throw new ArgumentNullException("saveThis", "saveThis is null.");

if (saveThis.Id == 0)

{

Insert(saveThis);

}

else

{

Update(saveThis);

}

}

private void Update(IPerson saveThis)

{

var tableAdapter =

new PersonDataSetTableAdapters.PersonTableAdapter();

var dataset = new PersonDataSet();

tableAdapter.FillById(dataset.Person, saveThis.Id);

if (dataset.Person == null || dataset.Person.Count == 0)

{

throw new InvalidOperationException("Unknown person.");

}

else

{

var adapter = new PersonToDataSetAdapter();

adapter.Adapt(saveThis, dataset.Person[0], true);

Save(dataset);

}

}

- Go to SqlPersonRepository.cs

- Add the highlighted code as shown above

- Run all the tests

Page 53: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 53 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The tests are all passing and you’ve finished implementing the Repository pattern.

Page 54: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 54 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Lab 04: Test Logic in Isolation Using Mock Objects

Introduction

There’s a reason why they’re called unit tests. You’re trying to test small units of functionality. It’s easy

to say that you should test isolated units of functionality but a lot of times – most of the time, actually –

the code we want to test is pretty complex and often has dependencies on other classes. Getting our

test set up and ready to go can be a real pain sometimes because of all the little dependencies from

object to object.

A great way to keep focused on unit tests and isolated logic is using the Dependency Injection Pattern

(DI) and mock objects. Dependency Injection is a way of structuring your objects so that any

dependencies that they have on other objects are explicitly called out on their constructor. If a

PersonManager object requires an instance of IPersonRepository in order to save to a database, then

the constructor for the PersonManager object will require a non-null instance of IPersonRepository to

be supplied. So, rather than having PersonManager go out and get an instance of IPersonRepository

itself, that constructor now requires us to inject the dependency.

If you’re doing Dependency Injection, you’re typically using interface types on the constructors rather

than concrete types. For example, IPersonRepository rather than Oracle9iPersonRepository,

SqlPersonRepository, or XmlFileSystemPersonRepository. So, what does this have to do with unit

testing? Well, since PersonManager only cares that the repository implements IPersonRepository rather

than any particular concrete implementation, we can inject simplified, faked out versions of the

repository during our tests called Mocks.

These mock objects implement the required interfaces and provide simplified, canned answers to

method calls. Having a simple object that provides canned answers allows us to quickly and easily

simulate certain scenarios without having to spend a ton of time – for example – loading up a database

with the particular data in order to support the scenario that we need to test. Want to simulate a

duplicate record in the database? Have the mock repository report that there’s a duplicate record.

Want to simulate the saving of new record in the database? Have the mock repository simulate that

insert.

By using simplified mock objects, you can focus on testing only the code that you really care about.

Lab Overview

In this lab, you’ll work on testing a class named CustomerManager. The code you’ll work on in this lab is

focused on the generation of unique customer Account Codes. Our fictional company has a 6 character

account code that is generated for each new customer. This account code has to be unique for each

customer and it’s CustomerManager’s job to generate account codes and to make sure that nothing is

saved with a duplicate account code. In order to do this work, it relies on ICustomerRepository’s

IsAccountCodeInUse() method to determine if a code is unique.

Page 55: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 55 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

In this test, we simply don’t care how ICustomerRepository is implemented. We only care about the

logic that happens in CustomerManager itself. Therefore, there’s no need to have a real database-based

implementation of ICustomerRepository. Plus, some of the cases we need to test are fairly complex so

it’s actually easier to skip the database dependency altogether.

Brainstorming the Tests

CustomerManager needs to be able to generate a suggested account code. We’ll also need it to check if

an account code is in use. If the code is in use, we’ll need it to retry up to 10 times until it gets a unique

code. We’ll also have a CreateNewCustomer() method that will save the new customer to the

repository but before it does it’ll check that the code is unique. If the code is unique, it’ll save. If the

code is not unique, it’ll throw an exception.

That sounds like probably 6 tests:

1. CreateSuggestedCustomerAccountCode()

2. CreateCustomerWithUniqueAccountCodeSucceeds()

3. CreateAccountCodeSucceedsWhenCodeIsUnique()

4. CreateAccountCodeRetriesWhenOriginalIsInUse()

5. CreateAccountCodeRetriesUntilCodeIsFound()

6. CreateAccountCodeThrowsExceptionAfter9Tries()

Create a Mock ICustomerRepository

Let’s say that someone’s already done some work on CustomerManager and they’ve already

implemented enough so that there’s a constructor. The constructor requires a non-null instance of

ICustomerRepository.

The first test you need to work on is just a simple method to generate suggested Account Codes and

there’s no database access required to implement that. Unfortunately, there’s no way to create an

instance of CustomerManager unless you pass in that instance of ICustomerRepository.

Page 56: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 56 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

To make things worse, you don’t want to use the existing implementation of ICustomerRepository

because it’s got insane amounts of complexity to it and using it will add way too much complexity to

your test.

You need your own mock version of repository.

- Go to the Labs.Tests project

- Create a Class named MockCustomerRepository.cs

Page 57: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 57 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

public class MockCustomerRepository : ICustomerRepository

{

public MockCustomerRepository()

{

}

public bool IsAccountCodeInUse(string accountCode)

{

throw new NotImplementedException();

}

public void SaveCustomer(string accountCode, string customerName)

{

throw new NotImplementedException();

}

}

- Add the highlighted code to MockCustomerRepository as shown above

Now that this MockCustomerRepository exists, you have a class that you can pass in to the constructor

for CustomerManager. It doesn’t do anything but it will satisfy the requirements of the constructor for

the purpose of our first test.

Create Suggested Account Code: Write the Test and Implementation

First write the test.

[TestMethod]

public void CreateSuggestedCustomerAccountCode()

{

var manager = new CustomerManager(

new MockCustomerRepository());

string result = manager.GetSuggestedAccountCode();

Assert.IsFalse(String.IsNullOrWhiteSpace(result),

"Result should not be null or whitespace.");

Assert.AreEqual<int>(6, result.Trim().Length,

"Account code should be 6 characters long.");

}

- Open to CustomerManagerFixture.cs

- Add the highlighted code to the CreateSuggestedCustomerAccountCode() test as shown above

Page 58: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 58 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Next, implement the GetSuggestedAccountCode() method on CustomerManager.

public class CustomerManager

{

public CustomerManager(ICustomerRepository instance)

{

if (instance == null)

throw new ArgumentNullException("instance",

"instance is null.");

m_RepositoryInstance = instance;

}

public string GetSuggestedAccountCode()

{

string guid = Guid.NewGuid().ToString();

return guid.Substring(0, 6);

}

private ICustomerRepository m_RepositoryInstance;

private ICustomerRepository RepositoryInstance

{

get { return m_RepositoryInstance; }

}

}

- Open CustomerManager.cs

- Add the highlighted code as shown above

- Run the unit tests

The CreateSuggestedCustomerAccountCode test should be passing.

Page 59: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 59 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Create Account Code: Write the Test

That first test was pretty easy. It simply tested the logic to generate account code values. Next it’s time

to write a test that will test the process of getting a suggested account code, verifying that it is unique,

and saving it to the repository.

[TestMethod]

public void CreateAccountCodeSucceedsWhenCodeIsUnique()

{

var mockRepository = new MockCustomerRepository();

var manager = new CustomerManager(mockRepository);

string result = manager.CreateAccountCode("Customer 123");

Assert.IsFalse(String.IsNullOrWhiteSpace(result),

"Result should not be null or whitespace.");

mockRepository.AssertSaveCustomerWasCalled();

mockRepository.AssertIsAccountCodeInUseWasCalled();

}

- Add the highlighted test code as shown above

Notice those last two lines of the test. We’re going to use the MockCustomerRepository to verify that

the CustomerManager made the calls that we expected.

- Compile the code

Page 60: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 60 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Create Account Code: Write Enough Code to Compile

The code compile should be failing right now so now you’ll add the implementation to

MockCustomerRepository and to CustomerManager.

using System;

using System.Collections.Generic;

using System.Linq;

using Labs.Interfaces;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Labs.Tests

{

public class MockCustomerRepository : ICustomerRepository

{

public MockCustomerRepository()

{

}

private bool m_WasIsAccountCodeInUseCalled = false;

public bool IsAccountCodeInUse(string accountCode)

{

m_WasIsAccountCodeInUseCalled = true;

return false;

}

private bool m_WasSaveCustomerCalled = false;

public void SaveCustomer(string accountCode, string customerName)

{

m_WasSaveCustomerCalled = true;

}

public void AssertSaveCustomerWasCalled()

{

Assert.IsTrue(m_WasSaveCustomerCalled,

"SaveCustomer() was not called.");

}

public void AssertIsAccountCodeInUseWasCalled()

{

Assert.IsTrue(m_WasIsAccountCodeInUseCalled,

"IsAccountCodeInUse() was not called.");

}

}

}

- Add the highlighted code to MockCustomerRepository.cs as shown above

Page 61: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 61 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Next let’s add just enough code to compile to CustomerManager.

public class CustomerManager

{

public CustomerManager(ICustomerRepository instance)

{

if (instance == null)

throw new ArgumentNullException("instance",

"instance is null.");

m_RepositoryInstance = instance;

}

public string CreateAccountCode(string customerName)

{

return null;

}

public string GetSuggestedAccountCode()

{

string guid = Guid.NewGuid().ToString();

return guid.Substring(0, 6);

}

private ICustomerRepository m_RepositoryInstance;

private ICustomerRepository RepositoryInstance

{

get { return m_RepositoryInstance; }

}

}

- Add the highlighted code to CustomerManager.cs as shown above

- Compile the code

- Run the tests

Page 62: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 62 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The CreateAccountCodeSucceedsWhenCodeIsUnique test is failing because the current implementation

returns a null Account Code.

Create Account Code: Write Enough to Make the Test Pass

Now let’s implement the code to make this unit test pass.

public string CreateAccountCode(string customerName)

{

if (String.IsNullOrEmpty(customerName) == true)

throw new ArgumentException(

"customerName is null or empty.", "customerName");

var suggestedAccountCode = GetSuggestedAccountCode();

if (RepositoryInstance.IsAccountCodeInUse(

suggestedAccountCode) == true)

{

throw new InvalidOperationException("Duplicate account code.");

}

else

{

RepositoryInstance.SaveCustomer(

suggestedAccountCode, customerName);

return suggestedAccountCode;

}

}

- Add the highlighted code to CustomerManager.cs as shown above

- Compile the code

- Run the tests

Page 63: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 63 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

This test now passes.

Retry Logic: Write the Test The last two tests start to get a little more intricate because of the “retry” logic. Fortunately, the

MockCustomerRepository class will help us to keep the logic organized and keep the test code from

getting overly complex.

One thing we’ll need is a way to easily get the repository to say that an Account Code is already taken.

We also need to check that the CustomerManager is generating new AccountCodes each time it checks

for uniqueness. Finally, we’ll need a way to verify that the retry logic is triggered 2 times.

[TestMethod]

public void CreateAccountCodeRetriesWhenOriginalIsInUse()

{

var mockRepository = new MockCustomerRepository();

// number of attempts required to get a unique code

mockRepository.NumberOfRequiredIsAccountCodeInUseAttempts = 2;

var manager = new CustomerManager(mockRepository);

string result = manager.CreateAccountCode("Customer 123");

Assert.IsFalse(String.IsNullOrWhiteSpace(result),

"Result should not be null or whitespace.");

mockRepository.AssertSaveCustomerWasCalled();

// check that it was called twice

mockRepository.AssertIsAccountCodeInUseWasCalled(2);

mockRepository.AssertReceivedAccountCodesWereDifferent();

}

- Add the highlighted test code as shown above

Page 64: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 64 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

There are a handful of things that we’re adding to this test.

1. We’re adding logic to the MockCustomerRepository to simulate IsAccountCodeInUse() results

using the NumberOfRequiredIsAccountCodeInUseAttempts property. Basically, we want the

MockCustomerRepository to report that an Account Code is in use until it has received X

number of attempts. We don’t really care about whether they’re really unique because we’re

only looking for the repository to *emulate* the behavior.

2. We’re adding an overload of the AssertIsAccounCodeInUseWasCalled() method that takes an

integer. This argument will allow us to ask the repository if it was called the right number of

times. In this test, we want the first check to say that the code is in use and then verify that the

retry logic was called for a second (successful) attempt.

3. The AssertReceivedAccountCodesWereDifferent() method will check that the account codes

that were checked for uniqueness changed with each attempt.

So, the [TestMethod] method is staying fairly simple but we’re adding some test-specific functionality to

the repository.

Page 65: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 65 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Retry Logic: Implement the Changes to MockCustomerRepository

Let’s implement the MockCustomerRepository changes.

public class MockCustomerRepository : ICustomerRepository

{

public MockCustomerRepository()

{

}

private bool m_WasIsAccountCodeInUseCalled = false;

private List<string> m_IsAccountCodeInUseAttemptValues =

new List<string>();

public bool IsAccountCodeInUse(string accountCode)

{

m_IsAccountCodeInUseAttemptValues.Add(accountCode);

m_WasIsAccountCodeInUseCalled = true;

if (NumberOfRequiredIsAccountCodeInUseAttempts == 0 ||

m_IsAccountCodeInUseAttemptValues.Count >=

NumberOfRequiredIsAccountCodeInUseAttempts)

{

return false;

}

else

{

return true;

}

}

public int NumberOfRequiredIsAccountCodeInUseAttempts { get; set; }

public void AssertIsAccountCodeInUseWasCalled(int expectedCount)

{

Assert.AreEqual<int>(expectedCount,

m_IsAccountCodeInUseAttemptValues.Count,

"Method was not called the expected number of times.");

}

public void AssertReceivedAccountCodesWereDifferent()

{

CollectionAssert.AllItemsAreUnique(

m_IsAccountCodeInUseAttemptValues,

"IsAccountCodeInUse values should have been unique.");

}

private bool m_WasSaveCustomerCalled = false;

public void SaveCustomer(string accountCode, string customerName)

{

...

- Add the highlighted code to MockCustomerRepository.cs as shown above

- Run the tests

Page 66: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 66 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The CreateAccountCodeRetriesWhenOriginalIsInUse() test should be failing and the previous two tests

you worked on should still be passing. The fact that the previous two tests are still passing is very

important because the changes you just made to the MockCustomerRepository changed the logic of the

previous tests a little bit.

The error message that you should see for CreateAccountCodeRetriesWhenOriginalIsInUse should say

System.InvalidOperationException: Duplicate account code. This is happening because the current

logic simply throws an exception when it finds a non-unique account code. Now, we could have just

written the retry logic into the CustomerManager from the beginning but this would have violated Test-

Driven Development because we wouldn’t have any test for this logic. Now that we have the test for

this retry logic, we can proceed with the implementation.

Page 67: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 67 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Retry Logic: Write Enough to Make the Test Pass

Asdf

public string CreateAccountCode(string customerName)

{

if (String.IsNullOrEmpty(customerName) == true)

throw new ArgumentException(

"customerName is null or empty.", "customerName");

bool isValid = false;

var suggestedAccountCode = GetSuggestedAccountCode();

if (RepositoryInstance.IsAccountCodeInUse(

suggestedAccountCode) == true)

{

suggestedAccountCode = GetSuggestedAccountCode();

if (RepositoryInstance.IsAccountCodeInUse(

suggestedAccountCode) == false)

{

isValid = true;

}

}

else

{

isValid = true;

}

if (isValid == true)

{

RepositoryInstance.SaveCustomer(

suggestedAccountCode, customerName);

return suggestedAccountCode;

}

else

{

throw new InvalidOperationException("Duplicate account code.");

}

}

- Open CustomerManager.cs

- Modify the CreateAccountCode(string customerName) method with the highlighted changes

- Run the tests

Page 68: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 68 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The CreateAccountCodeRetriesWhenOriginalIsInUse test should be passing.

Page 69: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 69 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Multiple Retry Logic: Write the Test

Only one test left to do. We’ve got the retry logic done but there’s still the case of multiple required

retry attempts.

From the perspective of the test code, there really isn’t much difference between running the test for 2

required IsAccountCodeInUse attempts and 10 attempts . So what we really need to do here is, extract

the test code from CreateAccountCodeRetriesWhenOriginalIsInUse() into a re-usable method (see code

sample below). This is what is known as an Extract Method refactoring.

There are two ways to do this:

1. Do it by hand

2. Use the Visual Studio 2010 Refactoring menu

To use the Visual Studio refactoring, you’ll select the contents of the

CreateAccountCodeRetriesWhenOriginalIsInUse method, go to the Refactor menu, and then choose

Extract Method.

Page 70: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 70 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

However you choose to do it, when you’re done, the code should look like the sample below.

[TestMethod]

public void CreateAccountCodeRetriesWhenOriginalIsInUse()

{

RunAndAssertIsAccountCodeInUseForNumberOfAttempts(2);

}

[TestMethod]

public void CreateAccountCodeRetriesUntilUniqueCodeIsFound()

{

RunAndAssertIsAccountCodeInUseForNumberOfAttempts(10);

}

private void RunAndAssertIsAccountCodeInUseForNumberOfAttempts(

int attemptCount)

{

var mockRepository = new MockCustomerRepository();

// number of attempts required to get a unique code

mockRepository.NumberOfRequiredIsAccountCodeInUseAttempts =

attemptCount;

var manager = new CustomerManager(mockRepository);

string result = manager.CreateAccountCode("Customer 123");

Assert.IsFalse(String.IsNullOrWhiteSpace(result),

"Result should not be null or whitespace.");

mockRepository.AssertSaveCustomerWasCalled();

// check that it was called twice

mockRepository.AssertIsAccountCodeInUseWasCalled(attemptCount);

mockRepository.AssertReceivedAccountCodesWereDifferent();

}

- Modify the code to look like the code above. (NOTE: don’t forget to change the code to use the

attemptCount variables!)

- Run the tests

Page 71: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 71 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The CreateAccountCodeRetriesUntilCodeIsFound test is failing with a Duplicate account code error

message.

This is happening because we haven’t written any ‘multiple retry’ code and currently have only single-

retry code.

Page 72: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 72 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Multiple Retry Logic: Write Enough to Make the Test Pass

Almost done. Just a few changes to CustomerManager to go.

public string CreateAccountCode(string customerName)

{

if (String.IsNullOrEmpty(customerName) == true)

throw new ArgumentException(

"customerName is null or empty.", "customerName");

bool isValid = false;

string suggestedAccountCode = null;

while (isValid == false)

{

suggestedAccountCode = GetSuggestedAccountCode();

if (RepositoryInstance.IsAccountCodeInUse(

suggestedAccountCode) == false)

{

isValid = true;

}

}

if (isValid == true)

{

RepositoryInstance.SaveCustomer(

suggestedAccountCode, customerName);

return suggestedAccountCode;

}

else

{

throw new InvalidOperationException("Duplicate account code.");

}

}

- Modify the CreateAccountCode() method with the highlighted code as shown above

- Run the tests

Page 73: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 73 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

All the tests should now be passing.

You’ve just implemented a suite of features in the CustomerManager class without needing a database

and without requiring any database setup logic.

Page 74: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 74 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Lab 05: Unit Test a User Interface Using the Model-View-Presenter Pattern

Prerequisite

This lab uses the SQL Server Northwind sample database.

1. Create a directory on your machine at C:\code\labs\northwind

2. Place a copy of the Northwind database files into this directory

Introduction Up to now, we’ve been living in the world of basic unit testing. When you’re developing a multi-tier

application, it’s pretty important in order to ensure quality but it can be pretty abstract.

What about something a little more “real”? Users aren’t going to be interacting with the application

through your unit tests. How would you test a screen in a user interface?

Well, if you’re writing a web application, Visual Studio Ultimate allows you to write Web Tests or Coded

UI tests that will exercise your UI. Unfortunately, both of these are fairly high-level and don’t give you

the granularity to really dive in and do serious testing.

Let’s face it – unit testing a user interface is difficult. User interfaces are built for users and users are

human. We’re good at locating things on the screen and figuring out what’s going on. Computers are

powerful but dumb. In order to let them unit test our UIs, we need to spell everything out simply and

clearly.

The real answer is to design your user interfaces for testability and one of the best ways to do this is

using a user interface interaction design pattern like Model-View-Presenter (MVP). MVP is a variation

of the Model-View-Controller Pattern. MVP is particularly well suited to modeling user interfaces that

will be implemented using ASP.NET Web Forms or Windows Forms.

The MVP consists of 3 parts:

1) The Model which represents your business objects and/or data.

2) The View which is an abstraction of your user interface screen.

3) The Presenter which contains all the logic for the various actions your View needs to perform

and is responsible for marshaling database between the View and the Model.

The really key concept is that your user interface (ASP.NET, Windows Form, etc) will have practically no

code in the “code behind” other than a few simple calls to talk to the Presenter. Since the Presenter will

have no knowledge of how the Views are actually implemented, they’re very easy to test using ordinary

unit tests.

Page 75: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 75 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Requirements

A lot of times, when you need to develop a user interface, a graphic designer or someone else will hand

you a picture of what they want the screen to look like. This is a good place to figure out what your

screen has to do and what all the values and operations are that will need to be tested.

The image below represents the screen mockup that’s just been handed to us to develop. It’s a search

screen for the company database.

The user will type in a company name search in the search box, press the Search button and display the

results.

In order to accomplish this, there are only 2 pieces of data that get passed around: the search string, and

the DataTable containing the results.

Page 76: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 76 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The Unit Tests & Code Structure

In order to meet these requirements, you going to need a [TestClass] called

CustomerSearchPresenterFixture and four tests:

1) CheckInitialize() – to verify that the screen is properly initialized at startup

2) SearchForUnknownValueReturnsNoResults()

3) SearchForKnownRecordsReturnsResults()

4) SearchUsingEmptyStringReturnsNoResults()

Page 77: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 77 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The implementation will go into the Labs.Business project and will use ICustomerSearchView to

represent the view and CustomerSearchPresenter to contain the logic for the screen.

The final steps of this lab will walk you through implementing the ASP.NET version of this functionality in

the Labs.WebUI project.

Page 78: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 78 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Write the Tests

Let’s write the code for the tests in CustomerSearchPresenterFixture.cs.

- Open CustomerSearchPresenterFixture.cs

First the CheckInitialize() method.

[TestMethod]

public void CheckInitialize()

{

ICustomerSearchView view = new CustomerSearchViewStub();

var presenter = new CustomerSearchPresenter();

presenter.Initialize(view);

Assert.AreEqual<string>(String.Empty, view.SearchValue,

"SearchValue was not empty.");

Assert.IsNull(view.Results, "Results should be null.");

}

- Add the highlighted implementation as shown above

Next the SearchForUnknownValueReturnsNoResults() method.

[TestMethod]

public void SearchForUnknownValueReturnsNoResults()

{

ICustomerSearchView view = new CustomerSearchViewStub();

var presenter = new CustomerSearchPresenter();

presenter.Initialize(view);

// search for something that doesn't exist

view.SearchValue = Guid.NewGuid().ToString();

presenter.Search(view);

Assert.IsNotNull(view.Results, "Results was null.");

Assert.AreEqual<int>(0, view.Results.Count,

"Row count was wrong.");

}

- Add the highlighted implementation as shown above

Page 79: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 79 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The SearchForKnownRecordsReturnsResults() method.

[TestMethod]

public void SearchForKnownRecordsReturnsResults()

{

ICustomerSearchView view = new CustomerSearchViewStub();

var presenter = new CustomerSearchPresenter();

presenter.Initialize(view);

// search for known records

view.SearchValue = "super";

presenter.Search(view);

Assert.IsNotNull(view.Results, "Results was null.");

Assert.AreEqual<int>(2, view.Results.Count,

"Row count was wrong.");

}

- Add the highlighted implementation as shown above

And finally, the SearchUsingEmptyStringReturnsNoResults() method.

[TestMethod]

public void SearchUsingEmptyStringReturnsNoResults()

{

ICustomerSearchView view = new CustomerSearchViewStub();

var presenter = new CustomerSearchPresenter();

presenter.Initialize(view);

// empty search should return no records

view.SearchValue = "";

presenter.Search(view);

Assert.IsNotNull(view.Results, "Results was null.");

Assert.AreEqual<int>(0, view.Results.Count,

"Row count was wrong.");

}

- Add the highlighted implementation as shown above

Now that you’ve entered the test code it’s time to try to compile.

- Compile the code

Page 80: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 80 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The build succeeded. If you’ve done all the labs to date, I’ll be you didn’t expect that to happen.

The enough code has already been created in order to make the projects compile.

- Run the unit tests

All the tests should be failing.

Page 81: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 81 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Implement the CustomerSearchPresenter Code

At the moment, CustomerSearchPresenter has no implementation and that’s why the tests are failing.

First, implement the code for the Initialize() method.

public void Initialize(ICustomerSearchView view)

{

if (view == null)

{

throw new ArgumentNullException(

"search", "Argument cannot be null.");

}

view.SearchValue = String.Empty;

view.Results = null;

}

- Open CustomerSearchPresenter.cs

- Add the highlighted code as shown above

Page 82: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 82 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Next, implement the Search() method.

public void Search(ICustomerSearchView view)

{

if (view == null)

{

throw new ArgumentNullException("search",

"Argument cannot be null.");

}

if (String.IsNullOrEmpty(view.SearchValue) == true)

{

// empty result set

view.Results =

new NorthwindDataset.CustomersDataTable();

}

else

{

CustomersTableAdapter tableAdapter =

new CustomersTableAdapter();

view.Results =

tableAdapter.GetDataByCustomerName(

String.Format("%{0}%", view.SearchValue));

}

}

- Add the highlighted code as shown above

Page 83: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 83 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Run the Tests

Now that the tests and the implementation are there, compile and run the tests.

- Compile the code

- Run the tests

All the tests should pass.

Implement the User Interface

Now that we have working and tested code to build upon, we can go ahead and implement our real user

interface using ASP.NET.

- Go to the Labs.WebUI project

- Open CustomerSearch.aspx.cs

Page 84: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 84 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

The there’s no logic here but the basic structure that we’ll need for the page are already here. Pretty

much all you have to do is make this page implement the ICustomerSearchView interface.

Page 85: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 85 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

public partial class CustomerSearch :

System.Web.UI.Page, ICustomerSearchView

{

private CustomerSearchPresenter m_Presenter =

new CustomerSearchPresenter();

protected void Page_Load(object sender, EventArgs e)

{

if (this.IsPostBack == false)

{

m_Presenter.Initialize(this);

}

}

public string SearchValue

{

get

{

return m_SearchCompanyName.Text;

}

set

{

m_SearchCompanyName.Text = value;

}

}

public NorthwindDataset.CustomersDataTable Results

{

get

{

return m_Grid.DataSource as NorthwindDataset.CustomersDataTable;

}

set

{

m_Grid.DataSource = value;

m_Grid.DataBind();

}

}

protected void m_BtnSearch_Click(object sender, EventArgs e)

{

m_Presenter.Search(this);

}

}

- Add the highlighted code as shown above to CustomerSearch.aspx.cs

- Compile the code

The code should compile.

Page 86: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 86 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Run the Web Application

Now it’s time to run the application.

If the text for the Labs.WebUI project is not in bold, you need to make it the StartUp Project for the

solution.

- Go to Solution Explorer

- Right-click the Labs.WebUI project

- Choose Set as StartUp Project from the context menu

Page 87: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 87 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

Now run the application.

- Go to the Debug menu

- Choose Start Debugging

You should see a browser window and the application should appear.

- In the Enter all or part of a company name box, type rest

- Click the Search button

Page 88: Visual Studio 2010 Unit Testing Labs - Benjamin Day Consulting, Inc. · 2020. 3. 1. · - From the Visual Studio main menu, choose Build and then Build DeckOfCards.UnitTests It probably

Visual Studio 2010 Testing Labs Page 88 of 88

Copyright © 2011 – Benjamin Day Consulting, Inc. – www.benday.com

Printing or duplication is prohibited without author’s expressed written permission.

You should see the matching results in the Search Results grid.

You’ve just implemented your first testable user interface using the Model View Presenter Pattern.