The Basic Hello World

The best way of learning a new language is still to write the classic Hello World application. I can’t count the number of times I’ve started a new project using this simple application. For example, this is the simplest Hello World application that can be done in C#:

After launching Visual Studio and having clicked File → New Project → Windows → Console Application, all you have to do is complete the Main method.

using System;
namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello World!");
    }
  }
}

But today this minimalist version of Hello World isn’t enough. Let’s look at what is missing from this example to make it more complete.

Prerequisites

Here’s what you’ll need to execute this tutorial:

  • Visual Studio (Community or Professional);
  • a Visual Studio Online account.

Code Isn’t Everything

While it’s interesting to see this code compiling and running on our workstation, this is just the beginning. Writing code and running it on its development environment is a piece of cake. All development tools offer a variety of features to facilitate this aspect of software development. Complications come later, often at the time of deployment, but sometimes a little earlier.

Let’s look at the full development cycle of an application.

application-cycle-940x348-1

Our Hello World example only concerns the development part of this process, but more often complex issues arise during other steps. It is therefore essential to enhance our Hello World using these steps.

For now, let’s set aside the steps prior to development and focus on the sub-stages of development: design, construction, and testing.

Code That Smells

In this code example, as simple as it may be, part of it already smells. We commonly refer to these as code smells. Can you see what’s wrong? How can we test this code?

Towards a Business Version of Hello World

For me, the first step would be to take the business logic out of the Main. In this case, it’s pretty simple:

using System;
namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      var greeter = new Greeter();
      greeter.Hello("World");
    }
  }
}
using System;
namespace HelloWorld
{
  public class Greeter
  {
    public void Hello(string name)
    {
      Console.WriteLine($"Hello {name}!");
    }
  }
}

This modification doesn’t seem significant, but it enables us to add tests. By placing the code in a non-static class and method, we can inject dependencies for the tests. What dependency is to be replaced in the Greeter class?

For now, the Greeter class writes directly into the console. Although it’s possible to intercept the console output, this is not ideal for tests. So we’ll go with a dependency injection. Let’s define the interface to be injected.

namespace HelloWorld
{
  public interface IReceiver
  {
    void Say(string text);
  }
}

Then, an implementation of this interface has to be created:

using System;
namespace HelloWorld
{
  class ConsoleReceiver : IReceiver
  {
    public void Say(string text)
    {
      Console.WriteLine(text);
    }
  }
}

We can now replace the implementation in the Greeter class:

namespace HelloWorld
{
  public class Greeter
  {
    private readonly IReceiver _receiver;
    public Greeter(IReceiver receiver)
    {
      _receiver = receiver;
    }
    public void Hello(string name)
    {
      _receiver.Say($"Hello {name}!");
    }
  }
}

One thing that should be noted about object-oriented design is that the type of object and its relationship with the others must be properly identified. In the present case, the Greeter class is clearly a processing class responsible for “greeting”, but the Greeter class cannot function alone. It cannot greet in a void. It needs a message receiver. This dependency must therefore be added for the system to function.

To maintain the original functionality, we inject a ConsoleReceiver instance:

namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      var greeter = new Greeter(new ConsoleReceiver());
      greeter.Hello("World");
    }
  }
}

Everything is now in place to conduct our first test. In Visual Studio, choose FileAdd New Project, TestUnit Test Project, and name the project “HelloWorld.Tests”.

To test the Greeter class, we must create a mock. A mock is an object that can take the place of another to verify the expected behaviour. So here is the mock for IReceiver:

namespace HelloWorld.Tests
{
  public class ReceiverMock : IReceiver
  {
    private string _text;
    public void Say(string text)
    {
      _text = text;
    }
    public string Text
    {
      get { return _text; }
    }
  }
}
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace HelloWorld.Tests
{
  [TestClass]
  public class GreeterTests
  {
    [TestMethod]
    public void Reveiver_should_be_greeted()
    {
      var receiver = new ReceiverMock();
      var greeter = new Greeter(receiver);
      greeter.Hello("World");
      Assert.AreEqual("Hello World!" + Environment.NewLine, receiver.Text);
    }
  }
}

Although our mock accomplishes the task, it’s not what we usually use. It would be too costly to write all the mocks needed for all possible scenarios. We usually use libraries. For this example, we will use Moq. To add a library to our test project, the recommended method is to use Nuget. Nuget is a dependency manager that uses a file to store the list of dependencies added. This file will then be used to reinstall these same dependencies when we compile the code on another machine.

In Visual Studio, right click on the test project, select Manage nuget packages, Browse, and Search: moq to Moq by Daniel Cazzulino. Select and Install. You will see that the package.config. file is added.

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Castle.Core" version="3.3.3" targetFramework="net461" />
  <package id="Moq" version="4.5.21" targetFramework="net461" />
</packages>

 

Now we can delete the ReceiverMock.cs file and rewrite the test as follows:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace HelloWorld.Tests
{
  [TestClass]
  public class GreeterTests
  {
    [TestMethod]
    public void Reveiver_should_be_greeted()
    {
      var mock = new Mock<IReceiver>();
      var greeter = new Greeter(mock.Object);
      greeter.Hello("World");
      mock.Verify(m => m.Say("Hello World!"), Times.Once);
    }
  }
}

Now that we have a complete project with tests (at least one), the next step is to add this project to a version control system. We will use Git. With Visual Studio 2015, it’s really easy to create your Git repo. At the bottom right of the window, select Publish to Git. This operation will:

  • create a .gitattributes file to change Git’s configuration;
  • create a .gitignore file to exclude all unwanted files;
  • add these two files into a commit;
  • add all the project files into another commit.

Then, to make these changes available to other developers, and above all to the build server (to come), the repo must be published. Visual Studio offers to publish on Visual Studio Team Services or on GitHub. We will use Visual Studio Team Services for now.

By clicking on Publish Git Repo, Visual Studio creates a repo on Visual Studio Team Services, makes the changes, and links this repo remote to the local repo as the origin. Now, if you take a look at the code section of the project, you should see your updated code on the TFS Online server. You can then define a build. Follow these instructions:

Once the build is created, you can request a new build by clicking on Queue new build…

If everything went well, you should have successfully completed a build. You can explore the various tabs of the build result to see your project statistics.

Conclusion

Making a HelloWorld today requires keeping in mind the entire software development cycle process. This simple example illustrates the various steps to take in order to be successful. The most important thing to ensure the successful completion of a project is to do these steps as soon as possible. If they are properly integrated in the development process, you will reduce long-term problems and more easily avoid the famous “Works on my machine”.

Read Bruno Barrette’s blog post on the mobile version of the Hello World.

Previous post

The distinction between compliance and engagement

Next post

Five tips for successful daily scrums

éric de carufel

Passionate, concerned, and careful are qualities describing Éric, for whom software development is a constant quest for improvement to strike a balance between perfection and client needs. His architectural approach is simple: to develop an architecture where it is easier to apply good practices than bad ones.

His involvement as a speaker and blogger is recognized by Microsoft, which awarded to him the prize of "Most Valuable Professional in Visual C#" (C# MVP) every year between 2009 and 2015.

No Comment

Leave a reply

Your email address will not be published. Required fields are marked *