No App Is An Island

No app is an island entire of itself; every app
is a piece of the continent, a part of the main

(Apologies to John Donne)

It’s very easy to be so focussed (either as a developer/team/department) on a single app/website/etc. that its place in the “continent” can be forgotten. This is not really a fault of the individual developer/team, rather more an organization problem.

Take the example of a bank’s mobile app in the app store. The journey of a person and the app is much bigger than just using the app itself. How did the person hear about the app in the first place and how did they find it in the app store (just by searching or did the bank have a poster with a QR code on it?), do they log into the app with existing Internet banking details (and if so how did they set up their account – on the banks website?), what happens when they close their accounts or want to uninstall the app? What happens if there is a problem with the app and they phone the call centre? If a new version of the app is released that doesn’t work will that cause an influx of calls to the call centre, overloading its capacity and potentially missing out on sales opportunities from other calls?

At a more technical level, what APIs does the app call into, do those APIs in turn interface with other systems (e.g. a banking mainframe). If the mainframe errors what effect does that have on the API and thus the app and the person using the app?

If a customer has a problem with the app and goes into a bank for help, are the customer service representatives there able to help? If not do they have a “hotline” number to  get straight through to IT/specialist app support personnel?

As developers we are also all consumers of software, sometimes this gives us more patience – sometimes less – when things are designed badly or don’t work.

If as software developers we get frustrated what is the “normal” person using software feeling?

Perhaps it’s time that every sufficiently large company had a dedicated user experience team that is intimately familiar with the entire “continent”/user journey. These “UX user champions” can then be part of development teams to ensure that indeed, no app is an island.

SHARE:

New Pluralsight Course: Feature Flag Fundamentals with Microsoft Feature Management

My latest Pluralsight video training course was just published just in time for some holiday season learning! :)

From the description: “Releasing software to production can be hard, risky, and time-consuming, especially if there is a problem and you need to roll back the deployment. In this course, Feature Flags Fundamentals and Microsoft Feature Management, you’ll gain the ability to effectively and efficiently manage the development and deployment of features. First, you’ll explore how to configure and use feature flags in code. Next, you’ll discover how to control features and HTML rendering using Microsoft feature flags in an ASP.NET Core app. Finally, you’ll learn how to customize Microsoft Feature Management and even manage features from Azure. When you’re finished with this course, you’ll have the skills and knowledge of Microsoft Feature Management needed to effectively deploy and manage features in production.”

You can read more about the course over on the official course homepage. Or start watching with a free trial today.

SHARE:

Approval Tests: Assert With Human Intelligence

In the previous article I described how the Approval Tests library can help reduce the amount of assert code that needs to be written. The second benefit of using Approval Tests is the ability to use innate human intelligence to decide if the result of the test is correct.

Imagine a scenario where you need to assert that a text-to-speech generator has generated the correct output. In this example the output could be a byte array representing a .WAV or .MP3 sound file. How would you write traditional asserts to test this output?

As another example, suppose you had to test code that applied a creative filter to an input photograph, this could be some sort of “make skin tones look nice” filter, the output in this case would be a modified image file. How would you assert that the output photo looked “nice”?

In cases like these using traditional asserts may be impossible or very time consuming to implement, there is no Assert.Speech(…) or Assert.LooksNice(…).

This is where the Approval Tests library offers great benefits. You could simply write Approvals.Verify(speechWavBytes); or Approvals.Verify(processedImageBytes); In the case of the sound file you could listen to it and decide if it sounds correct. In the case of the processed photo, you could look at it on screen and use human intelligence to decide if it “looks nice”.

Once you are happy you can approve the results and then in future tests runs if the output accidentally changes due to a bug the tests will fail.

If you want to see Approval Tests in action and learn more about how they can make your testing life easier check out my Approval Tests for .NET Pluralsight course which you can currently start watching for free today with a Pluralsight free trial.

SHARE:

Approval Tests: Write Tests More Quickly

Sometimes assert code in tests gets big and messy and complicated when the output we’re testing is complex.

Approval Tests is a library that can help simplify assert code. The library has other benefits/use cases which I’ll cover in future posts such as using human intelligence to judge if the output is correct; providing a safety net when refactoring legacy code that has no tests; and even testing view rendering.

In the following test code, notice the assert phase:

