KINTO Tech Blog
Test

Boost Quality with Clear Unit Test Method Names

Cover Image for Boost Quality with Clear Unit Test Method Names

Introduction

Hello. Hi there. I'm Uehara (@penpen_77777), a backend engineer in the KINTO FACTORY Development Group. I usually use Go and Rust for development, and my preferred editors are Vim and NeoVim.

This time, I'll talk about the improvement of test code quality, particularly focusing on how to name test methods.

I referred to the following book for this topic.
https://book.mynavi.jp/ec/products/detail/id=134252

Challenges related to test code

If you write test code on a daily basis, have you ever had the following experiences?

  • You have test code, but you don’t fully understand its purpose, which creates doubts about the reliability of the test results and reduces confidence in the adequacy of the testing.
  • In reviewing test code someone else wrote, you have vaguely approved it even though you don't fully understand it.
  • You have tweaked test code to pass the testing phase on a whim, concerning that you are not able to submit a pull request (PR) if the continuous integration (CI) failed as you don't really know what kind of test you're doing.

To solve these problems, why not start by improving your test method names? That's what I would like to propose.

Why Do We Need to Improve Test Method Names?

Let's look at specific examples of why test method names need to be improved to ensure test code quality.

Code Examples E-commerce site system

In the e-commerce site system written in Go, we have implemented the following method of purchasing products. If the product is in stock, the order can be placed; otherwise, it returns an error and the order fails.

// ECサイトシステム構造体
type ECSystem struct {
    // 商品ごとの在庫数
    stock map[string]int
}

// 商品を購入するメソッド
func (c *ECSystem) Order(itemID string) error {
    // 商品がない場合はエラーを返す
    if c.stock[itemID] <= 0 {
        return ErrOutOfStock
    }

    // 商品があれば在庫数を減らす
    c.stock[itemID]--

    // 正常終了
    return nil
}

Test Method Name Before Improvement

Suppose that you created the following test code to conduct unit testing for this production code:

func Test_正常系_在庫数が0より大きい場合(t *testing.T) { }
func Test_異常系_在庫切れエラー(t *testing.T) { }

When you look at these names, the following questions come to mind:

🤔 "If the inventory quantity is greater than 0, what is the correct result?"
🤔 "Under what conditions does the out-of-stock error occur?"

I personally feel that I see this pattern of test method names surprisingly often. I think this is what happens when we get so caught up in how to test and what to write inside the test method that there's no time left to think about the method name itself.

In this case, the sample code and the method implementation are simple, so you can easily guess what the test is about from its name. Meanwhile, the actual product code contains a large number of lines of code for a test method involves a large number of the methods, making us difficult to understand what kind of test it is.

Improvement of the First Stage

One reason why the name of the test method before improvement is difficult to understand is that the name does not contain either the preconditions or expected results.

func Test_正常系_在庫数が0より大きい場合(t *testing.T) { } // 「想定結果」の欠落
func Test_異常系_在庫切れエラー(t *testing.T) { }         // 「事前条件」の欠落

To put this the other way around, it is easier to understand if you put both preconditions and expected results in the test method name. Let's unify it into the following format:{ Method under test }_{ Preconditions }_{ Expected results }

func Test_Order_在庫数が0より大きい場合_正常終了(t *testing.T) { }
func Test_Order_在庫数が0の場合_在庫切れエラー(t *testing.T) { }

It's a little better, but there's still room for improvement.

Problem: Method Name Includes How the Method Is Tested

The current test method names focus on how the tests are executed, which makes them harder to read and interpret.
🤔 "The name includes the content of a successful end of the order process... Does that mean the user can buy the product?"
🤔 "The name describes that the order returns an out-of-stock error... Does that mean the user can't buy the product?"

In other words, when you read it, your brain has to translate it into the "what"(or what kind of behavior is tested), which adds cognitive load and makes it hard to read.

Regarding this naming convention, the book "Unit Testing: Principles, Practices, and Patterns" states the following.

I've tried various naming conventions over the past decade, and among them, the most famous and probably the most useless one is: { Method under test }{ Preconditions }{ Expected results }

The argument is that we should think of an appropriate name for each test rather than following a strict naming convention, but personally, I feel that calling it "the most useless" is a bit of an exaggeration. I believe it's very useful if you consider the benefit of standardizing naming convention among team members, by operating with the mindset of following rules if you are unsure how to name a method.

Final Improvement: Write the "What"

In your test method names, write the "what" (the method behavior), not the "how" (how you test the method). The "how" part (to check a successful end and error occurrence) is implemented in a test method.

func Test_商品の在庫がある場合_ユーザは商品を購入できる(t *testing.T) { 
   // ここで正常終了しているかをみる
}

func Test_商品の在庫がない場合_ユーザは商品を購入できない(t *testing.T) {
    // ここで在庫エラーが発生しているかをみる
}

Furthermore, "Unit Testing: Principles, Practices, and Patterns" states the importance of naming methods concisely to help even non-engineers (domain experts) understand them.

Why Should We Use Names That Non-Engineers Can Understand?

"Since test code is only seen by developers, why don't we have to make it a name that non-engineers can understand?" You might hear such opinion.

"Unit Testing: Principles, Practices, and Patterns" addresses this opinion as follows:

This is because cryptic names impose a cognitive burden on everyone, regardless of whether they are a developer or not. These names place an extra cognitive load on developers to figure out what the test case actually verifies and how its content relates to which business requirement.

Even if you write code that only developers can understand, you'll end up where:

  • It becomes difficult to grasp what the test is verifying and which requirements it corresponds to.
  • Code maintenance becomes difficult.
    • Even the person who wrote it won't be able to read it a few months later.
  • It's hard for other engineers to understand during code reviews.

This results in the following challenges related to test code I mentioned at the beginning:

  • You have test code, but you don’t fully understand its purpose, which creates doubts about the reliability of the test results and reduces confidence in the adequacy of the testing.
  • In reviewing test code someone else wrote, you have vaguely approved it even though you don't fully understand it.
  • You have tweaked test code to pass the testing phase on a whim, concerning that you are not able to submit a pull request (PR) if the continuous integration (CI) failed as you don't really know what kind of test you're doing.

That's how we get there.

Naming Test Methods Clearly Is Hard

To be honest, coming up with clear, easy-to-understand test method names is really hard.

Why is that? That's because it requires you to take your hands off the keyboard, look away from the monitor, and ask yourself as follows:

"What are we actually trying to create?"

When you feel like you're drowning in a sea of code, just stop for a moment and consider what problems the method right in front of you is designed to solve and for whom.

  • What role does this object play in the system?
  • Is the task you are currently working on really going to make your users happy?
  • Do we really have a good grasp of the domain knowledge used in this business area?

Thinking seriously about test method names is actually about all of this.

And then you'll notice that something magical starts to happen when you explore clearer test method names.

The test code becomes easier to read. The production code naturally gets more organized. Conversations in the team are becoming smoother.

It's just a test method name, but it's so much more.

Small improvements lead to happiness for the entire development team. That's the kind of experience I really want you to have.

Conclusion

This time, I wrote about the test method name.

  • Make the test method name easy to understand to help grasp what kind of test is conducted.
  • Provide the names so clearly that even non-engineers can understand them.
  • If simply improving test method names can enhance the quality of your test code, don't you think it's a cost-effective improvement?

I hope this article has given you a sense of the importance of test method names.

The book "Unit Testing: Principles, Practices, and Patterns," which I referred to this time, has about 400 pages; meanwhile, the explanation about the test method names is about 6 pages, which is not long at all. However, personally, I feel this is the most important and instructive part of the book.

If you're struggling with unit testing, I highly recommend reading " "Unit Testing: Principles, Practices, and Patterns." I'm sure it will give you the answer to your problems!

References

https://book.mynavi.jp/ec/products/detail/id=134252

Facebook

関連記事 | Related Posts

We are hiring!

【QAエンジニア(リーダークラス)】QAG/東京・大阪・福岡

QAグループについて QAグループでは、自社サービスである『KINTO』サービスサイトをはじめ、提供する各種サービスにおいて、リリース前の品質保証、およびサービス品質の向上に向けたQA業務を行なっております。QAグループはまだ成⾧途中の組織ですが、テスト管理ツールの導入や自動化の一部導入など、QAプロセスの最適化に向けて、積極的な取り組みを行っています。

【バックエンドエンジニア】my route開発G/東京

my routeについてmy routeの概要 my routeは、移動需要を創出するために「魅力ある地域情報の発信」、「最適な移動手段の提案」、「交通機関や施設利用のスムーズな予約・決済」をワンストップで提供する、スマートフォン向けマルチモーダルモビリティサービスです。