A few days ago, I mentioned how I wanted to do a bit of experimentation with a Fibonacci Sequence implementation. Before I did my experimentation, I wanted a working sequence and a set of tests to validate that. This way I would know if my refactoring broke something. [Update: here's some of that experimentation.]
So let's TDD into a Fibonacci Sequence. You can grab the code from GitHub: jeremybytes/fibonacci-tdd. There are branches to go along with each step.
Initial Project
For the initial project, I created a console application and a class library to hold my tests. I figured that I could put my Fibonacci sequence class in the console application and then move it to its own project if needed.
We'll basically be looking at 2 files. The first is "FibonacciSequence.cs", and (as mentioned) this is in the console application:
Then we have the test, "FibonacciSequenceTests.cs":
Since we're going to be writing tests to make sure we've got the sequence right, I included the first 12 Fibonacci numbers. If you're not familiar with the Fibonacci sequence, each value is determined by adding the previous 2 values. So the 6th value (8) is determined by adding the previous values of 3 and 5.
The First Test
So let's write a test for the first element in the sequence. But before we do that, I'm going to do a bit of setup.
Since I want this to be a sequence (which means IEnumerable in the C# world), I'm going to stub out the interface in our production class:
I know that this is writing code before tests. But since we know we want a sequence, it makes sense to give ourselves a framework to hang our code on. Notice that even though I've added the code for the "IEnumerable<int>" interface, we haven't included any implementation. We'll write our test first.
And here's the first test:
This creates an instance of the sequence, pulls the first value, and then checks to see that it's "1".
This tests fails (as expected):
The reason for the failure is the "NotImplementedException" that gets thrown by our class.
So let's write the very simplest code possible to get this to pass:
The "yield return" will create an enumerator for us in the background so we don't have to deal with it explicitly. For more information on IEnumerable, you can check out this article series: Next, Please! A Closer Look at IEnumerable.
This code is a bit too simple. We know that it doesn't really fulfill our sequence needs. But it does get our first test to pass:
So let's move on to the next test.
Testing the 2nd Element
Now that we've got things set up, it should be easier to write additional tests. Lets set up a test for the 2nd element in the Fibonacci Sequence:
Testing the first element of a sequence is easy. Testing the second element is a bit trickier. What I did here was use the "Take" method (one of the awesome LINQ methods) to grab the first 2 items of the sequence. Then I take the "Last" value.
But there's a problem with this test. It passes:
Yikes. That's not good. When doing TDD, I expect the test to fail until I add proper implementation code. So what's the problem?
The "Take" method will grab up to the number of values requested. In our case, the sequence only returns a single item. But "Take" doesn't care; it grabs as much as it can. Then when we ask for the "Last" value, we end up getting the only one that's there, which is the first value.
Correcting the Test
When we have a test that passes when we expect it to fail, we've either got a problem with our code or a problem with our test. In this case, the problem is with our test. "Take" is not the appropriate LINQ method to use here. Fortunately, there's another LINQ method we can use: "ElementAt":
One thing to keep in mind is that "ElementAt" uses a 0-based index. So using "1" will get us the 2nd item in the sequence.
Now our test fails:
This is better. The reason for the failure is that there is no 2nd item so "ElementAt" throws an exception.
For this implementation, we'll do something very simple:
I'm feeling like this is too simple still. But I'm also thinking that we can get a little more info to let the method take shape a little more naturally before we think about refactoring.
This is enough to get the test to pass:
So let's keep moving.
Testing the 3rd Element
Next we'll write a test for the 3rd element, which we expect will be "2":
This test fails (as expected), so let's write a bit more code:
This gets our test to pass, but it's kind of stupid to keep writing the method this way. So, I'm going to to a bit of refactoring.
Refactoring to Something a Bit More Useful
Since I see a little bit of a pattern forming here, I'm going to add a "for" loop to the implementation:
I know that this won't work for the entire sequence. But it works for the tests that we have in place right now. We'll worry about fixing this in a bit, but not until our test cases call for it.
Parameterizing the Tests
It looks like we're writing the same test over and over again. This is where I look to see if we can parameterize the test. In this case, I think it will work pretty well.
Here's a new test:
I'm using NUnit, so I can set up test cases that get passed in as parameters. For more information, take a look at this article: Parameterized Tests with NUnit.
This will plug the values of the TestCases into the parameters of the test. So in the first case, it will use "0" for the "ElementAt" call (which is the first item in the sequence) and it will use "1" as the "expected" value in the assertion.
This lets us test the first 3 elements of the sequence with a single test. And our results show the tests are passing:
Notice that the results show the parameter values plugged in. This is really useful when one (or more) of these test cases fail.
Testing the 4th Element
Testing the 4th element of the sequence is as simple as adding another test case:
This test passes without any changes to our code. And that's okay. We expect the 4th element is "3", and that's what our code returns.
It's interesting to see that the simple "for" loop that we built in the code works for 3 elements in the sequence. But we'll see it break down with the next element.
Testing the 5th Element
Adding the test case for the 5th element creates a failing test. I won't show a screen shot, but the test just has "TestCase(4,5)" added.
Now we have to do a bit of math to really implement the Fibonacci Sequence:
Like the description of the Fibonacci Sequence, I add together the previous two values. A couple of local variable hang on to those values so they can be used to calculate the next item.
With this in place, all of our tests pass.
Refactoring
The tests are passing, but I see some dead code in the implementation. We are no longer using the indexer of the "for" loop. Since we don't need the indexer, we can swap the "for" loop for a "while" loop:
I'm never a huge fan of "while(true)", but it is a good way to create an infinite sequence.
The First 12 Elements
Since we've implemented the definition of the Fibonacci Sequence, we would expect that additional tests would pass. I set up the test cases for the first 12 elements:
And all of the tests pass:
Yay!
But there's a problem.
Overflow!
I've done a lot of experimentation with the Fibonacci Sequence. (I'm not sure why it intrigues me so much.) One thing I know about it is that it will overflow a 32-bit integer pretty quickly. How quickly?
Let's set up the console application to find out. We'll add code to our console application to print out the first 50 values of the Fibonacci Sequence.
And here's the output:
As we can see, something weird starts to happen at item #47. We overflow the standard integer and end up with a negative number (since the default 32-bit integer is signed).
Testing for Overflow
Now that we know where our problem is, we can create a test for it.
This test grabs elements #46 and #47 (remember, "ElementAt" is 0-based). Then we check to make sure that #47 is greater than #46.
Since this is the pivot-point of the 32-bit integer, element #47 give us a negative value, so it is *not* greater. This means our test fails.
Fixing Overflow
To fix the overflow, we'll change from using "int" (a 32-bit integer) to using a "long" (a 64-bit integer). The code is fairly easy to update at this point.
There's also one change we need to make in our tests. The "expected" parameter type needs to be changed to "long".
With this in place, we now have all of our tests passing (including the one that checks for overflow):
And we can see our console application is behaving as expected as well:
Wrap Up
This code isn't perfect. Our implementation returns an infinite sequence (because of the "while(true)"). But even our updated code isn't infinite. After a while we'll overflow the 64-bit integer as well. So we should probably add some checks in the code for overflow and end the sequence. But I'll leave that as an exercise for you.
We've reached our goal which is to have an implementation of the Fibonacci Sequence with a set of valid tests. Now that we have this in place, we can do some experimentation with the implementation.
And with the tests in place, we'll know immediately if our experimentation breaks something. In an upcoming article, we'll look at those experiments. [Update: here's a bit of that experimentation.]
Happy Coding!
Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts
Monday, April 24, 2017
Friday, January 6, 2017
Does X Make You Successful?
I was recently asked to complete a survey about Test Driven Development. After looking through the questions, I found that I couldn't really answer them.
Disclaimer
I'm not much of a TDDer myself. I'm a huge believer in unit testing, and I'm convinced that Unit Testing Makes Me Faster. But I'm more of a "test along side" developer, where I'm writing code and writing tests more-or-less together. I don't strictly follow the red-green-refactor cycle, and I don't mandate 100% code coverage in my projects.
Note: If you're curious about my unit testing talk, you can see a recording from Visual Studio Live! from last May.
You might ask why I have videos showing people how to do TDD if I don't use it widely myself. That's mostly environmental. Based on the types of applications I've been building and the environments I've been working in, TDD hasn't been a the the best fit (although I'm sure there are those who disagree with me). But I have seen TDD be an extremely useful tool in a lot of circumstances, so I want to encourage people to explore it and help them get over some of the roadblocks that might stop them.
Other Factors
The problem with trying to isolate success to any one practice (whether Agile, Scrum, TDD, CI/CD) is that there are always other factors that influence success or failure.
Failures
Specifically with regard to TDD, I've seen teams fail horribly using it. I was a bit outside of these groups, and TDD was not the cause of their issues. There were issues with the management not trusting the developers. There were issues of mandating tools and processes that the developers did not believe in. There were issues around team dynamics and trust.
So there were projects that failed while using TDD. But I would not attribute the failure to TDD.
Successes
On the other side, I have a good friend who is a huge TDD proponent. He has been very successful using it, and he helps other people understand it and be successful with it.
I also know a company with a very successful development department. They have several teams that all build code using TDD. But they also have good team dynamics, trust, and a learning mindset. They are always looking for ways to do things better, and they are not afraid to discard things that don't work in their environment.
Isolating Success
The gist of this is that it's really hard to isolate what makes us successful.
I've heard people say, "Once we went to CI/CD, we saw X improvement [in speed / cost / maintenance]." But it's really hard to credit that to Continuous Integration/Continuous Delivery only. That's because most teams are not ready to simply flip the CI/CD switch.
To get to the point where we can be successful with CI/CD, we need to have good automated testing in place, we need to have good source control, we need to have good branch/merge practices. Then we can get to automated deployments. So even if we can make our users happier once when we have CI/CD in place, our success is really attributable to the other factors as well.
Continuous Improvement
One thing that I emphasize when I'm encouraging people to include unit testing in their environment is that it takes time to learn something new. It's not something that we will be instantly productive with.
With any process, framework, library, or language, we go through 3 phases:
There is No Silver Bullet
There is so single tool or practice that will make us successful. I've seen teams using Agile fail and I've seen teams using Agile succeed (and I won't get into the "you're doing it wrong" discussion here). I've seen teams using TDD fail, and I've seen teams using TDD succeed.
My biggest frustration was watching a group that was really broken. The management didn't trust their developers and so they tried to come up with the one process that would ensure that every project would be successful. But there is no silver bullet. And every 6 months, they would give up on what they were doing and try another process to ensure success. Over the course of years, I saw each of these processes fail.
There was nothing wrong with the practice or process they chose. And the practice was not the cause of the failure. We need to look beyond any particular practice and talk about what makes up a productive team.
Asking the Right Questions
Programming practices come and go. Programming languages come and go. Programming frameworks come and go. Each of these can be useful tools in the hands of good developers. And they can also be used to create complete disasters.
We need to think about the questions that we ask about any of these tools.
So rather than asking if a particular tool or practice makes us successful, we should be asking "What problem is this tool designed to solve?" And of course, "Do I have this problem?"
Happy Coding!
- In how many projects did you use TDD?
- Was TDD successful in at least 50% of the projects?
- Did TDD delay the release date of the projects?
- Describe the advantages you realized after using TDD.
- Describe the disadvantages you realized after using TDD.
- Did the software maintenance decrease after using TDD?
Disclaimer
I'm not much of a TDDer myself. I'm a huge believer in unit testing, and I'm convinced that Unit Testing Makes Me Faster. But I'm more of a "test along side" developer, where I'm writing code and writing tests more-or-less together. I don't strictly follow the red-green-refactor cycle, and I don't mandate 100% code coverage in my projects.
Note: If you're curious about my unit testing talk, you can see a recording from Visual Studio Live! from last May.
You might ask why I have videos showing people how to do TDD if I don't use it widely myself. That's mostly environmental. Based on the types of applications I've been building and the environments I've been working in, TDD hasn't been a the the best fit (although I'm sure there are those who disagree with me). But I have seen TDD be an extremely useful tool in a lot of circumstances, so I want to encourage people to explore it and help them get over some of the roadblocks that might stop them.
Other Factors
The problem with trying to isolate success to any one practice (whether Agile, Scrum, TDD, CI/CD) is that there are always other factors that influence success or failure.
Failures
Specifically with regard to TDD, I've seen teams fail horribly using it. I was a bit outside of these groups, and TDD was not the cause of their issues. There were issues with the management not trusting the developers. There were issues of mandating tools and processes that the developers did not believe in. There were issues around team dynamics and trust.
So there were projects that failed while using TDD. But I would not attribute the failure to TDD.
Successes
On the other side, I have a good friend who is a huge TDD proponent. He has been very successful using it, and he helps other people understand it and be successful with it.
I also know a company with a very successful development department. They have several teams that all build code using TDD. But they also have good team dynamics, trust, and a learning mindset. They are always looking for ways to do things better, and they are not afraid to discard things that don't work in their environment.
Isolating Success
The gist of this is that it's really hard to isolate what makes us successful.
I've heard people say, "Once we went to CI/CD, we saw X improvement [in speed / cost / maintenance]." But it's really hard to credit that to Continuous Integration/Continuous Delivery only. That's because most teams are not ready to simply flip the CI/CD switch.
To get to the point where we can be successful with CI/CD, we need to have good automated testing in place, we need to have good source control, we need to have good branch/merge practices. Then we can get to automated deployments. So even if we can make our users happier once when we have CI/CD in place, our success is really attributable to the other factors as well.
Continuous Improvement
One thing that I emphasize when I'm encouraging people to include unit testing in their environment is that it takes time to learn something new. It's not something that we will be instantly productive with.
With any process, framework, library, or language, we go through 3 phases:
- Learning the technical bits
This is where we get the basics about how to install tools, what commands are available, and how to get things working from a technical standpoint. - Learning the best practices
This is where we look for experience and advice from other people who have used this tool. We can see what worked for them and what didn't work. And this gives us a good place to start in our environment. - Learning how things fit in our environment
This is where we see what works in our own world. The best practices that we picked up from other developers were things that worked well in their environment, but that doesn't mean they will work for us.
There is No Silver Bullet
There is so single tool or practice that will make us successful. I've seen teams using Agile fail and I've seen teams using Agile succeed (and I won't get into the "you're doing it wrong" discussion here). I've seen teams using TDD fail, and I've seen teams using TDD succeed.
My biggest frustration was watching a group that was really broken. The management didn't trust their developers and so they tried to come up with the one process that would ensure that every project would be successful. But there is no silver bullet. And every 6 months, they would give up on what they were doing and try another process to ensure success. Over the course of years, I saw each of these processes fail.
There was nothing wrong with the practice or process they chose. And the practice was not the cause of the failure. We need to look beyond any particular practice and talk about what makes up a productive team.
Asking the Right Questions
Programming practices come and go. Programming languages come and go. Programming frameworks come and go. Each of these can be useful tools in the hands of good developers. And they can also be used to create complete disasters.
We need to think about the questions that we ask about any of these tools.
What problem is this tool designed to solve?There was a time in my career where I did an analysis of the MVVM design pattern, and determined that it was not appropriate for our environment. Of the 3 problems it was designed to solve, we had already solved 2 of those problems another way, and we didn't have the 3rd problem. Since then, I have used MVVM quite successfully in a lot of other environments. But we do need to stop and ask those questions.
Do I have this problem?
So rather than asking if a particular tool or practice makes us successful, we should be asking "What problem is this tool designed to solve?" And of course, "Do I have this problem?"
Happy Coding!
Tuesday, November 29, 2016
Jeremy Talks with Steve Bishop about TDD
Today I had the opportunity to talk with Steve Bishop on his YouTube channel. Steve gave me the chance to show some code and talk about using TDD in a real-world scenario -- in this case, creating a class that gets data from a service.
You can watch it on YouTube: Real-Time Coding with Jeremy Clark - Test Driven Development
Or you can watch it here:
The code is taken from a talk (and hopefully soon-to-be-produced video series) which shows a few of my struggles with adopting TDD. It's easy to get stuck when we try to do things in a particular order. There are certain things that are difficult to unit test, and sometimes we need to skip over them and get on to other things.
If you'd like to download the code, you can visit my website: Test Driven Development in the Real World.
The code download has 3 states: (1) the starting code (if you'd like to follow along), (2) the "minimal" completion state, which is how we left the code at the end of this video (more or less), and (3) a fully-completed state that has all of the methods and has been refactored into a few different classes. This is just one option for completing the task. The specifics always depend on the needs of our particular application.
Also, if you'd like to take a closer look at exception handling (as mentioned in the video), take a look at this video on my channel: TDD Debugging and Testing Exceptions.
A big thanks to Steve for hosting me on his show. I'll look forward to doing it again in the future.
Happy Coding!
You can watch it on YouTube: Real-Time Coding with Jeremy Clark - Test Driven Development
Or you can watch it here:
The code is taken from a talk (and hopefully soon-to-be-produced video series) which shows a few of my struggles with adopting TDD. It's easy to get stuck when we try to do things in a particular order. There are certain things that are difficult to unit test, and sometimes we need to skip over them and get on to other things.
If you'd like to download the code, you can visit my website: Test Driven Development in the Real World.
The code download has 3 states: (1) the starting code (if you'd like to follow along), (2) the "minimal" completion state, which is how we left the code at the end of this video (more or less), and (3) a fully-completed state that has all of the methods and has been refactored into a few different classes. This is just one option for completing the task. The specifics always depend on the needs of our particular application.
Also, if you'd like to take a closer look at exception handling (as mentioned in the video), take a look at this video on my channel: TDD Debugging and Testing Exceptions.
A big thanks to Steve for hosting me on his show. I'll look forward to doing it again in the future.
Happy Coding!
Wednesday, March 9, 2016
More TDD Videos
I've recently published two more videos that explore Test-Driven Development a bit more. If you're new to TDD, then you might want to start with TDD Basics in C#.
The latest videos use the rules for Conway's Game of Life as the problem to be solved. I ran across Conway's Game of Life many years ago when I was first getting involved with computers, and the patterns have intrigued me ever since.
Or watch it here:
To continue on with the code from Conway's Game of Life, we fix some bugs and test for exceptions.
Or watch it here:
For more articles on Conway's Game of Life and unit testing, take a look here: Coding Practice with Conway's Game of Life.
More videos on unit testing are on the way. Future topics will include using TDD with real-world applications, mocking, and testing asynchronous methods.
Happy Coding!
The latest videos use the rules for Conway's Game of Life as the problem to be solved. I ran across Conway's Game of Life many years ago when I was first getting involved with computers, and the patterns have intrigued me ever since.
TDD: Don't Turn Off Your BrainWatch the video on YouTube: TDD: Don't Turn Off Your Brain
Test-Driven Development (TDD) lets our code develop out of our tests. But this doesn't mean that we turn off our brain. We still need to make decisions on our design as we write our tests. In this video, we'll take some "bigger steps" with TDD to implement the rules for Conway's Game of Life. Along the way, we'll see the types of design decisions we need to keep in mind.
Or watch it here:
TDD Debugging & Testing ExceptionsWatch the video on YouTube: TDD Debugging & Testing Exceptions
Test-Driven Development (TDD) lets our code develop out of our tests. But it is also extremely useful when we have to debug existing code. When we have a bug, we can first write a failing unit test, then write the code to get that test to pass. In addition to debugging, in this video, we'll see how we can test for exceptions in our tests. There are several approaches with different advantages.
Or watch it here:
For more articles on Conway's Game of Life and unit testing, take a look here: Coding Practice with Conway's Game of Life.
More videos on unit testing are on the way. Future topics will include using TDD with real-world applications, mocking, and testing asynchronous methods.
Happy Coding!
Tuesday, February 23, 2016
New Video: TDD Basics with C#
I've just published a new video on YouTube, this time taking a look at the basics of test-driven development. This starts at the very beginning and walks through the Red-Green-Refactor cycle to build up code one step at a time.
This video is great for developers who want to get started with TDD. The code we build is pretty simple: an implementation of FizzBuzz. In later videos, we'll take a look at unit testing and TDD with real-world code.
Or watch it here:
Happy Coding!
This video is great for developers who want to get started with TDD. The code we build is pretty simple: an implementation of FizzBuzz. In later videos, we'll take a look at unit testing and TDD with real-world code.
TDD Basic with C#Watch the video on YouTube: TDD Basics with C#
Test-Driven Development (TDD) lets our code develop out of our tests. We do this by following the Red-Green-Refactor cycle. In this video, we look at the basics of TDD by implementing FizzBuzz in C#. We'll be using NUnit as our testing framework, but these principles work with whatever environment we choose.
Or watch it here:
Happy Coding!
Sunday, April 5, 2015
My Approach to Testing: Test Public Members
In my presentation "Clean Code: Homicidal Maniacs Read Code, Too!", I spend quite a bit of time refactoring code (not as much time as I'd like, which is why I put out a supplemental video: Clean Code: The Refactoring Bits).
Unit tests are a vital part of the code that I show. The unit tests are what make sure that I don't inadvertently change functionality as I refactor the code. Much of the refactoring involves extracting out pieces of code and putting them into their own methods. This makes the code easier to navigate.
I do get questions about my testing technique as I show this code. Here's a question that I got at Nebraska.Code() right after the presentation:
The longer answer is my approach to unit testing: I test the public members of my code.
Let's take a look at some examples so that I can show this in action, and then I'll talk about why I take this particular approach.
Refactoring Code
Here's the "Initialize" method that we start with:
During the process of making this more readable, I extract out a couple of methods and move some assignments around. What we end up with are a couple of private methods in addition to our public one:
This takes some of the details and "hides" them in private methods. This way, when we first walk up to the "Initialize" method, we can easily decide which parts of the code are important for what we're doing. If we don't care about the dependency injection container bits, then we can skip right over those and go to the "RefreshCatalog" method.
Here's another example; this time we refactor the "RefreshCatalog" method. Here's the original:
And the version with bits extracted:
This makes "RefreshCatalog" much easier to follow. We get a high-level overview of what the method is doing. If we need to look at details, then we can drill into those methods. This is especially appreciated when you walk up to this method for the very first time. (And we have to remember that sometimes we keep walking up to the same method "for the very first time" over and over again -- we get put on other projects and have to come back to this application 6 months later to make some enhancements, and we have to get our bearings again.)
Testing Public Members Only
So why don't my unit tests change as part of this process? Because I'm only testing the public members of my class -- whether public methods, properties, or events.
So if we turn on CodeLens and take a look, we'll see that only the public methods have unit tests:
In the case of "Initialize", we see that there are 19 tests (which is all of them). This is because each of our tests runs the "Initialize" method as part of the setup even if it's not explicitly testing this code.
We see something similar for "RefreshCatalog":
In this case we have 4 tests that call "RefreshCatalog" directly. In 2 of these tests, we check to see if the service is called based on the state of the cache:
If we look at the details of the first test, we see that it does call "RefreshCatalog":
And it also indirectly calls the "IsCacheValid" property and the "RefreshCatalogFromService" method.
I won't go into the other details of this test; it gets a little weird because we're testing an asynchronous service that uses APM (Asynchronous Programming Model) wrapped in a Task which (sort of) changes it to TAP (Task Asynchronous Pattern). So there's a little helper object ("tracker") to make testing easier. This would be a good topic for another day.
[Update: You can see how the tracker works in this article: Tracking Property Changes in Unit Tests.]
Why Not Test Private Members?
So the question is why don't I create tests for the private members? My reasoning is that I like to keep the code as clean as possible.
When I create production objects, I want to modify the code as little as possible for testing. When we want to test private members, we basically have 3 options:
Option 1: Reflection
Our first option to test private members is to use reflection to crack open our class so that we can access the bits of code we're not normally allowed to look at.
I don't really like this option because our tests become *very* complicated very quickly. Reflection is one of those things that is difficult to understand on its own. When we make it a requirement for our unit tests, then we're just asking for trouble.
The end result when we take this approach is that we just don't bother with tests because they are too difficult to create.
Option 2: Change Access Modifiers to Public
Another option is to change the "private" members that we want to test to "public."
I'll just cross this option off immediately. I don't want to mess with making things visible to the outside world when it's not appropriate. Scoping and visibility are huge parts of building good objects, libraries, and APIs. We can't compromise that for testing.
Option 3: Change Access Modifiers to Protected
Another option is to change the "private" members that we want to test to "protected." When we do this, we can then create a wrapper class in our tests. This wrapper class descends from our production class, so it has access to the protected members. It can then supply wrapper methods or properties to access the protected members of the base class.
I don't like this option, either. The main reason for that is that I feel like I'm not testing my production code. Instead of testing my actual class, I'm testing some mutation of that class. I'm not confident that the behavior between the test class and the production class will be identical, so I lose faith in my tests.
What if I Really Need to Test a Private Member?
In the examples that I've shown here, it's pretty easy to say, "I'm okay testing at a higher level." By testing the "RefreshCatalog" method, we end up testing all of the private members indirectly.
My answer to this is pretty simple:
When I extract that out into its own class (whether an instance class or a set of related methods in a static class), now I can test that class directly.
Of course, we would have to get into a discussion on the proper visibility of the new class and methods. But that's easier to take care of. If I have a protected or internal class that's only available to the code in the same assembly or namespace, I can create a test class that is only responsible for directly calling into this protected class. But I'm not wrapping the class itself, just creating a test class that is capable of calling into the real class.
Many Approaches to Testing
There are many approaches to unit testing. And I will be the last one to say that this is an example that everyone should follow. This particular approach of testing the public members has worked out well for me in the majority of situations that I've run into. It has the added benefit of being fairly resistant to refactoring.
When we create the public members of our classes, these generally remain unchanged. This is because the public interface is how the outside world interacts with our objects. So we try to change these as little as possible.
The private members, however, are subject to change. We can rename private methods, move things to other methods, properties, or classes, and combine similar code into consolidated methods. If we are directly testing the private members, we're less likely to make these types of changes because it would mean making major changes to our tests as well (especially if we're using reflection which would not give us compile-time errors, only run-time errors).
Wrap Up
I encourage people to try different approaches to testing. Each has its advantages and disadvantages. I've managed to dial in an approach that works well for me and the types of applications that I normally build. But that doesn't mean that I don't keep exploring.
I'm still working on TDD. I've had some really good successes with it lately, and I've got another piece of code that needs some help, so I'll be doing some more experimentation this week.
Overall, automated testing gives me confidence in my code. The tests are proof that my code does what I think it does. And I can get immediate feedback by re-running tests whenever I change code (without having to run my application).
So experiment, try different techniques, and come up with a testing approach that works well for you. Ultimately, you will end up with better code.
Happy Coding!
Unit tests are a vital part of the code that I show. The unit tests are what make sure that I don't inadvertently change functionality as I refactor the code. Much of the refactoring involves extracting out pieces of code and putting them into their own methods. This makes the code easier to navigate.
I do get questions about my testing technique as I show this code. Here's a question that I got at Nebraska.Code() right after the presentation:
![]() |
| Question Time! |
How do you modify your tests after extracting code into the new private methods?The short answer is: I don't.
The longer answer is my approach to unit testing: I test the public members of my code.
Let's take a look at some examples so that I can show this in action, and then I'll talk about why I take this particular approach.
Refactoring Code
Here's the "Initialize" method that we start with:
During the process of making this more readable, I extract out a couple of methods and move some assignments around. What we end up with are a couple of private methods in addition to our public one:
This takes some of the details and "hides" them in private methods. This way, when we first walk up to the "Initialize" method, we can easily decide which parts of the code are important for what we're doing. If we don't care about the dependency injection container bits, then we can skip right over those and go to the "RefreshCatalog" method.
Here's another example; this time we refactor the "RefreshCatalog" method. Here's the original:
And the version with bits extracted:
This makes "RefreshCatalog" much easier to follow. We get a high-level overview of what the method is doing. If we need to look at details, then we can drill into those methods. This is especially appreciated when you walk up to this method for the very first time. (And we have to remember that sometimes we keep walking up to the same method "for the very first time" over and over again -- we get put on other projects and have to come back to this application 6 months later to make some enhancements, and we have to get our bearings again.)
Testing Public Members Only
So why don't my unit tests change as part of this process? Because I'm only testing the public members of my class -- whether public methods, properties, or events.
So if we turn on CodeLens and take a look, we'll see that only the public methods have unit tests:
In the case of "Initialize", we see that there are 19 tests (which is all of them). This is because each of our tests runs the "Initialize" method as part of the setup even if it's not explicitly testing this code.
We see something similar for "RefreshCatalog":
In this case we have 4 tests that call "RefreshCatalog" directly. In 2 of these tests, we check to see if the service is called based on the state of the cache:
If we look at the details of the first test, we see that it does call "RefreshCatalog":
And it also indirectly calls the "IsCacheValid" property and the "RefreshCatalogFromService" method.
I won't go into the other details of this test; it gets a little weird because we're testing an asynchronous service that uses APM (Asynchronous Programming Model) wrapped in a Task which (sort of) changes it to TAP (Task Asynchronous Pattern). So there's a little helper object ("tracker") to make testing easier. This would be a good topic for another day.
[Update: You can see how the tracker works in this article: Tracking Property Changes in Unit Tests.]
Why Not Test Private Members?
So the question is why don't I create tests for the private members? My reasoning is that I like to keep the code as clean as possible.
When I create production objects, I want to modify the code as little as possible for testing. When we want to test private members, we basically have 3 options:
Option 1: Reflection
Our first option to test private members is to use reflection to crack open our class so that we can access the bits of code we're not normally allowed to look at.
I don't really like this option because our tests become *very* complicated very quickly. Reflection is one of those things that is difficult to understand on its own. When we make it a requirement for our unit tests, then we're just asking for trouble.
The end result when we take this approach is that we just don't bother with tests because they are too difficult to create.
Option 2: Change Access Modifiers to Public
Another option is to change the "private" members that we want to test to "public."
I'll just cross this option off immediately. I don't want to mess with making things visible to the outside world when it's not appropriate. Scoping and visibility are huge parts of building good objects, libraries, and APIs. We can't compromise that for testing.
Option 3: Change Access Modifiers to Protected
Another option is to change the "private" members that we want to test to "protected." When we do this, we can then create a wrapper class in our tests. This wrapper class descends from our production class, so it has access to the protected members. It can then supply wrapper methods or properties to access the protected members of the base class.
I don't like this option, either. The main reason for that is that I feel like I'm not testing my production code. Instead of testing my actual class, I'm testing some mutation of that class. I'm not confident that the behavior between the test class and the production class will be identical, so I lose faith in my tests.
What if I Really Need to Test a Private Member?
In the examples that I've shown here, it's pretty easy to say, "I'm okay testing at a higher level." By testing the "RefreshCatalog" method, we end up testing all of the private members indirectly.
But what if one of those private members is so important to my functionality that I really want to test it directly?For example, if I'm accepting credit cards, I want to do the Luhn check against them. This will at least make sure that the number itself is potentially valid before checking against a credit card processor.
My answer to this is pretty simple:
If a private method is important enough that I need to test it directly, it's probably important enough to have its own class.This really takes me to the Single Responsibility Principle and Separation of Concerns. Needing to test a private method directly is a code smell. If I run across that, then it probably means that it's a separate concern that needs its own place in the code.
When I extract that out into its own class (whether an instance class or a set of related methods in a static class), now I can test that class directly.
Of course, we would have to get into a discussion on the proper visibility of the new class and methods. But that's easier to take care of. If I have a protected or internal class that's only available to the code in the same assembly or namespace, I can create a test class that is only responsible for directly calling into this protected class. But I'm not wrapping the class itself, just creating a test class that is capable of calling into the real class.
Many Approaches to Testing
There are many approaches to unit testing. And I will be the last one to say that this is an example that everyone should follow. This particular approach of testing the public members has worked out well for me in the majority of situations that I've run into. It has the added benefit of being fairly resistant to refactoring.
When we create the public members of our classes, these generally remain unchanged. This is because the public interface is how the outside world interacts with our objects. So we try to change these as little as possible.
The private members, however, are subject to change. We can rename private methods, move things to other methods, properties, or classes, and combine similar code into consolidated methods. If we are directly testing the private members, we're less likely to make these types of changes because it would mean making major changes to our tests as well (especially if we're using reflection which would not give us compile-time errors, only run-time errors).
Wrap Up
I encourage people to try different approaches to testing. Each has its advantages and disadvantages. I've managed to dial in an approach that works well for me and the types of applications that I normally build. But that doesn't mean that I don't keep exploring.
I'm still working on TDD. I've had some really good successes with it lately, and I've got another piece of code that needs some help, so I'll be doing some more experimentation this week.
Overall, automated testing gives me confidence in my code. The tests are proof that my code does what I think it does. And I can get immediate feedback by re-running tests whenever I change code (without having to run my application).
So experiment, try different techniques, and come up with a testing approach that works well for you. Ultimately, you will end up with better code.
Happy Coding!
Monday, February 16, 2015
More TDD Practice: Finishing Up the Library
Yesterday, I did some coding practice: using test-driven development to implement a library that makes a service call and then parses the data. There were a few things left undone, so today I coded those up (also using TDD).
You can get the code for this in the sunset-tdd branch of the GitHub project: jeremybytes/house-control. And a collection of all the articles for this project are available here: Rewriting a Legacy Application.
As a reminder, the classes that we are working with are SunsetTDD and SunsetTDDTest.
Implementing GetSunrise
The first step is pretty easy: we just need to implement the "GetSunrise" method of our class. This is pretty similar to our "GetSunset" method.
We'll start with a test:
This looks like our test for "GetSunset" that we saw yesterday. It uses a mock object to get the service data, and our expected output is February 15, 2015 at 6:35:18 a.m.
Of course, this fails. That's because our method is still not implemented:
But we'll grab the functionality from our other method (with appropriate changes for sunrise):
With this code in place, we have a passing test. (And we can see this in the "1/1 passing" note of the Code Lens information.)
Caching Functionality
Now we need to move on to something a bit more difficult: caching. We don't want to make a service call every single time we need the sunrise or sunset data. So, we'll make the service call and save off that data (with the date that the data refers to). Since we're generally only dealing with on day at a time, this is sufficient to reduce the number of service calls that the application needs to make.
Caching Test #1
We'll start by writing a unit test to test for the caching functionality:
Just like with other tests, we create a mock object to supply us with the data. But unlike the other tests, we're calling the "GetSunset" method twice with the same date parameter. Ideally, we should only make one service call in this scenario.
And that's exactly what Moq allows us to check for. Notice the last line of our method. We're using our mock object ("serviceMock") and using the "Verify" method to see how many times a particular method is called. In this case, we want to know how many times the "GetServiceData" method is called. This is the method that actually gets data from the service.
We can see from the 2nd parameter of the "Verify" method that we are expecting this to be called only one time. But that's not what's happening (as we can see from the failing unit test).
If we look at the unit test message, we see why the test failed:
This tells us exactly what we expect to find. Our unit test is expecting that the service will be called once, but it is actually called 2 times. No surprise since we have not yet implemented the cache.
Cache Implementation
To implement the cache, We'll add 2 fields to our class:
These will hold the data that actually comes from the service as well as the date that was used as a parameter to get that data. Again, since we're only dealing with one date at a time (generally), this simple cache will work for us.
To populate the cache, we've created a new method:
This method is fairly straight-forward. It checks to see if the cache date is the same as the date that we're looking for. If it is *not* the same, then it calls the service to get fresh data and populates our 2 cache fields. If the dates do match, then we simply return our cached data.
The last step is to use this in our "GetSunset" method. This is as simple as swapping our our call to "SunsetService.GetServiceData" (that actually calls the service) with our new method "GetServiceData" (which will use the cache).
With this in place, our test passes. But we have a few more scenarios to test.
Caching Test #2
Our next scenario is to test the cache by calling "GetSunrise" multiple times. Here's our test:
And this test fails. That's because we still need to update the "GetSunrise" method to use our new caching method.
And that's easy enough to do:
Now our test passes. Now in my normal coding, I would have a tendency to have updated *both* the "GetSunset" and "GetSunrise" methods at the same time. But since I'm practicing my TDD, I've been resisting the urge to update code before I've written a test for it.
Additional Tests
Just because our cache is working doesn't mean that we're done with testing.
Caching Test #3
As another scenario, I want to check that "GetSunset" and "GetSunrise" both share the same cache. Here's the test:
Notice that instead of calling the same method twice, we call "GetSunrise" one time and "GetSunset" one time (both with the same date parameter). Our expectation is that our service only gets called once due to the cache.
And that's exactly what we see. This test passes without us needing to modify the code.
Caching Test #4
As our last test, I want to make sure that the cache is *not* used when we call methods with different date parameters. Here's that test:
Here we have 2 different date variables ("date1" and "date2"). And we use these to call the "GetSunset" method with different parameters. This time, our "Verify" is a little different: it expects that our service will be called exactly 2 times.
And that's the behavior that we get. This test passes without needing to modify any of our code.
"Test First" Does Not Mean We Stop When the Code is Written
The moral of this is that we aren't necessarily done creating tests after our code is in place. We need to make sure that we test various scenarios to make sure that we have a good set of tests in place. So even if we have "TDD'd" all of our code creation, we need to check for thoroughness in our tests.
There are probably a few more test scenarios that I need for this library. And I'll review the tests some more to look for gaps. (This is one of the things I hope that Smart Unit Tests will be able to help us with once it is released.)
Wrap Up
More coding practice is good. I'm glad that I kept going with this. I wasn't quite sure how I would handle the caching functionality. And it turns out that I ended up with similar code to my original implementation. The methods are a little bit different (and I think a little bit cleaner).
The more I do this, the more I understand the advantages and disadvantages of using TDD. I still need to try this with different types of code (including libraries that call into a database or do data validation). But this is a good start, and I'll keep exploring.
I encourage you to explore different techniques and technologies. Pick the ones that are most useful to you and incorporate them into your development process.
Happy Coding!
You can get the code for this in the sunset-tdd branch of the GitHub project: jeremybytes/house-control. And a collection of all the articles for this project are available here: Rewriting a Legacy Application.
As a reminder, the classes that we are working with are SunsetTDD and SunsetTDDTest.
Implementing GetSunrise
The first step is pretty easy: we just need to implement the "GetSunrise" method of our class. This is pretty similar to our "GetSunset" method.
We'll start with a test:
This looks like our test for "GetSunset" that we saw yesterday. It uses a mock object to get the service data, and our expected output is February 15, 2015 at 6:35:18 a.m.
Of course, this fails. That's because our method is still not implemented:
But we'll grab the functionality from our other method (with appropriate changes for sunrise):
With this code in place, we have a passing test. (And we can see this in the "1/1 passing" note of the Code Lens information.)
Caching Functionality
Now we need to move on to something a bit more difficult: caching. We don't want to make a service call every single time we need the sunrise or sunset data. So, we'll make the service call and save off that data (with the date that the data refers to). Since we're generally only dealing with on day at a time, this is sufficient to reduce the number of service calls that the application needs to make.
Caching Test #1
We'll start by writing a unit test to test for the caching functionality:
Just like with other tests, we create a mock object to supply us with the data. But unlike the other tests, we're calling the "GetSunset" method twice with the same date parameter. Ideally, we should only make one service call in this scenario.
And that's exactly what Moq allows us to check for. Notice the last line of our method. We're using our mock object ("serviceMock") and using the "Verify" method to see how many times a particular method is called. In this case, we want to know how many times the "GetServiceData" method is called. This is the method that actually gets data from the service.
We can see from the 2nd parameter of the "Verify" method that we are expecting this to be called only one time. But that's not what's happening (as we can see from the failing unit test).
If we look at the unit test message, we see why the test failed:
This tells us exactly what we expect to find. Our unit test is expecting that the service will be called once, but it is actually called 2 times. No surprise since we have not yet implemented the cache.
Cache Implementation
To implement the cache, We'll add 2 fields to our class:
These will hold the data that actually comes from the service as well as the date that was used as a parameter to get that data. Again, since we're only dealing with one date at a time (generally), this simple cache will work for us.
To populate the cache, we've created a new method:
This method is fairly straight-forward. It checks to see if the cache date is the same as the date that we're looking for. If it is *not* the same, then it calls the service to get fresh data and populates our 2 cache fields. If the dates do match, then we simply return our cached data.
The last step is to use this in our "GetSunset" method. This is as simple as swapping our our call to "SunsetService.GetServiceData" (that actually calls the service) with our new method "GetServiceData" (which will use the cache).
With this in place, our test passes. But we have a few more scenarios to test.
Caching Test #2
Our next scenario is to test the cache by calling "GetSunrise" multiple times. Here's our test:
And this test fails. That's because we still need to update the "GetSunrise" method to use our new caching method.
And that's easy enough to do:
Now our test passes. Now in my normal coding, I would have a tendency to have updated *both* the "GetSunset" and "GetSunrise" methods at the same time. But since I'm practicing my TDD, I've been resisting the urge to update code before I've written a test for it.
Additional Tests
Just because our cache is working doesn't mean that we're done with testing.
Caching Test #3
As another scenario, I want to check that "GetSunset" and "GetSunrise" both share the same cache. Here's the test:
Notice that instead of calling the same method twice, we call "GetSunrise" one time and "GetSunset" one time (both with the same date parameter). Our expectation is that our service only gets called once due to the cache.
And that's exactly what we see. This test passes without us needing to modify the code.
Caching Test #4
As our last test, I want to make sure that the cache is *not* used when we call methods with different date parameters. Here's that test:
Here we have 2 different date variables ("date1" and "date2"). And we use these to call the "GetSunset" method with different parameters. This time, our "Verify" is a little different: it expects that our service will be called exactly 2 times.
And that's the behavior that we get. This test passes without needing to modify any of our code.
"Test First" Does Not Mean We Stop When the Code is Written
The moral of this is that we aren't necessarily done creating tests after our code is in place. We need to make sure that we test various scenarios to make sure that we have a good set of tests in place. So even if we have "TDD'd" all of our code creation, we need to check for thoroughness in our tests.
There are probably a few more test scenarios that I need for this library. And I'll review the tests some more to look for gaps. (This is one of the things I hope that Smart Unit Tests will be able to help us with once it is released.)
Wrap Up
More coding practice is good. I'm glad that I kept going with this. I wasn't quite sure how I would handle the caching functionality. And it turns out that I ended up with similar code to my original implementation. The methods are a little bit different (and I think a little bit cleaner).
The more I do this, the more I understand the advantages and disadvantages of using TDD. I still need to try this with different types of code (including libraries that call into a database or do data validation). But this is a good start, and I'll keep exploring.
I encourage you to explore different techniques and technologies. Pick the ones that are most useful to you and incorporate them into your development process.
Happy Coding!
Subscribe to:
Posts (Atom)


















































