Devil's advocate

We will continue to demonstrate testing small, but already we have hit our next example. Playing devil's advocate is a useful technique in many circumstances. The way that we play devil's advocate in TDD is by imagining the simplest, and possibly most erroneous, approach to making the test pass. We want to force the test to make the code right instead of writing the code that we believe to be correct. For instance, in this case, the desire is to make the test that was just written pass by adding an Items list. But the test doesn't require that at this point. It only requires that Items exists as a property on the class. There is no designation of a type in the test. So, to play devil's advocate, make the test pass by using Object as the type and setting the Items object to a simple non-null value.

internal class TodoList
{
public object Items { get; } = new object();

public TodoList()
{
}
}

Okay, now all the tests pass but that clearly isn't a proper solution. Thinking small steps, we could force the implementation to have a count, surely that will require it to be a list of Todos. Add the following to the last test:

Assert.Empty();

To make that pass, Items must change:

public IEnumerable<Object> Items { get; } = new List<Object>();

Remember what we discussed about the SOLID principles in Chapter 8What to Know Before Getting Started. We want to use interface segregation and limit ourselves only to the interface we need. We don't need the full IList interfaces capability so we don't need to use it. All that is needed is the ability to iterate over a collection of items. The simplest interface for doing this is IEnumerable.

We still have a problem though: we are using an Object as our enumerable type. We want to use only a specific class. Let's fix that now. Modify the last test one more time to include a type assertion.

[Fact]
public void CanGetTodos()
{
// Arrange
var todo = new TodoList();

// Act
var result = todo.Items;

// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom<IEnumerable<Todo>>(result);
Assert.Empty();
}

Now, update the class, shown as follows:

internal class TodoList
{
public IEnumerable<Todo> Items { get; } = new List<Todo>();

public TodoList()
{
}
}

public class Todo
{
}

As you can see, we added what seemed to be a fairly small test and ended up creating a property, assigning a default value, and creating a class. Can you think of any way we could have made this smaller?

Our next test might verify that the Todo items start as empty, but if we think back to the laws of TDD, the first law is to write a failing test. Right now, if we wrote a test that verified Items to be empty we would expect that test to pass. So, what test should we write?

The test we have decided to write next is a test to verify a means to add a Todo item.

[Fact]
public void AddTodoExists()
{
// Arrange
var todo = new TodoList();
var item = new Todo();

// Act
todo.AddTodo(item);
}

internal class TodoList
{
public IEnumerable<Todo> Items { get; } = new List<Todo>();

public TodoList()
{
}

internal void AddTodo(Todo item)
{
}
}

Up to this point, we have been taking steps that would likely resemble the same steps you would take in normal development, cutting giant swathes of functionality into the code. This is the first test where we stop before we have actually achieved valuable functionality. This is part of taking those small steps though. We could deploy the application right now. It wouldn't be very useful but we do have that option. If we had reached the end of our sprint, the product owner might request that, in order to deploy as soon as possible, we hard-code in some Todo items just so something is available in the UI.

Our next test seems to be fairly straightforward. We will verify that we can actually add a Todo using our new method. There is a catch though because this test is testing functionality and not general class structure. We suggest having a test class specifically for this method.

public class TodoListAddTests
{
[Fact]
public void ItAddsATodoItemToTheTodoList()
{
// Arrange
var todo = new TodoList();
var item = new Todo();

// Act
todo.AddTodo(item);

// Assert
Assert.Single(todo.Items);
}
}

internal class TodoList
{
private List<Todo> _items = new List<Todo>();

public IEnumerable<Todo> Items
{
get
{
return _items;
}
}

public TodoList()
{
}

public void AddTodo(Todo item)
{
_items.Add(item);
}
}

Now, that really was a flying leap off a cliff. That one test nearly changed all of our application code. We just completely changed the implementation of Items, and we added code to the AddTodo method. Is there a way that we could have broken those into two or more steps? We still have a lot to do with this application, and we will cover some of it. But, before we go on, write down the next few tests that you think you would write. Try not to skip this exercise because breaking up functionality into small chunks like this is one of the areas where most developers struggle when learning TDD.

We are going to temporarily pause the forward progress of this sample application because we have already begun to work ourselves into a corner. To prevent getting blocked, we should be testing negative cases first.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.245.219