[Guest Post] Visual Studio for Mac Helps You Write Tests

benday

Benjamin

You’re writing tests for your code, right? No? Just say ‘yes’. It’ll make this blog post go a lot easier. So anyway, I’m happy to hear that you’re writing tests for all your code. Whether you’re following the “test first” / Test-Driven Development (TDD) approach or whether you’re just writing some unit tests or integration tests, Visual Studio for Mac has some nice features to make your life as a developer a lot easier.

In this article, I’ll walk you through the process of writing and running unit tests using Visual Studio for Mac.

Some Terms

Before I jump in on the features, let’s get a few terms out of the way. (For more information on the different types of tests and why you might choose one over the other, check out this article.)

What’s a “unit test”? A unit test is a piece of code that exercises and verifies the execution of a piece of code in your application. That application is referred to as the “system under test” or SUT. Let’s say I’m writing a .NET Core application using C#. When I’m writing code, if I have a C# class in my application, I’ll have at least one test class in my test project. For every public method in my application’s class, I’ll have at least one test method in my test class that tests the corresponding application method.

If you’re new to automated testing, you might want to check out this article on unit testing best practices.

Test-driven development (TDD) isn’t a type of test – it’s a way of writing features & tests. TDD is commonly referred to as “test first” and the idea is that you’re going to write your tests before you write your application code. Starting with your tests gets you a better overall design and cleaner code.

Writing your tests first can be a little cumbersome because you’re starting with almost nothing. Thankfully, Visual Studio for Mac has some features that help to make it easier. In this example, I’ll show how we can generate code as part of our test-first development workflow.

Tour of the Sample Code

Source code is available at https://github.com/benday-inc/visual-studio-mac-tdd-demo. This repo contains both “before” code so that you can code along and also an “after” folder that has the completed sample. If you’re interested in starting from scratch, you might want to check out this guide to getting started with the Testing tools in Visual Studio for Mac.

Let’s say that you’re going to write a calculator using test-driven development (TDD). The first step is to start writing your tests. To save time, I’ve created a .NET Core solution in Visual Studio for Mac that consists of an MSTest-based unit project, an class library for my application, and an ASP.NET Core MVC web application.

Visual Studio for Mac - Solution Explorer showing UnitTest1.cs selected.
Figure 1 – The empty calculator solution in Solution Explorer

The class that contains the calculator logic is eventually going to go into the Benday.CalculatorDemo.Api class library project but at the moment that class doesn’t exist. We’re going to start writing our test code in a class called CalculatorFixture in the Benday.CalculatorDemo.UnitTests project.

Rename the Unit Test Class

Let’s start by editing UnitTest1.cs. Right now that class just has the default structure for an MSTest-based unit test in it and the class is named UnitTest1.

New empty unit test method shown in editor.
Figure 2 – The empty calculator fixture test class

Let’s change the class name to be CalculatorFixture.

Unit test shown renamed to CalculatorFixture
Figure 3 – Rename class to be CalculatorFixture

Right now the file name doesn’t match the class name – the file is named UnitTest1.cs and the class name is CalculatorFixture. There’s an easy way to fix that. Click on the “CalculatorFixture” word of the class declaration and press Option-Enter to bring up the Quick Actions and Refactorings menu. One of the options is Rename type to CalculatorFixture. Visual Studio for Mac is smart enough to know that your class name doesn’t match the class name implied by your filename so if you choose this item from the menu, it’ll rename the class for you.

Visual Studio for Mac refactoring menu shown, with option "Rename file to CalculatorFixture.cs" highlighted.
Figure 4 – Use the Quick Actions and Refactorings menu to rename the file to CalculatorFixture.cs

Now if you look at the Solution Explorer window, you’ll see that the filename now says CalculatorFixture.cs. Sure, this isn’t life-changing but it’s one of those little things that makes coding a little more pleasant.

Solution Explorer shown, with CalculatorFixture.cs class selected.
Figure 5 – Renamed file in solution explorer

