Testing with Lapsang

! Warning: this post hasn't been updated in over three years and so may contain out of date information.

Lapsang logoThis post is the second in a series of articles on the Lapsang language. This time round, I want to explore its Test-driven development/Behaviour-driven-development (TDD/BDD) features. As previously mentioned, Lapsang takes many ideas that are considered programming “best practice” and makes them features of the language. As you might therefore imagine, testing in Lapsang isn’t relegated to a mere framework. In fact, it’s not even just built into the language syntax. Lapsang takes testing as seriously as it can be taken, by treating the presence of untested code as a compilation error. So using TDD when developing Lapsang code isn’t just a good idea, it’s mandatory if you want the code to compile!

Now more astute readers hopefully are thinking “hang on” at this point. Lapsang is a hypothetical language and so obviously there is no compiler. So why am I making claims about what its non-existent compiler can do? Throughout this post, I will talk about how I imagine the compiler would work as if it really existed and really could do the things I describe. The language I’d have to use would be immensely cumbersome if I didn’t do it this way, so hopefully you’ll forgive me this poetic license.

To begin, take another look at the fileProperty interface, and its accompanying default class, from the previous post.

The following code then forms the (simplified) set of tests for the class in the above code.

Any set of tests must be defined inside a test package. This notifies the compiler that this package is allowed to do its own type mapping, and that it is allowed to reference the class under test directly. To support this, the next set of statements must be of the form tests <item under test>. These statements do more than this though. As previously mentioned, for the FileProperties class to compile, it must be tested. The “tests” statements help the compiler relate tests against code to determine if the code is properly tested.

After this section comes the usual list of uses statements, followed by the mapping information. The important line here is map lapsang.io.file to FileMock. This overrides the default mapping of the interface and instead maps it to a locally defined mock. That way, we can test FileProperties in a proper unit test-fashion, without having to worry about side-effect and permission issues related to accessing the real file system.

if you are familiar with BDD, or the Jasmine JavaScript testing framework, then the section covering the actual tests will be familiar to you. “describe” declares a set of tests and is followed by a string describing those tests. Within this example’s describe block, there is code that is executed for each test, followed by two tests. The code executed before each test starts with the keyword “mutable“. By default, all objects in Lapsang are immutable, ie their state cannot change once initialised. In addition, they are not allowed to directly cause side-effects. Objects that need to hold state that can change over time, or for example that directly access some sort of non-deterministic IO source, must therefore be marked as mutable. This tells the compiler that they are exempt from the normal rules on immutability and are free to change/cause change as required. So a mock file object is created before each test, leading on to the tests themselves.

A test starts with “it” in this case. There is more to BDD than covered here, describe/it being a simple form. Following it is string that describes the particular test. Then the block of code following forms the test. The keyword “expect” is, in some ways, the equivalent to “Assert” in other – JUnit-style – test frameworks. Again those familiar with Jasmine will know there is an important difference: the test doesn’t stop when an expect fails. This neatly solves the problem of multiple asserts versus multiple tests, in that tests can have multiple expect statements and get details of all the ones that fail, not just the first one. If you trace through the execution paths used by the two tests, then – I hope! – you’ll see that all paths through FileProperties are covered by these two tests. Thus the compiler would deem the code worthy of compilation.

The last section of this example relates to mocking interfaces. I’ll come back to this topic in detail in a later post. For now though, I’ll sum it up by saying the aim here is that the compiler automatically generates a class that meets the requirements of the mocked interface, plus in this case adds an extra – mutable – property: ValidPath, along with a manually defined version of the Exists() method.

So that’s it for the whistle-stop overview of unit testing in Lapsang. Next time, I’ll take a look at composition.