If you want to build up your tolerance for looking like an idiot (everyone should), try implementing something you’re just beginning to learn live on Twitch…
Oof.
Get the Git
Check out the project on Github
Watch the Replay
Skip to the Good Stuff
Project Update
During yesterday’s stream, I managed to flesh out several more of the entities I’ll be using in the open source learning management system I’m developing live.
We got:
-
Course
entity fleshed out-
CourseLesson
addedCourseLessonAttachment
added
-
Person
addedAuthor
added (which includes aPerson
record, as well)AuthorCourseLesson
many-to-many entity added
Then I embarrassed myself. Hard.
I’m still an absolute neophyte when it comes to TDD and unit testing in general.
I knew that.
So why did I think I’d just pop a service AND a test in live with no trouble?
🤷♂️
Anyhoo. Thank goodness for great community members and chatters. TheGrumpyGameDev (another great live code streamer) was there to rescue me and literally hold my hand through writing my first proper TDD test (on this project).
First he stopped me from even creating the ICourseService
interface.
In TDD (the proper way), you START. WITH. THE. TEST.
His question to me was, “How do you know what your interface should define?”
The answer: your unit test TELLS you what functionality you should implement.
- Write the test
- See the test fail (even to compile at this stage)
- Write the code under test
- See the test pass
- Refactor/move on
And always in the smallest measures possible.
It’s the Red-Green-Refactor pattern, but to a more minute level than I’d considered previously.
So. We (I) fumbled and we (he) got me there.
Here’s the first TRULY properly arranged TDD test I’ve ever written.
[Fact]
public async Task GetCoursesAsync_ShouldReturnExpectedCourseList()
{
// given (arrange)
List<Course> databaseCourses = new List<Course>();
databaseCourses.Add(new Course()
{
Id = 1,
Name = "Course #1"
});
databaseCourses.Add(new Course()
{
Id = 2,
Name = "Course #2"
});
this.appDbContextMock.Setup(db =>
db.SelectCoursesAsync())
.ReturnsAsync(new List<Course>(databaseCourses));
// when (act)
List<Course> actualCourses = await subject.GetCoursesAsync();
// then (assert)
// 1. Actual list of courses == expected courses
// 2. DB was hit once (and no more)
actualCourses.Should().BeEquivalentTo(databaseCourses);
appDbContextMock.Verify(db => db.SelectCoursesAsync(), Times.Once);
appDbContextMock.VerifyNoOtherCalls();
}
And here’s the code it tested (written entirely AFTER the test was written and failing):
public async Task<List<Course>> GetCoursesAsync() => await db.SelectCoursesAsync();
If you’re new to TDD or unit testing, that probably looks absurd.
I mean seriously, WHY would you write 32 lines of code (including comments and new lines, but still) just to test a SINGLE line of code?
Note: the service does call a repository method that just returns a simple dump of the Courses table, but I’m testing business logic, not practically built-in DB functionality
Because now I NEVER, EVER have to worry about whether my SelectCoursesAsync
method is doing what it’s supposed to do, no matter what I change in the system.
It STILL seems like overkill. Why would that method ever change to need to be tested again?
It probably won’t. But it might.
And as I add more and more and more simple, then more complex business logic to my application, I’ll always be able to rely on the suite of tests I develop along the way to be sure I don’t introduce a problem with unintended consequences.
Next Steps in TDD
All that said, I’m still scratching the surface in my description of TDD here and in my own understanding of it.
To that end, I’m checking out two great resources recommended to me yesterday:
- I’ve ordered Test Driven Development: By Example by Kent Beck. Apparently the Bible of TDD.
- I’m gonna watch through the library of videos from JitterTed on TDD (and also check out his upcoming course)
I’ve been sold on TDD (it’s already saved my butt on another project). Now I learn.
Wrapping Up
It was a frustrating but productive stream day for CoreLMS. Next week: more tests and services!
Come hang out, chat, and save my butt, too. I need you. 🤣
- John