TDD Step #1: Write Your Tests

Now let’s write the empty structure of our calculator tests by adding test methods for Add(), Subtract(), Multiply(), and Divide(). When I know that I’m going to want to write a test case but I’m not quite ready to write it, I’ll put in a call to Assert.Inconclusive(). If I left the test method code empty, it would look like a passing test but if I put the call in to Assert.Inconclusive(), it’ll provide me a nice placeholder reminding me that I need to come back to implement the tests. It’s not going to fail but it won’t pass either.

Unit test class shown with Add, Subtract, Multiply, and Divide methods which all just call Assert.Inconclusive()
Figure 6 – Test methods for Add, Subtract, Multiply, and Divide marked as Inconclusive

Run the Tests

Now that we’ve got some code, let’s try to run the tests. The easiest way to run all the tests in a test class is to right-click on the class name and choose Run Test(s).

CalculatorFixture class is selected, context menu shown with "Run Test(s)" option highlighted.
Figure 7 – Run Tests in a Test Class

You should see the Tests panel appear and your tests should run.

Tests window displayed, with Add, Divide, Multiply, and Subtract showing as Inconclusive status.
Figure 8 – The tests ran and were inconclusive

Write the Test Code

Now let’s write some test code for the Add() method.

Additional test code has been written in the Add method, adding 2 + 3 and expecting a result of 5
Figure 9 – Code for the Add() test method

If you press Command-B to build your solution, this is going to fail. The error should be something like “CalculatorFixture.cs(39,39): Error CS0246: The type or namespace name ‘Calculator’ could not be found”. This is because there’s no class named Calculator yet.

In the API project, there’s a class called Class1.cs. Let’s rename this class to Calculator and change the filename to Calculator.cs.

After you’ve renamed that class to be Calculator, you’ll still be getting an error because of a missing using statement. In CalculatorFixture.cs, on line 12, click on the word Calculator and then type Option-Enter to bring up the Quick Actions and Refactorings menu.

You should see an option to add a using statement. Choose that option.

Context menu shown, with "using Benday.CalculatorDemo.Api" option highlighted.
Figure 10 – Add the missing using statement

Now that that’s fixed, try to rebuild the solution. You should now be getting an error that says “Error CS1061: ‘Calculator’ does not contain a definition for ‘Add’ and no accessible extension method ‘Add’ accepting a first argument of type ‘Calculator’ could be found”. This is because there’s no method named Add() on the Calculator class.

TDD Step #2: Create an implementation / Add the Missing Method

Alright. So we’re trying to write that Add() method and our test class is failing to compile because that method doesn’t exist yet. Let’s use the Quick Actions and Refactorings menu to generate that method for us.

In CalculatorFixture.cs, on line 19, click on the word Add() and then type Option-Enter to bring up the Quick Actions menu. One of the options should be “Generate method ‘Calculator.Add’”. If you choose this option, Visual Studio for Mac will create a basic implementation for you including the arguments and return values.

Context menu shown, with "Generate method 'Calculator.Add'" selected
Figure 11 – Generate the Add() method

After you run the Generate method action, when you open up Calculator.cs, you’ll now see that there’s an implementation of the Add() method that throws a NotImplementedException.

Generated code for Calculator Add method shown. This method just throws NotImplementedException.
Figure 12 – Generated Add() method

Now when you rebuild your solution, it builds successfully without errors. When you run all your tests, you should now see that the Add test is failing and that the other 3 tests are saying inconclusive.

Test windows is shown, with Add test failing.
Figure 13 – Add test is failing

Implement the Add() Method

Next let’s implement the add method in the Calculator class.

Add method has been changed to return the sum of the two parameters.
Figure 14 – Implement the Add() method

After you’ve added the implementation for the Add() method, re-run the tests. The test for Add should pass.

Test window is shown, this time with the Add test passing.
Figure 15 – The test for Add() passes

Summary

