Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting Started with Interfaces and Mocks #3

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

psiberknetic
Copy link
Contributor

@psiberknetic psiberknetic commented Dec 13, 2019

In this section, we'll have a basic discussion about using interfaces and mocks for testing.

The use of interfaces is a much larger conversation that applies to much more than simply testing. Prudent use of interfaces helps us create systems with much less coupling between components. This allows us to modify, maintain and test components much more easily.

Mocks, specifically, help with testing to allow us to independently test components that have dependencies without having the tests themselves require the dependencies. Dependencies can often be difficult to set up the way we want, or don't allow us to have complete control of the data we receive. Mocks can also help us when we're testing other things that we can't control like time or randomness.

Let's look at some concrete examples.

@psiberknetic psiberknetic force-pushed the basic-use-of-interfaces-and-mocks branch 2 times, most recently from 8a78d46 to 359cd2e Compare December 13, 2019 16:10
@psiberknetic
Copy link
Contributor Author

psiberknetic commented Dec 13, 2019

We're going to start building the back end for a simple dice game. The first thing I've done is to create a really simple implementation of a six-sided die (D6). I've simply called it Die. Looking through that code, there are no predicates where logic flow could diverge, so I'm not really seeing anything that I need to write tests about. My test coverage tool isn't happy, but that's the topic of a different conversation (one we have here).

The Die object is really simple. It has a Value property and a Roll() method. Roll() is automatically called during the constructor to ensure each Die has an initial, random value. I'm also using a separate, static class to do the rolling to avoid the issue of having to get creative about seeding random number generators (RNGs) to ensure we get different values for different dice that were created at the same time. Simple so far.

Let's implement something we need to test. I'm even going to write the tests first! Another topic for another time.

@psiberknetic
Copy link
Contributor Author

Now let's make the tests pass by implementing some code.

@psiberknetic
Copy link
Contributor Author

And now we have passing tests for the IsValidRoll method. I'm feeling good about this! Let's add another method and some tests. One of the things we need to check is that if all five of the dice have the same value. We'll call that a five-of-a-kind. This is definitely an original game and not a rip-off some some existing game...

Comment on lines 29 to 37
[TestMethod]
public void IsFiveOfAKind_AllDiceAreTheSame_ReturnsTrue()
{
IEnumerable<Die> dice = null; //Uh oh... how do I create dice that are all the same?
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh-oh, I've run into a problem. The DIe class randomly generates its value when it is created or when I roll it. That value is read only (as I will maintain that it should be), so how can I create an IEnumerable where the dice all have the same value? This is one of the reasons that objects that use randomness can be difficult to test. This is where interfaces can come to our rescue.

Let's do a little refactoring...

@psiberknetic
Copy link
Contributor Author

psiberknetic commented Dec 13, 2019

In this commit, I've created an IDie interface and have refactored some code (especially in the functions I need to test) to use the interface instead of the concrete Die implementation.

You can poke through the commit to see exactly what I've changed.

Now that we have an interface we can use, let's create a Mock object in our testing project that implements IDie. Remember, we're not trying to test the functionality of our Die class, just the extension methods that use it.

@psiberknetic
Copy link
Contributor Author

Ok, we've now implemented a version of IDie in our testing project that allows us to set its value. Now we can actually write tests for our IsFiveOfAKind() method.

@psiberknetic
Copy link
Contributor Author

Now I've added a couple tests for IsFiveOfAKind using our new SettableD6. I have a test where the five dice all have the same value and one where they don't. I've also thrown in a test where an exception is thrown if an invalid roll is passed to the method. Right now all the tests fail because we haven't implemented IsFiveOfAKind. Remember, before the use of the interface, we couldn't even WRITE these tests. Progress!

Let's make them pass.

@psiberknetic
Copy link
Contributor Author

And now all of our tests pass.

I hope this information was helpful. Check back soon to see an example of how to use Mocking frameworks to make this kind of change even easier.

If you liked what you saw here and would like to learn more, feel free to contact me at [email protected]!

@psiberknetic psiberknetic force-pushed the basic-use-of-interfaces-and-mocks branch from e0fb744 to 999b5e7 Compare December 18, 2019 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant