10 reasons to use TDD (Test Driven Development)

  1. It will help you improve your OOD

    Proper object oriented design is the key to writing good extendable, maintainable, and stable software. If you pile too much functionality into one big class or one big method, you’re just asking for trouble. TDD makes it easier to adhere to the SRP (Single Responsibility Principle) by encouraging you to create smaller classes with less functionality.

  2. Get it in front of your users faster

    Designing your classes to inherently rely on abstract (mockable) dependencies is a sure way to get on the fast track to building a demo. For example, in a database driven application, a mock data layer can be substituted with generated data to be consumed by the front end. Though somewhat unorthodox, mocking frameworks can work just as well to fake a back end system in a running application as they can for unit test mocking! In software development, getting the product in front of your users can be the most important step, because it gives them a chance to change requirements early when it’s less painful.

  3. Good coverage

    TDD will have you writing a test for every bit of functionality you are coding. You are automatically forcing yourself to have a high code coverage metric if you stick to the cadence: Write breaking test, fix test, refactor.

  4. Quickly verify all functionality

    When refactoring code, it is very useful to be able to quickly verify all existing functionality in an instant. This isn’t necessarily a benefit of TDD itself, but rather having good unit test coverage of business rules. Sometimes in an emergency situation (e.g. an outage due to code bug), we are forced to cut corners and not necessarily write a test first. Having that safety net of unit tests there is a huge confidence booster. Naturally by developing with TDD, you are building a safety net as you go!

  5. It forces you to rely only on abstractions (Dependency Inversion Principle).

    You wouldn’t solder a lamp directly to the electrical wiring in a wall, would you?

    The dependency inversion principle is one of the 5 SOLID principles of object oriented design. It states that when designing a class, any and all of the other classes that are used should be via abstractions. That is to say, a class should not reference another concrete type. When done correctly, test driven development encourages adherence to this principle, because you will always need something to mock when writing a test.

  6. Smaller problems are easier to solve.

    (2 + 2) / 2 – 2 * 6 = ?

    If you understand the order of operations (if you’re here, I’m sure you do), then your brain automatically broke this equation down into solvable parts. Likely, you figured out that 2+2 is 4, and 2 * 6 is 12, so the equation became 4/2 – 12. Then you might just solve 4/2 and finish out with -10. The point is that you broke the larger problem down into smaller chunks, because that’s the easiest way to get to the answer. (Bonus points if you can just look at equations like that and spit out an answer!). Any programmer worth their salt isn’t going to attack a large application by writing one big blob of code. They’re going to understand what the customer wants, break it down into pieces, and build those pieces to fit together for the larger system. TDD is a great way to do just that without completely understanding the big picture immediately.

  7. It feels really good

    I’ve done quite a few projects with TDD now. The first time it feels strange, like it can’t possibly work. You toil for a few days or weeks on writing these individual little bits, solving little tiny problems as you go. The larger problem is not necessarily in your brain the entire time, so it feels foreign. Finally, when it comes time to make a demo, you get to connect all the little pieces, which almost always involves an IoC container for me. This is a very satisfying process and brings me a lot of joy.

  8. Opens up the path for future testing

    This is a topic I have talked about at length. Some may not see the value in this immediately, but I find this extremely important. Simply by following the TDD pattern, you are ensuring future testability of your classes. No one writes bug-free code every time. I can’t tell you how many times I have been seriously happy when it comes time to fix a bug in code that I’ve used TDD with. I come back to find that in order to reproduce the bug, I just have to provide a very specific mock up in a new unit test. The path was laid by me in the past, and now it is super easy to prove that the bug is fixed by fixing a failed unit test.

  9. Stops analysis paralysis

    From Wikipedia:

    Analysis paralysis or paralysis of analysis is an anti-pattern, the state of over-analyzing (or over-thinking) a situation so that a decision or action is never taken, in effect paralyzing the outcome.

    Sure, any new application needs some analysis, but when the above happens, nothing gets done. TDD allows you to get started right away by solving small problems immediately. Sometimes, the bigger picture starts to come together when you start chipping away at the little bits.

  10. Slow is smooth, and smooth is fast

    This is an old saying applied to targeting a firearm. The saying explains that if you move too fast, you’re going to make a mistake and fail. I believe the same saying can be applied to software development as well. The argument against TDD and unit tests in general that I’ve heard in the past is that they slow you down. It’s a natural thought to have: I can either start writing code to solve the problem, or start writing code to test non-existent code, and then write the same code to solve the problem anyway.

    WRONG!

    This argument infuriates me, because it typically comes from someone of power who is trying to justify cutting corners. Sure, if you put two people on solving the same complex problem, one with TDD and one hacking directly towards a solution, the latter is going to finish quicker, but that’s just not a real life scenario long term. With any given application, someone is going to need changes. While TDD might take longer in the initial phases, it pays dividends extremely quickly when changes start pouring in. The class design is decoupled, class responsibilities are seriously limited, so requirements changes very rarely actually mean changing working code! Instead, it is safer and quicker to extend and write new code. Less bugs are created, and new features can be added a lot quicker in the long run.

How does JavaScript Scope work?

In a previous post, I reviewed how JavaScript treats the “this” keyword.. In this post, I want to talk about how JavaScript defines scope. As a C# programmer coming to JavaScript a few years ago, I did not know this fact, and thus I assumed my C# knowledge of “this” and scope would follow to JS.

Only functions create scope in JavaScript

Take the following C# code for example:

public int x = 1; // this is a class member, and thus is scoped to the class instance
void Foo()
{
    if (true)
    {
        int i = 1;
    }
    // i is inaccessible here, because it is scoped to the if block
}

And the following javascript code:

var x = 1; // Variables not declared in a function are global
function foo() {
    if (true) {
        var i = 1;
    }

    alert(i); // This is perfectly legal, and I is accessible here.

    // Any variables declared here are scoped to the function foo.
    // To force scope:
    (function () {
        var y = 1;
        z = 2; // Declare a variable in the global scope by leaving out the var keyword!
    })();
    // y is not accessible here, because it was declared inside of a function
    // an anonymous self executing function still creates scope
}

alert(z); // z is undefined
foo();
alert(z); // z is 2 after running foo().
alert(window.z); // z is attached to the window object because it was declared without var!

Pay attention to the comments, please. Especially the bit about leaving out var, creating a globally scoped variable attached to window.

This can be a big sticking point for developers coming from C# or Java where scope is very different. Many bloggers will take this type of post to the extreme and explain other concepts like closures and prototype, as well as combining the topic of context binding the “this” keyword, but I am keeping this succinct for a reason. I’ve already covered “this” in a previous post, and I can probably do a post on closures and using the prototype more in depth another time.

In my opinion, this topic stands on its own as one of the most confusing points for a developer that is new to JavaScript, so it deserves a separate post.

Multi touch and gesture input in Windows with DPI scaling

I mentioned in a previous post that I am working on a new project related to gesture input. That very day, I hit a wall regarding desktop scaling, and last night I broke through it! Perhaps a topic for another post: with some applications, TDD can get you to a certain point, but integration testing is a must pretty early on.

The FRBTouch project is no exception! There are a few different problems to solve with this project:

  • Touch event capturing
  • Gesture detection
    • Taking touch events and making gestures
    • e.g. One touch event down then up is a tap
  • Coordinate translation
    • Taking window coordinates and translating them in an application (e.g. a FlatRedBall game)

The first two bullet points turned out to be the easiest, because they were mockable. For instance:

        public void single_tap_registers_from_one_touch()
        {
            // Arrange
            _container
                .Arrange<ITouchEventProvider>(p => p.Events)
                .Returns(new List<TouchEvent>
                {
                    new TouchEvent
                    {
                        Id = 1,
                        Position = Vector2.Zero,
                        Action = TouchEvent.TouchEventAction.Down,
                        TimeStamp = DateTime.Now
                    },
                    new TouchEvent
                    {
                      Id = 1,
                      Position = Vector2.Zero,
                      Action = TouchEvent.TouchEventAction.Move,
                      TimeStamp = DateTime.Now.AddMilliseconds(10)
                    },
                    new TouchEvent
                    {
                        Id = 1,
                        Position = Vector2.Zero,
                        Action = TouchEvent.TouchEventAction.Up,
                        TimeStamp = DateTime.Now.AddMilliseconds(200.0)
                    }
                });
            var gestureProvider = _container.Instance;

            // Act
            var samples = gestureProvider.GetSamples();

            // Assert
            var gestureSamples = samples.ToList();
            Assert.AreEqual(1, gestureSamples.Count);

            var tap = gestureSamples[0];
            Assert.AreEqual(Vector2.Zero, tap.Delta);
            Assert.AreEqual(Vector2.Zero, tap.Delta2);
            Assert.AreEqual(Vector2.Zero, tap.Position);
        }

That’s the test that proves a tap gesture is detectable given how the touch events are provided. It was easy to setup a mock scenario for drag and pinch as well, and just assert the required gesture return values. The TouchEvent object maps pretty closely to the events that User32.dll provides, also, so there wasn’t that much to test for actually capturing events.

The major problems came when attempting to translate coordinates from touching an XNA game window into world coordinates. I use a surface pro for all development, and it is pretty much a necessity to have 150% scaling on at all times, because the size of the screen is small. Windows scales all windows up, but in doing so it breaks the coordinate system for touch input. This is not something you can see or solve with test driven development (at least not traditional unit tests), because it requires a live scaled window and graphics object to operate.

To solve the problem, one simply has to disable the auto scaling, and tell Windows that the application will handle understanding the DPI settings. You have to make your application DPI Aware. (More info). The window will then not auto-scale, and the coordinate system will not be broken, so normal translation routines will work.

Test driven development (TDD) should drive your class design (Part 2 of 2): The right way

Update (9/16/2013): Example source code is now attached, including source for parts 1 and 2.

In the previous post, I proposed a simple example:

You work for Company A, and they want you to write a simple web portal for them. You learn that there is a process in place already that spits out about 30 images as email attachments, and they would much rather see these displayed on a web site. The images will have to be categorized into main, sub, and chart type

In that post, I outlined how one might develop a library for such a site without TDD. Erase that code from your memory. DELETE.

In this post, I will be showing how test driven development can vastly improve the flexibility of the code you write, using the same example. I will be using a 3rd party tool called JustMock from Telerik. I highly recommend at least finding some mocking tool, as this will greatly enhance the TDD process. You can read a previous post for more on the subject of dependency injection, IoC, and mocking. It is through these principles and patterns that TDD becomes a really powerful method for class design. Let’s get started!

Sometimes staring at a blank test class can be rather daunting.

dont-panic-circle

Calm down!

DON’T PANIC!

LET GO!

*SLAP*

Feel better? Me too… I got to slap you!

In all seriousness, think of the test area as your white board. Your API isn’t written yet, but you get to use it already!

What is the most basic thing you need to do? Load charts from somewhere right? What holds things? A container? Perhaps… but, let’s call it a Repository. We know it needs to return an enumerable collection of Chart objects. We also know that something is going to depend on this “Repository” object to load its charts from. Let’s say we haven’t settled on the architecture yet (MVC, WebForms, HTML5 with a WebAPI service). We need to test that SOMETHING can load the charts using a repository just to get started, so let’s call it ChartLoader. I think we have enough to write our first unit test. I typically start by renaming the class and first test method, and inserting my standard AAA comments:

	[TestClass]
    public class when_loading_charts
    {
        [TestMethod]
        public void chart_loader_returns_charts_from_repository()
        {
            // Arrange
            // Act
            // Assert
        }
    }

TDD has a cadence to it. You will:

  1. Write a test.
  2. The you run all tests.
    1. New test fails because the requirement isn’t met yet
  3. Do the simplest thing to make all tests pass again
  4. Refactor
    1. Only after everything passes do you refactor. It may be tempting, but don’t do it earlier! Get a good baseline first.
  5. Repeat for the next requirement

The first test

	[TestMethod]
	public void chart_loader_returns_charts_from_repository()
	{
		// Arrange
		var container = new MockingContainer<ChartLoader>();
		container.Arrange<IChartRepository>(r => r.Charts).Returns(new List<Chart>
		{
			new Chart
			{
				FileName = "file1.png",
				Category = new ChartCategory
				{
					Main = "main",
					Sub = "sub",
					Type = "type"
				}
			}
		});

		// Act
		ChartLoader loader = container.Instance;
		IEnumerable<Chart> result = loader.GetCharts();

		// Assert
		Assert.AreEqual(1, result.Count());
		Assert.AreEqual("file1.png", result.First().FileName);
		Assert.AreEqual("main", result.First().Category.Main);
		Assert.AreEqual("sub", result.First().Category.Sub);
		Assert.AreEqual("type", result.First().Category.Type);
	}

So, we have a test for this fictitious ChartLoader object, and we know it needs to load from a repository, so ChartLoader needs some abstract definition of a repository (IChartRepository), and we know it needs to get charts… IChartRepository.Charts is going to be an IEnumerable, but it’s undefined so far. The Arrange call is essentially setting up a return value from the chart repository, so that when ChartLoader hits it, it will get the list as defined in the .Returns() call. We’ll see that in the next code section. So, the next thing to do is call the method in the ChartLoader to get the charts, and then assert that everything the ChartLoader returned, matched exactly what the repository returned (and you know exactly what it’s going to return, because you mocked it!)

So now there is a test with a bunch of red lines, because nothing is real yet. This is why I love tools like ReSharper, which let me auto generate the classes very quickly. What gets generated:

    public interface IChartRepository
    {
        IEnumerable<Chart> Charts { get; }
    }

    public class ChartLoader
    {
        public IEnumerable<Chart> GetCharts()
        {
            throw new NotImplementedException();
        }
    }

    public class ChartCategory
    {
        public string Type { get; set; }
        public string Main { get; set; }
        public string Sub { get; set; }
    }

    public class Chart
    {
        public ChartCategory Category { get; set; }
        public string FileName { get; set; }
    }

Now, the test will fail, but at least everything compiles. This is where you go to step 4 in the process. Do the simplest thing to make all the tests pass. In this case it’s 1 test, but get in the habit of running all tests… find the keyboard shortcut for doing this and learn it! Optionally, if you’re really into it, you can get a tool that automatically runs your tests like NCrunch

Here, there is a certain pattern that IoC containers work with: Your class constructor should take parameters for all of its dependencies. Here is the implementation of ChartLoader I came up with to get the tests to pass:

    public class ChartLoader
    {
        private readonly IChartRepository _chartRepository;

        public ChartLoader(IChartRepository chartRepository)
        {
            _chartRepository = chartRepository;
        }

        public IEnumerable<Chart> GetCharts()
        {
            return _chartRepository.Charts;
        }
    }

Now typically, you will do whatever refactoring that is necessary, which includes moving classes into appropriate folders and projects, etc, because all of the generated code I’ve shown is at the bottom of the test file. That would be out of the scope of this post, though, and sometimes it’s ok to leave them inline while you’re still writing tests.

This might actually be a good time to create a fake IChartRepository implementation on the front end site and actually give the business something to look at!

Hah! Did you think TDD was slow? How much quicker did we just deliver something to business and get feedback? This is VERY early on, and it is invaluable, because business requirements change when the people using a system get to play with it. You can and should show them something as soon as possible.

