Sorting Kata - Linq Order By

In the setup we created a solution with two projects. The purpose of these two projects was for a Sorting Kata. Now we write a sorting method to test.

BookSorting.Test/SortingTest.cs
using System.Collections.Generic;
using System.Linq;

using NUnit.Framework;

namespace BookSorting.Test
{
    [TestFixture]
    public class SortingTest
    {
        [Test]
        public void Authors_SortedByLastName()
        {
            var book1 = new Book
                        {
                            Title = "Wait: The Art and Science of Delay",
                            Author = new Author
                                     {
                                         LastName = "Partnoy",
                                         FirstName = "Frank",
                                     },
                        };

            var book2 = new Book
                        {
                            Author = new Author
                                     {
                                         LastName = "Watt",
                                         FirstName = "Andrew",
                                     },
                            Title = "Beginning Regular Expressions",
                        };

            var books = new List<Book>
                        {
                            book1,
                            book2,
                        };

            var bookshelf = new Bookshelf();

            books = bookshelf.LinqOrderBy(books);

            Assert.AreEqual(books.First(), book1);
        }
    }
}

We use CTRL+. to add the Bookshelf class to the BookSorting project. Then we use it again to add the LinqOrderBy method that accepts the list of books. Our goal is for the method to return a sorted list. Then we can assert that the correct book appears first.

Running the test causes the following error:

1 test failed: System.NotImplementedException

Hurray, the test failed. We use the Red, Green, Refactor pattern from Kent Beck's book, Test Driven Design By Example.

The problem is, it failed for the wrong reason. We want to see a failure because the books are not sorted. Let's take a look at the Bookshelf class.

BookSorting/Bookshelf.cs
using System;
using System.Collections.Generic;

namespace BookSorting
{
    public class Bookshelf
    {
        public List<Book> LinqOrderBy(List<Book> books)
        {
            throw new NotImplementedException();
        }
    }
}

Well, that explains our "NotImplementedException".

So, what is it going to take to get this test to fail for the right reason? Let's take a look at Uncle Bob's Transformation Priority Premise. The first transformation we should try is "nil". Can we get the test to fail by doing nothing? No, our code will not compile unless we return something. The next step is a constant. Let's just return the books list as is.

BookSorting/Bookshelf.cs
using System.Collections.Generic;

namespace BookSorting
{
    public class Bookshelf
    {
        public List<Book> LinqOrderBy(List<Book> books)
        {
            return books;
        }
    }
}

1 test passed

This is why we Red, Green, Refactor. Our test passed when we didn't want it to. Let's flip the order we put the books into the list.

BookSorting.Test/SortingTest.cs
var books = new List<Book>
{
    book2,
    book1,
};

1 test failed: Expected: <BookSorting.Book> But was: <BookSorting.Book>

Alright, it failed for the right reason. Now we need to deal with that failing test message. It's not very helpful. We have two options, change the Assert method to have a message, or overwrite the Book ToString method to return the name of the book instead of the object name "BookSorting.Book".

Here's an updated assert.

BookSorting.Test/SortingTest.cs
Assert.AreEqual(
    book1,
    books.First(),
    string.Format(
        "Expected {0}, {1}. {2} but was {3}, {4}. {5}",
        book1.Author.LastName,
        book1.Author.FirstName,
        book1.Title,
        books.First().Author.LastName,
        books.First().Author.FirstName,
        books.First().Title));

1 test failed Failed: Expected Partnoy, Frank. Wait: The Art and Science of Delay but was Watt, Andrew. Beginning Regular Expressions Expected: <BookSorting.Book> But was: <BookSorting.Book>

I would not want to write that much code for every assert. Plus, the expected message is still not helpful. Let's overwrite the Book ToString method.

BookSorting/Book.cs
namespace BookSorting
{
    public class Book
    {
        public Author Author { get; set; }

        public string Title { get; set; }

        public override string ToString()
        {
            return string.Format(
                "{0}, {1}. {2}",
                Author.LastName,
                Author.FirstName,
                Title);
        }
    }
}

We can roll back our assert.

BookSorting.Test/SortingTest.cs
Assert.AreEqual(book1, books.First());

1 test failed Expected: Partnoy, Frank. Wait: The Art and Science of Delay But was: Watt, Andrew. Beginning Regular Expressions

I can see the advantage of calling Book.ToString() and getting the author and title instead of <BookSorting.Book> so let's stick with that for now.

We saw a failing test (Red), and we made sure the message was helpful. Now we can make it green.

BookSorting/Bookshelf.cs
using System.Collections.Generic;
using System.Linq;

namespace BookSorting
{
    public class Bookshelf
    {
        public List<Book> LinqOrderBy(List<Book> books)
        {
            return books.OrderBy(b => b.Author.LastName)
                        .ThenBy(b => b.Author.FirstName)
                        .ThenBy(b => b.Title)
                        .ToList();
        }
    }
}

1 test passed

Please contact me about this Kata. I would love to get your feedback.

Let's continue the Kata by using the built-in sort method of a List.