From here you can now implement the rest of the tests using the same process. Visual Studio for Mac’s ability to generate code for you while you’re writing your test-first unit tests can really help you to avoid some common coding headaches.

If you’re looking for more advanced info on automated testing techniques such as how to break up testing dependencies and testing ASP.NET application security, check out my Architecting an ASP.NET Core MVC Application for Unit Testability course on Pluralsight.

About the Author

Benjamin Day is a consultant and trainer specializing in software best practices using Scrum with Microsoft’s DevOps tools. Ben’s main areas of emphasis include Azure DevOps, Scrum, software testing, and software architecture. He is a Microsoft MVP, a certified Scrum trainer via Scrum.org, and a speaker at conferences such as Pluralsight Live and VSLive. When not developing software, Ben’s been known to go running and sea kayaking in order to balance out his love of cheese, cured meats, and champagne. His online courses are available at https://courses.benday.com and at http://www.pluralsight.com. He can be contacted via http://www.benday.com.

 

2 comments

Leave a comment

  • Avatar
    coopejim

    It’s great that you are encouring people to write more tests, and particularly to use TDD. But speaking as someone who has been doing this, and championing it at conferences, magazines and user groups since a previous century, I think that you have some issues you need to address.

    Firstly, why call it CalculatorFixture? It is a test fixture, of course, but we already know that because you’ve marked it with an attribute. Using the word “test” or “fixture” is just not necessary. Also naming fixtures and test methods after class and method names is a trap for young players. We often rename classes and methods, and we have tools to help with that, but they will not help when you’ve mixed in those names in your test code. A much better idea is to use the domain language for these things, as that changes much less frequently. I find naming the test fixture after the feature I’m working on is often a better idea. It is also possible to have > 1 test fixture for one class sometimes (for example, if you have a scientific calculator, there might be a lot of operations to test), and you don’t want to go down the CalculatorFixture1, CalculatorFixture2 route.

    Your test name is too vague. What happens if the test called Add() fails? Is the code under test wrong, or does the test need to change? The name should be descriptive enough for you to tell what is expected.

    Also on the naming front, while you have code under test (or a system under test, or source under test, pick your expression), don’t call it that! Call it what it is, using the domain language. It will make your test much easier to read. You’ve written a test for a calculator, so why not call it that? “systemUnderTest.Add()” means nothing, “calculator.Add()” is much clearer. Remember that TDD is more a design activity than it is a testing activity, so this is your first chance to embed the domain language in your code. Use it!

    Lastly, whenever I see

    // Arrange
    // Act
    // Assert

    I know I’m dealing with a unit testing newbie. If it is not obvious in your test what the separate sections are, then your test code is too complex. Test code is first class code, and should follow the same rules as all the rest. You will need to extract methods, use informative naming, etc. It’s quite common to have a method for the “act” step, btw. Especially if you’re doing TDD, you will often not have settled on the method name or signature, so havig fewer places to change it is better.

    There is further bad advice in that link you include. The naming conventions are poor. There is incorrect information about fakes, stubs and mocks (the correct generic term is test double, and there are differences between fake, mock and stub, besides there being a whole set that are not mentioned). See http://xunitpatterns.com and the related book for better information.

    Then there is nonsense about not using setup and teardown methods. You should, of course, NOT repeat code in every test method. That’s madness in ANY code. It makes your tests extremely fragile, for starters. This advice comes from the xUnit developer, who was wrong about this from the very start (and promptly ignored his own advice and told people to use constructors and destructors for this purpose). You should put all the common setup in one place (usually the setup method), and only put what is DIFFERENT in your tests. That way your tests are much more robust against future code changes. Teardown is a rarer thing (and you need to be careful, as it’s possible that it can be skipped, like if you exit a debugging session, for example). Definitely try not to repeat the constructor call to the code under test all over the place. When you’re TDDing something, you probably haven’t settled on the things to inject into the constructor yet, and that’s much easier to change if there are fewer calls to it (ideally only 1).

    I hope that gives you something to think about.