Back to TDD. The second test:

We have an object that can get charts, but we still don’t have anything loading from disk. Let’s write another test (because in TDD we always write a test before a feature):
I typically start a new test class when I’m dealing with a new system under test, but for this example, I won’t bother.

        [TestMethod]
        public void chart_repository_returns_charts_from_directory()
        {
            // Arrange
            var container = new MockingContainer<DirectoryChartRepository>();
            container.Arrange<IDirectory>(d => d.GetFiles()).Returns(new List<string>
            {
                "file1.png",
                "file2.png"
            });

            // Act
            DirectoryChartRepository repository = container.Instance;
            IEnumerable<Chart> result = repository.Charts;

            // Assert
            Assert.AreEqual(2, result.Count());
            Assert.AreEqual("file1.png", result.ElementAt(0).FileName);
            Assert.AreEqual("file2.png", result.ElementAt(1).FileName);
        }

Here I just want to prove that charts can load from a directory. Now, what really makes this a special scenario (and why I chose this example) is that there is an inherent dependency on the file system in the requirement: Charts will be loaded off the hard drive. This is about class design and controlling inputs in order to test things properly, though. I don’t want a hard drive to have to spin up in order to run a unit test. Tests should execute in milliseconds and be self contained units. The arrange section of a unit test HAS TO contain all of the dependency mocking, or it is going to be a brittle test that requires too much setup. Furthermore, System.IO.Directory.GetFiles(path) is static and can’t be overridden, so it’s impossible to mock (without a major effort or something that can fiddle with system libraries at runtime).

So, now let’s do some more class generation.

    public interface IDirectory
    {
        IEnumerable<string> GetFiles();
    }

    public class DirectoryChartRepository : IChartRepository
    {
        public IEnumerable<Chart> Charts { get; }
    }

So now everything compiles, but the new test still fails, because the DirectoryChartRepository object doesn’t actually use IDirectory and isn’t building any chart objects from the directory. So we have to do more to get it to pass:

    public class DirectoryChartRepository : IChartRepository
    {
        private readonly IDirectory _directory;

        public DirectoryChartRepository(IDirectory directory)
        {
            _directory = directory;
        }

        public IEnumerable<Chart> Charts
        {
            get
            {
                // TODO: I'm going to need a way to inject the path
                return _directory.GetFiles().Select(f => new Chart
                {
                    FileName = f
                });
            }
        }
    }

Now both tests should pass, and we can move on. How do charts get categories? NEW TEST! Seeing the pattern now? (Good, because I’m going to go faster):

Going faster. The third test:

	[TestMethod]
	public void chart_repository_categorizes_charts_from_categorizer()
	{
		// Arrange
		var container = new MockingContainer<DirectoryChartRepository>();
		container.Arrange<IDirectory>(d => d.GetFiles()).Returns(new List<string>
		{
			"file1.png"
		});

		container.Arrange<IChartCategorizer>(c => c.GetChartCategory(Arg.IsAny<Chart>()))
			.Returns(new ChartCategory
		{
			Main = "main",
			Sub = "sub",
			Type = "type"
		});

		// Act
		DirectoryChartRepository repository = container.Instance;
		IEnumerable<Chart> result = repository.Charts;

		// Assert
		Assert.AreEqual("main", result.ElementAt(0).Category.Main);
		Assert.AreEqual("sub", result.ElementAt(0).Category.Sub);
		Assert.AreEqual("type", result.ElementAt(0).Category.Type);
	}

    public interface IChartCategorizer
    {
        ChartCategory GetChartCategory(Chart chart);
    }

