Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

Time Tortoise: Three Test Topics

By Duncan Smith Mar 8 0

Console

This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.

One of the preconditions for using test-driven development is a willingness to spend time figuring out how to test your project in an automated way. Just as research and experimentation is required to get your program to do what you want, TDD requires research and experiments that lead to test code.

This week, I have three test topics: testing UI behavior without a UI, using a console application for testing, and testing time-related features.

Testing UI Behavior Without a UI

One of the primary goals of MVVM is to minimize user interface code, which tends to be difficult to test. But ultimately the user cares about how the UI works, not whether all of your View Model tests pass. So if your UI is broken but your tests aren’t, you have to find a way to reproduce the problematic UI behavior in a test. That’s the scenario I ran into this week.

Last week, I added code to support deleting an activity. In proper TDD form, I wrote the tests first, then implemented the code. However, I didn’t implement the View code (i.e., the Delete button). This week, when I added a button and tried it out, I got this error message:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException was unhandled by user code. Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

The Delete method in MainViewModel had just two lines:

Activities.Remove(SelectedActivity);
_repository.DeleteActivity(SelectedActivity);

It is supposed to work as follows: remove the selected activity from the in-memory activities list (line 1), and then delete it from the database (line 2). But because of XAML binding, line 1 has side effects. Activities is bound to a ListView control. Removing the selected activity from Activities causes it to also be removed from the ListView. This means that the activity in question can no longer be selected (since it’s gone). The control handles this by setting its SelectedIndex property to -1. This triggers more data binding, in this case an update to SelectedActivity that initializes it to an empty activity. Therefore, the SelectedActivity being deleted in line 2 is not the activity that was removed from the list. It’s a new activity with id 0, and Entity Framework doesn’t know anything about it.

Before we fix this bug, or any bug, TDD requires that we first write a unit test to reproduce it. Since the bug is related to the behavior of a UI control, does that mean we need a UI test to reproduce it? That would work, but UI tests are slower and more complex than unit tests, so we want to avoid them if possible. Is there another way to reproduce the problem?

To reproduce the exact error in question, we need an Entity Framework dependency. Fortunately, we have an in-memory database that we can hook EF up to without slowing down the test much.

To avoid depending on an actual ListView, we can simulate just the part of the ListView that responds to row removal. Here’s that simulated ListView:

mvm.Activities.CollectionChanged +=
    delegate (object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Remove)
        mvm.SelectedIndex = -1;
};

mvm.Activities is an ObservableCollection, a type of collection that is normally attached to a control through data binding. In that normal case, the control then listens for events that it’s interested in, like CollectionChanged, which fires “when an item is added, removed, changed, moved, or the entire list is refreshed.” Since we don’t want to depend on a UI control, we instead have a delegate that runs when the event fires. If the change to the collection is a Remove action, then the delegate sets SelectedIndex = -1, just as the ListView would do.

The rest of the test (not shown here) just carries out the steps required to reproduce the bug: create an activity, save it, and then try to delete it. Just as when the steps are carried out manually in the UI, we get a DbUpdateConcurrencyException. So this integration test successfully reproduces the problem.

As is often the case with TDD, the fix requires less code than the test: just keep a copy of the selected activity. Since the copy is in a local variable, it isn’t affected by the ListView selection change, so we can use it for the EF delete operation:

var selectedActivity = SelectedActivity;
Activities.Remove(selectedActivity);
_repository.DeleteActivity(selectedActivity);

Re-running the test verifies that this solves the problem.

Although it took some work to reproduce this problem with a test, the test is now available in the future to protect against regressions. It also resulted in knowing a bit more about how ListView works.

Using a Console Application for Testing

One of the characteristics of a testable component is that it can be used by multiple consumers. One consumer could be a graphical user interface — in this case, a XAML page. Another consumer is the unit test suite, which tries to simulate the graphical UI. But once you have two consumers, it’s not hard to add another one, like a command-line interface.

In the future, it may be useful to have a Time Tortoise command-line interface for end-users. Some actions are more efficiently carried out from the command line than with a mouse or touch screen. But for now, I’m using a console app project just for testing purposes.

The Time Tortoise console app that I added this week is best used for exploratory testing: trying out the view model to see how it works. Since the console app doesn’t have the Assert functionality of an xUnit.net project, verifying results is a manual process. Therefore, it’s not appropriate for writing tests that will be run regularly.

This week, I used the console app to see which events are fired when activities are added and deleted from the activity list. The output is shown at the top of this post. I used the results of this experiment to write the integration test that I described above.

.NET Core console apps are a bit different from regular .NET console apps. By default, they compile to a DLL rather than an EXE. You then run them using the dotnet command-line tool:

dotnet TimeTortoise.Console.dll

There’s also an option to compile to EXE, which I haven’t tried yet.

Testing Time-Related Features

So far, the Time Tortoise features I have implemented have been fairly basic: creating, retrieving, updating, and deleting activity names in a database. I’m now moving on to time segments: start and end dates and times associated with an activity. For example, I might want the app to record that I worked from 3/7/2017 10:00 PM to 3/7/2017 11:00 PM editing a blog post.

Dates and times are complicated to deal with because the human clock and calendar system is complicated. But even ignoring that, date- and time-related features are inherently problematic for unit tests, because they are often nondeterministic. For example, a basic user story for Time Tortoise is “select an activity and start timing it.” The “start timing” part of that story uses the current date and time. But the current date and time keeps changing. This makes it difficult to write a unit test that asserts against a fixed value.

An effective way to solve this problem is to use dependency injection. This is the same approach that I am using for selecting between real and mock repository classes. MainViewModel can accept either a mock repository or a real repository in its constructor, because they both implement the same interface, IRepository.

Similarly, components that need to know the current date and time shouldn’t just call DateTime.Now, since this would prevent unit tests from getting consistent results. Instead, they should accept an IDateTime, which could be either a mock date/time object that returns known values, or a standard date/time object that gets its results from the system clock.

There’s no standard IDateTime interface in .NET. I could take a dependency on NodaTime, which has other advantages in addition to testability. And I may do that at some point. But for now I’m just using a simple IDateTime interface and a SystemDateTime class that implements the interface by delegating to the standard DateTime class.

Categories: TT

Prev
Next

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • LeetCode 11: Container With Most Water February 24, 2021
  • LeetCode 47: Permutations II February 17, 2021
  • LeetCode 897: Increasing Order Search Tree February 10, 2021
  • LeetCode 394: Decode String February 3, 2021
  • LeetCode 1022: Sum of Root To Leaf Binary Numbers January 27, 2021
  • LeetCode 1288: Remove Covered Intervals January 20, 2021
  • LeetCode 227: Basic Calculator II January 13, 2021
  • A Project for 2021 January 6, 2021
  • Lessons from the 2020 LeetCode Monthly Challenges December 30, 2020
  • Quora: Are Math Courses Useful for Competitive Programming? December 23, 2020
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2021 Duncan Smith