Crafting automated verification of coding standards
In a world where automation is increasingly ubiquitous and inexpensive to produce, we still are maintaining a lot of information about team coding standards in boring documents.
Every programming language now comes with a Lint-like tool which checks the language best practices. However, each team has its own coding practices and standards, and these usually end up in a document and not in an executable specification.
At Mercap we use Smalltalk (more than one dialect) and one of the advantages of having an open environment with very interesting metaprogramming capabilities is that it allows you to easily build as code many of these practices and rules defined by the team. These checks are then automatically run before closing a unit of work, instead of being dead text in a document.
This is the first in a series of posts where the idea is to share the team standards and practices we automated and how we did it. We will see that it is not rocket science, when the tools are adequate and allow the programmer to take control of the development environment and make it their own.
Our first case is a simple one: We have a naming scheme for packages containing the tests of another package: Given a package named
P then the corresponding test package must be named
This convention allows us to infer programmatically given a package, the name of the package containing the test cases, and the other way around.
The check that we are going to implement then is: For all the packages containing tests authored by the Mercap team check the existence of its package under test.
In VA Smalltalk the component representing a package is called an
Applicationan it's a first class object, we can send it messages and implement new behavior.
Getting the packages of our authorship
Hands on! We must first be able to discern from all the packages available in the image which ones are authored by the Mercap team, because it makes no sense to run this check on third party packages. We can get all the available packages in the image with:
Now we need a way to filter the packages authored by the team. We can implement it in several ways, let’s start with the easier one:
Any time we create a new package in VA Smalltalk it creates automatically for us a subclass of Application named like the package and representing it. For example if we create a package “Math”, the system creates a subclass of Application called Math.
So we will add a new method to
Application telling us if the package is authored by Mercap or not:
Application class >> #isOwnedByMercap
Now we just need to re-implement it in the classes representing the packages authored by us. For example, if we created a “Math” package:
Math class >> #isOwnedByMercap
Great! Now we can easily filter the packages:
System loadedApplications select: [:package | package isOwnedByMercap ]
This is just one possible implementation, we can do something similar using the ENVY/Developer functionality that allows us to attach metadata to each software component under version control. Maybe a future post will go this route.
The methods added are versioned alongside the code of each package. A disadvantage of this implementation is that any time a new package is created we need to remember to implement the #isOwnedByMercap method. This can be improved in several ways, for example we can change the IDE so the “Create Package” action automatically compiles the method (because if anyone at Mercap creates a new package we know that it must be marked as authored by Mercap).
Usually in Smalltalk the IDE is implemented in the same language and can be changed without effort, so it’s possible to tailor it to our convenience.
Implementing the check
Now we have all the pieces we need to implement our check. Just create a new TestCase and add the following method:
| allPackages allPackageNames testPackages failedPackages |
allPackages := System loadedApplications select: [:package | package isOwnedByMercap].
testPackages := allPackages select: [:package | package name endsWith: #Tests].
allPackageNames := allPackages collect: [:package | package name].
failedPackages := testPackages reject: [:testPackage |
| expectedPackageName |
expectedPackageName := testPackage name withoutLast: #Tests size.
allPackageNames includes: expectedPackageName].
self assert: failedPackages isEmpty
Voilà! Our first automated check is implemented and ready to be run alongside the rest of our test suite.
Thanks for reading and see you in the next post.