[Fact]
public void TraditionalAsserts()
{
    var lines = new List<string>
    {
        "Widget sales: 42",
        "Losses: 99",
        "Coffee overheads: 9,999,999"
    };

    var sut = new ReportGenerator(title: "Annual Report",
                                    footer: "Copyright 2020",
                                    lines);

    string report = sut.Generate();

    // Notice the complexity of the asserts below
    Assert.Equal("Annual Report", report.Split(Environment.NewLine)[0]);
    Assert.Equal("Widget sales: 42", report.Split(Environment.NewLine)[2]);
    Assert.Equal("Losses: 99", report.Split(Environment.NewLine)[3]);
    Assert.Equal("Coffee overheads: 9,999,999", report.Split(Environment.NewLine)[4]);
    Assert.Equal("Total lines: 3", report.Split(Environment.NewLine)[6]);
    Assert.Equal("Copyright 2020", report.Split(Environment.NewLine)[8]);
            

    // We could also have just asserted using a long expected string rather than individual line asserts
}

And for reference the ReportGenerator class looks like the following:

public class ReportGenerator
{
    public string Title { get; }
    public List<string> Lines { get; }
    public string Footer { get; }

    public ReportGenerator(string title, string footer, List<string> lines)
    {
        Title = title;
        Footer = footer;
        Lines = lines;
    }

    public string Generate()
    {
        var report = new StringBuilder();

        report.AppendLine(Title);
        report.AppendLine();

        foreach (var line in Lines)
        {
            report.AppendLine(line);
        }


        report.AppendLine();
        report.AppendLine($"Total lines: {Lines.Count}");

        report.AppendLine();
        report.AppendLine(Footer);

        return report.ToString();
    }
}

So in the test there are 6 lines of assert code:

Assert.Equal("Annual Report", report.Split(Environment.NewLine)[0]);
Assert.Equal("Widget sales: 42", report.Split(Environment.NewLine)[2]);
Assert.Equal("Losses: 99", report.Split(Environment.NewLine)[3]);
Assert.Equal("Coffee overheads: 9,999,999", report.Split(Environment.NewLine)[4]);
Assert.Equal("Total lines: 3", report.Split(Environment.NewLine)[6]);
Assert.Equal("Copyright 2020", report.Split(Environment.NewLine)[8]);

If the output was more complex or bigger (for example 100’s or 1000’s of lines of text) then the assert code would get even more messy and harder to maintain. Or what if the output was some binary representation such as an array of bytes representing a generated image or text to speech sound file?

It’s in these cases when dealing with complex output that Approval Tests can help to simplify the assert code as shown in the following test:

[Fact]
[UseReporter(typeof(DiffReporter))]
public void ApprovalTestsVersion()
{
    var lines = new List<string>
    {
        "Widget sales: 42",
        "Losses: 99",
        "Coffee overheads: 9,999,999"
    };

    var sut = new ReportGenerator(title: "Annual Report",
                                    footer: "Copyright 2020",
                                    lines);

    string report = sut.Generate();

    Approvals.Verify(report);
}

Notice in the preceding code the line: Approvals.Verify(report); This line calls Approval Tests and will create a new  “received” .txt file in the test project. You can examine this text file and if it is correct rename it to be an “approved” file. When the test runs in the future, Approval Tests will use the approved file (which should be added to source control) and if the generated report is the same then the test will pass, otherwise the test will fail and a new received file will be output. The [UseReporter] attribute lets you specify how to visualize approval failures, in this example by using a diff tool, and there’s a number of other reporters that come out of the box that you can use.

If you want to see Approval Tests in action and learn more about how they can make your testing life easier check out my Approval Tests for .NET Pluralsight course which you can currently start watching for free today with a Pluralsight free trial.

SHARE:

Coding with Compassion

From an engineering perspective we judge code by its complexity, test coverage, etc. These are all empirical measurements that give us an indication of the qualities that the code base possesses.

We say things like: "we have 90% code coverage and 2764 unit tests".

We may give ourselves targets such as:

  • No class longer than 100 lines of code;
  • 100% code coverage for new tests;
  • Treat all compiler warnings as errors;
  • and so on...

These are all mechanical, empirical, engineering type things and there is nothing wrong with that.

I think there may be another aspect to the way we think about software; a more human-centric way - Compassionate Coding.

All human beings are the same. We all want happiness and do not want suffering.

Dalai Lama

Compassionate Coding has two aspects: compassion for the end-user and compassion for the next developer who has to understand our code.

More...

SHARE: