Unit test naming conventions

Posted on by Matthias Noback

Recently I received a question; if I could explain these four lines:

/**
 * @test
 */
public function it_works_with_a_standard_use_case_for_command_objects(): void

The author of the email had some great points.

  1. For each, my test I should write +3 new line of code instead write, public function testItWorksWithAStandardUseCaseForCommandObjects(): void
  2. PSR-12 say that "Method names MUST be declared in camelCase". [The source of this is actually PSR-1].
  3. In PHPUnit documentation author say "The tests are public methods that are named test*" and left example below
  4. PHPStorm IDE from the last version gives for you ability to generate a TestCode and they do not use an underscore too:

    PHPUnit generating test code

  5. I opened a popular frameworks (Symfony, Laravel, YII2, CodeIgniter4) test folders in Github and then I opened a popular PHP library and also do not find underscore usage in test and @test annotation.

This made me realize that, yes, what I'm doing here is a convention, and maybe it's not the most popular one. Well, I've seen tests that use an even less conventional approach to method naming by using some kind of character that isn't a space but still looks like one:

/**
 * @test
 */
public function it works with a standard use case for command objects(): void

This is too much for me, but who knows, in a couple of years maybe...

I'll first respond to the objections now:

  1. I don't think it's bad to write 3 new lines for every test method. It's not like it's actual code that gets duplicated, it's just an annotation. The code never has to be modified, so it will never suffer from Shotgun Surgery (unless you switch test frameworks maybe). Anyway, I have a live template configured in PHP so I only have to type it and it will complete this with /** @test */ public function etc. We'll talk about it later.
  2. Philosophically speaking, I don't think a test method is a regular object method. Yes, of course, it is technically a method. PHPUnit instantiates your TestCase and will call its methods. But that is just the way PHPUnit does it. You might as well write something like this:

    it('works with a standard use case for command objects');
    

    In fact, there are test frameworks that let you write tests like this.

    You will never find any normal code (a.k.a. production code) call these test methods. I do think that would look rather weird.

    Instead of regular methods, I think we should consider test methods to be documentation or more specific: specification. This makes other aspects than code style conventions more important: it needs to be readable and precise. And in my experience testItWorksWithAStandardUseCaseForCommandObjects is really hard to read. Then again, maybe I just have to practice.

    But what if your code style fixer automatically turns "snake_case" method names into camelCase ones? Just configure the tool to ignore your tests.

  3. The way things are done in framework documentation can, but doesn't have to be considered the standard. The writer of the documentation has different goals than you have. Maybe they want to show the easiest way to get started with their framework. Maybe they just show the way that most users do it. Maybe nobody knows what most users do, and they just pick a way. What I do know is that the PHPUnit documentation also mentions this other way: "Alternatively, you can use the @test annotation in a method’s docblock to mark it as a test method."
  4. Although I love PhpStorm, I'm totally not inclined to do as they do. As an example, they have invented the convention that we have to annotate all the exceptions that a method throws. They also have an inspection that warns you about calling a method that could throw an exception. I bet that a lot of developers have since been wrapping things in try/catch blocks and coming up with inventive ways of dealing with exceptions. Not because we have to, but because the IDE developers thought it would be a good idea. End of rant. Conclusion: don't let the IDE determine how you write your code (unless you have thought about it and agree with it of course). When it comes to generating a test class and "generating test methods": totally ignore what PhpStorm has to offer (see also a previous article). This is not the way you should write a unit test (that is, every test method tests one method, and you don't even specify what's so special about what you're doing).
  5. The fact that popular frameworks and libraries use a certain convention is not a reason to use the same. For all I know, they may be stuck in the same perspective: do as others do, use a convention that's easy to follow, etc. Also, again, you have different goals than framework authors and I'd definitely recommend choosing your own convention that works for you and your team. If that is testCamelCase() after all, no problem.

However, it_might_be_nice_to_explain_the_convention_I_use().

In my projects, most unit tests will test a specific type of object. Some tests also indirectly use other types of objects, but the focus is mostly on one type or class. So not every test will test one class. Also, not every class will have a unit test, because not every class can be tested in isolation.

"But my project has a coding standard that forces every class to have a corresponding unit test!"

Just drop that standard. As I mentioned, not every class can be unit-tested, so you could never live up to this standard. Also, not every class deserves to have a unit test. It may be too simple or it may only be used as part of a larger aggregation of objects.

Now a unit test tests the behavior of an object, but if you write your test first, it will serve more like a specification of this behavior. You describe what the object should be capable of, the test fails, you then add that capability, and then the test passes. First it's a specification, then it becomes a test that proves that the object follows your specification. That's why I think it's a good idea to use the test names to specify the object behavior by talking about it. Starting test method names with it_ can be very helpful to get you in this "specification" mindset. It also automatically increases the distance between the test and the code of the subject under test. As you may know, it's an important quality of a unit test to keep a healthy distance from the object's implementation.

Because writing specifications is a play of language, I don't think we should force ourselves to use it_ all the time. Let's not make it another convention we have to work around. Just read the test names and verify that they form correct English sentences. Also check that domain experts without any programming experience could understand the tests and relate them to their own ideas about the business domain. I think this last approach works really well with entities, which in my experience are the objects that are best suited for unit testing.

Another suggestion: run PHPUnit with the --testdox as a "test" for your method names. It produces output like:

ProductionOrder

  • It can be created based on a product ID and a quantity to be produced.
  • It can not be cancelled if ingredients have been reserved for it.
  • You can report progress on it if ingredients have been reserved for it.
  • It can not be closed if it has not been fully produced yet.
  • ...

Some remarks:

  • Most of these sentences start with "it", which naturally refers to the unit we're testing (the ProductionOrder).
  • I don't always start with "it" though. In this case I liked "You can" better than "It is possible to".
  • Instead of saying "It throws an exception", look for alternatives like "It's impossible", "You can't", "It fails to", "It does not accept", and so on.

In the end, I don't often use --testdox, but still read the test names to find out if they produce a nice list of specifications.

P.S. Even though this article is about naming conventions for unit test methods it turned out to be advice to drop any tool that forces you to do things that are not justifiably good, but are just "the rules".

PHP testing
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Chris Dell
@throws is an important feature of php doc comments (and most other languages). It lets consumers/implementers know that they should handle the exceptions listed. It should be used whenever you are explicitly throwing an exception in a public function. 
See also C# XML Doc comments 
Davide Borsatto
What is your opinion of tools like PHPSpec? By their nature they force you to have one unit test correspond to one class. I feel like it has pros (test classes are usually more focused) and cons (you must shift to other tools like Behat the second you need to work with even just 2 classes).