Tests compile but fail again. Add a dependency on IChartCategorizer to the DirectoryChartRepository, and use it. New DirectoryChartRepository class:

    public class DirectoryChartRepository : IChartRepository
    {
        private readonly IDirectory _directory;
        private readonly IChartCategorizer _chartCategorizer;

        public DirectoryChartRepository(IDirectory directory,
            IChartCategorizer chartCategorizer)
        {
            _directory = directory;
            _chartCategorizer = chartCategorizer;
        }

        public IEnumerable<Chart> Charts
        {
            get
            {
                return _directory.GetFiles().Select(f =>
                {
                    var chart = new Chart
                    {
                        FileName = f
                    };
                    chart.Category = _chartCategorizer.GetChartCategory(chart);
                    return chart;
                });
            }
        }
    }

Tests pass. Things are really starting to come together at this point. We still want to load categories from that XML file perhaps, but who’s going to maintain the XML file? What if they want to be able to manage categories themselves? Things are really starting to get going now, and with this class design, to implement any categorization requirements, it’s simply an implementation of IChartCategorizer. XMLChartCategorizer or SQLChartCategorizer? Well for now let’s do DictionaryChartCategorizer since we’re going to be the only ones maintaining the categories for now.

Fourth test:

	[TestMethod]
	public void dictionary_categorizer_loads_categories_from_dictionary()
	{
		// Arrange
		var container = new MockingContainer<DictionaryChartCategorizer>();
		container.Arrange<IChartCategoryDictionaryProvider>(d => d.GetCategories()).Returns(new Dictionary<string, ChartCategory>
		{
			{
				"file1.png",
				new ChartCategory
				{
					Main = "main",
					Sub = "sub",
					Type = "type"
				}
			}
		});

		// Act
		var result = container.Instance.GetChartCategory(new Chart
		{
			FileName = "file1.png"
		});

		// Assert
		Assert.AreEqual("main", result.Main);
		Assert.AreEqual("sub", result.Sub);
		Assert.AreEqual("type", result.Type);
	}

    public interface IChartCategoryDictionaryProvider
    {
        Dictionary<string, ChartCategory> GetCategories();
    }

    public class DictionaryChartCategorizer : IChartCategorizer
    {
        public ChartCategory GetChartCategory(Chart chart)
        {
            throw new NotImplementedException();
        }
    }

Partway through writing the Arrange section, I realize that I need a way to control the dictionary of categories, and this is a good time to make a new IChartCategoryDictionaryProvider object, because I can implement that interface for XML, SQL, a Code Only version, or this mock version! We have a compiling test that fails due to exceptions. Let’s fix that:

    public class DictionaryChartCategorizer : IChartCategorizer
    {
        private readonly IChartCategoryDictionaryProvider _chartCategoryDictionaryProvider;

        public DictionaryChartCategorizer(IChartCategoryDictionaryProvider chartCategoryDictionaryProvider)
        {
            _chartCategoryDictionaryProvider = chartCategoryDictionaryProvider;
        }

        public ChartCategory GetChartCategory(Chart chart)
        {
            var categories = _chartCategoryDictionaryProvider.GetCategories();
            ChartCategory category;
            if (categories.TryGetValue(chart.FileName, out category))
            {
                return category;
            }
            return null;
        }
    }

With that new implementation of DictionaryChartCategorizer, everything passes again. So, we have the basic structure of our API down that satisfies all of the requirements, but STILL no call to System.IO.Directory.GetFiles() and no loading charts from XML? What gives? We’ve reached the point where we need real implementations in order to call this done, but we could easily skip that and write the whole front end website. As noted, we could have done that a long time ago as soon as we had the ChartLoader object.

Now let’s finish this thing out

The test for this is basic, because there’s not much to arrange or assert:

	[TestMethod]
	public void filesystem_directory_does_not_throw_exception()
	{
		// Arrange
		var directory = new FileSystemDirectory("C:\");

		// Act
		var result = directory.GetFiles();

		// Assert
		// Pass if no exceptions
	}

    public class FileSystemDirectory : IDirectory
    {
        private readonly string _path;

        public FileSystemDirectory(string path)
        {
            _path = path;
        }

        public IEnumerable<string> GetFiles()
        {
            return System.IO.Directory.GetFiles(_path);
        }
    }

You’ll notice I didn’t mock anything, because this class doesn’t have a mockable dependency. It goes directly to a static method to get files off the disk, and that’s exactly what we didn’t want polluting our classes making testing them hard. It has to be done at some point, though, and at least we have a single class to handle it and it’s tucked away neatly in an abstraction. As a side note, this opens up the possibility of having a non filesystem directory (store image byte arrays in SQL?), which is a pretty neat concept, and no code would have to change.

Almost done!

	[TestMethod]
	public void xml_categorizer_loads_categories_from_xdoc()
	{
		// Arrange
		var document =
			new XDocument(new XElement("charts",
				new XElement("chart", new XAttribute("maincategory", "main"), new XAttribute("subcategory", "sub"),
					new XAttribute("type", "type"), new XAttribute("filename", "file1.png"))));

		var categorizer = new XmlChartCategorizer(document);

		// Act
		var category = categorizer.GetChartCategory(new Chart
		{
			FileName = "file1.png"
		});

		// Assert
		Assert.AreEqual("main", category.Main);
		Assert.AreEqual("sub", category.Sub);
		Assert.AreEqual("type", category.Type);
	}

    public class XmlChartCategorizer : IChartCategorizer
    {
        private readonly XDocument _document;

        public XmlChartCategorizer(XDocument document)
        {
            _document = document;
        }

        public ChartCategory GetChartCategory(Chart chart)
        {
            var chartElement = _document.Descendants("chart").FirstOrDefault(x => x.Attribute("filename").Value == chart.FileName);
            if (chartElement != null)
            {
                return new ChartCategory
                {
                    Main = chartElement.Attribute("maincategory").Value,
                    Sub = chartElement.Attribute("subcategory").Value,
                    Type = chartElement.Attribute("type").Value
                };
            }
            return null;
        }
    }

That’s it. We have everything we need, and the library has tests for all business functionality. The design is a LOT different, no? You might not see the value in such a design pattern at first, but consider how flexible it is to change. I’ve already outlined some changes that would be very simple, but more than just simple changes, even complex changes are not going to damage the integrity of the rest of the library. Changing the Categorizer implementation to a SQLChartCategorizer does not have anything to do with where the files come from on disk. Files could come from cloud blob storage, and categories could come from an infinite monkey array… the rest of the code won’t care.

If you implement the infinite monkey array, though, I want to see the code.


(Contains 1 attachments.)

Bust that cache!

<shamelessplug>I don’t know if I have shared this, but I work for Caesars Entertainment doing web development. Go make a hotel reservation and you’ll be using everything that my work has been for the last 2 years.</shamelessplug>

Anyway, as web development goes, the site is very javascript heavy, and we have some core functionality existing in a javascript file linked via a

<script type="text/javascript" language="javascript" src="blahblah.js"></script>

tag. Regardless of the specifics, certain browsers end up being a pain when it comes to caching. For instance, we make a change to blahblah.js, but the browser has an old version of it cached for speedy loading and it ends up not picking up the change. It takes some time for the browser to get around to updating, and every browser is different, so I did some searching.

I found a site (http://davidwalsh.name/prevent-cache) which basically says to do this:

<script type="text/javascript" language="javascript" src="blahblah.js?(insert some dynamic number based on current time here)"></script>

This effectively makes the browser see a new reference for the javascript file every time it loads the page… not completely ideal for all situations. Namely, browser cache can be a very helpful thing to improve site performance… we like browser cache, but we need to make it work for us and not against us.

The simple solution is:

<script type="text/javascript" language="javascript" src="blahblah.js?(insert static version # here)"></script>

The version # is actually loaded via a web.config file, but that’s neither here nor there… what is important is that every time we want the browser to reload the cached js file, we simply increment that version # and the browser doesn’t have that src reference cached, so it has to download it.

Cache busted!