I want to write a unit test!

Habit of writing unit tests

Especially until you can build and deploy the app you are developing and see it on the screen It takes 5 minutes and 10 minutes to spare, but in that case, The operation of writing business logic but testing it only manually produces only pain.

In this article, I know it's better to write it somehow, but honestly I don't know how to write it with the current structure, To the developers who wouldn't feel like spending that much because they don't know what they're happy about. It provides a design method for effectively writing unit tests with xUnit.

Basically java8 and JUnit4, but the content is not so language-specific.

Major premise

Unit tests are not "testing"

Unit tests can only check the behavior of a function by assertions.

Manual testing is the most efficient way to find unknown defects.

Unit testing is a tool to help development

Rather than improving the quality, it is possible to automatically perform the minimum checks so that the development speed is slowed down by boring and unexpected rework does not occur later.

Obviously, this article is aimed at people who don't usually write unit tests, so just in case.

Benefits of writing unit tests

Rework is on the front

As in the above example, it takes 5 minutes and 10 minutes to be able to check from the screen, but in that case, For complex business logic, it is more efficient to generate rework before testing from the screen.

Unit tests generally allow you to see the results faster than you can start the server and see it on the screen. Also, as the name of the unit (unit) test suggests, it is possible to verify at the level of each function before connecting all the functions. Another reason is that the implementation => validation time lag is smaller.

In other words

--Since verification can be performed at a stage before the state where it can be connected to the screen, rework can be found before the implementation procedure. --The verification itself takes less time than tapping on the screen after starting the server.

In these two points, rework can be detected quickly.

Specifications can be described

Often mentioned as a merit of TDD, Unit tests are usually written in the form of assertions (such as the return value of function XX should be ZZ when the argument is YY). This allows subsequent developers to ** successfully write ** by reading the test code for unit tests. You will be able to know most clearly that "this function has such specifications".

(Writing well is very important, It's rather confusing if it doesn't have both completeness as a test case and readability as a code.)

Not only can developers understand it, of course. If a developer who doesn't understand the function makes a bad fix, As long as the CI turns properly It is possible to automatically detect that "the behavior has changed from the specifications that should be" in the unit test.

Proper maintenance of unit tests is This leads to the ability to automatically detect defects that would otherwise be noticed only at the integration test stage.

Easy to understand the usability of public functions

This is also often cited as an advantage of TDD, The test code will be the first user of the (planned) public function you implement.

Especially in Java, I personally think that signature design determines various factors such as extensibility and risk of modification. By actually calling the function from the test code, it is often possible to regain the usability of the function.

If you notice this after it is called by various classes, the risk of modification and the range of influence will be enormous.

Can test kinsoku

When performing a combination test, there is a prohibition. The point is that values that cannot actually be entered from the screen are excluded from the test case as a prohibition. In this case, the judgment that "you cannot enter from the screen" is unreliable.

For example, even if the front end is validated, It doesn't make sense if the value is rewritten by a malicious user at the moment of sending.

Unit tests do not have the restriction that they can be entered or not entered from the screen. Even for input patterns that are prohibited cases It is possible to test.

It would be nice to be able to write a test that expects an exception to be thrown.

Why can't I write unit tests?

Well, I gave various merits, As you read this article, you haven't written a unit test as mentioned above, I am fighting daily with large-scale application development work.

Why I haven't written it, why my seniors around me Considering that no one wrote as far as the eye can see Maybe actually In the first place, it may be because the structure is such that it is difficult to write unit tests on the product code side.

In this section, "Why can't I write unit tests?" I will explain the features when the structure makes it difficult to write unit tests.

Here, as a common function in your workplace, Imagine something named "complete" that controls the completion process of something.

public Map<String,Object> complete(BigBean bigBean) {
// ...
SomeKindOfConfig config = dao.loadConfig(someId);
this.computeSomeValue(bigBean);
if(config.getSomeAttribute() == 1) {
// ...
} else {
// ...
}
// ...
for(DtlBean dtl : bigBean.getManyDtls()) {
// ...
}
if(bigBean.getOtherAttribute() == 0) {
// ...
}
dao.updateSomething(bigBean);
return res;
}

The function is too long and the expected return value is unclear

When doing something completion I think that it is common to make a functional design that is convenient because various processes can be performed with the touch of a button. It is a pattern in which they are all written in one function.

Such processing is often done I have to try this setting later and branch the process! Because it can be a difficult situation like DB access is also chaos, such as entering ad hoc on the way.

For business logic that feels like this legacy, Unit tests are very difficult to write.

There are various problems other than being too long, If you often do it with just one function, It is difficult to write in the form of an assertion that "the output should be like this for the input" It is a big feature. Since there are many branches and the combination explodes, the amount of test class description becomes huge, which is inefficient.

I have changed the argument

private void computeSomeValue(BigBean bigBean) {
//...
bigBean.setSomeValue(someValue);
//...
}

If there is a void method whose argument content can change On the other hand Since it becomes impossible to clearly write the assertion that "when there is such an input, it should be as an output". I think you should avoid it as much as possible.

If you really want to meet the above requirements, The signature itself,

private SomeValueClass computeSomeValue(BigBean bigBean) {
//...
return someValue;
}

It is better to write like this.

Depends on something other than arguments

Depending on the unit test operation, If something like a test environment isn't ready for a quick refresh For example, it is not easy to test a DB value-dependent one with a unit test.

In the case of the above process I am going to get the setting-like value from the DB on the way, This will result in a complete method assertion for the same argument Depending on the state of the DB, it will succeed or fail.

In the first place, for subsequent developers I think it's a bad implementation to go to see the settings suddenly on the way.

In this case, mock Dao with Mockito on the unit test side, etc. There is a way to deal with it.

Easy-to-write unit test design

Appropriate division

A function of complexity that does not explode the combination of branches, If it is implemented separately in a loosely coupled state, You can implement and run unit tests very effectively.

For example, let's say you have the ability to pay rent for multiple months one day. We will implement a preview function that allows you to see how much rent you will pay for the month.

As an argument,

--The date when to pay. --An array in which the target month is in the state of YYYYMM.

There will be.

As an output,

--The date is displayed in YYYY / MM / DD. --The target month is displayed in the format of MM month. --When there are multiple target months, MM month --MM month is displayed from the first month to the last month. --Each target month is displayed as "before" if it is a year before the payment date, and "next" if it is the next year.

Suppose that the specification is.

If you try to connect everything from the screen and hit it normally,

--When there are multiple? When singular? --When it's a double-digit month? When it's a single digit month? ――When before? Next time?

There are various things, and it tends to increase as a combination.

However, since these are a set of simple specifications if divided, If you write a unit test of "a function that just formats a singular month" You can see that you can trust the moon format to some extent, If you write a unit of "function to sort multiple months", There is no need to relentlessly test the order of "functions that output by connecting the beginning to the end of multiple months with hyphens". And, "Compare the date and the year and month, and if the year and month are earlier," before ", If there is a unit test of "a function that returns" next "if the year and month are later", The unit test of the final preview display function You only need to test the representative value that they are properly combined.

If you implement each as a set with a unit test that is small and guarantees a return value for the argument, "At least it's working properly so far, isn't it?" There is also the advantage that you can develop with peace of mind because you can proceed while checking.

Referential transparency

An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior. As a result, evaluating a referentially transparent function gives the same value for same arguments.

https://en.wikipedia.org/wiki/Referential_transparency

"A function is referentially transparent, which means that replacing that function with its return value does not change the behavior of the program. As a result, when you execute a function that is referentially transparent, ** always returns the same return value for the same argument **. "

Write the business logic you care about in referential transparency. Personally, I think it's nice to have a clear separation between a class that has a state and a function that returns an output for an input.

Not only is it easy to write unit tests, but it's also easy to write unit tests if it always returns the same return value for the same argument. It makes the unit test effective in that it "reproduces the movement guaranteed by the unit test in any state".

So how much should I write?

Basically, it seems that you should prepare at least one for each public method

I think there are some rules here.

Effective writing

Do test design quickly and properly

When writing a unit test as a specification description, However, it may be counterproductive to make an assertion by passing the value that came up to the dark cloud.

First of all, the minimum

It is effective to grab some of the methods that are commonly known as test methods.

Rather, in the case of a function that can not be put into the case with these two Since the test itself in the unit test may be unsuitable, You may want to consider validation with other methods.

Allows you to combine and execute designed cases

@Runwith(Theories.class)

Or

@Runwith(Parameterized.class)

You can use to test by passing multiple "sets of arguments and expected values" to one test code. In short, you can make the inputs and outputs comprehensive and easy to write.

Prepare a test suite and rotate it many times

If the number of test classes increases Create a Suite class that bundles it for each package, Let's be able to re-run all the test classes under it at once at any time.

If there is any change If you turn it all over immediately, As long as it is asserted in the unit test, it will work with the specifications so far, You can see immediately.

For projects that operate CI tools such as Jenkins and travis I think it's a good idea to turn it around again when the changes are pushed to the codebase.

Summary

Why write unit tests?

--Rework is on the front --Specifications can be described --Easy to understand the usability of public functions --Can test kinsoku

Why can't I still write unit tests?

--The function is too long and the expected return value is unclear --The argument has been changed --Depends on something other than arguments

How do you make unit tests easier to write?

--Appropriate division --Referential transparency

That was all.

Recommended Posts

I want to write a unit test!
I want to write a nice build.gradle
I want to simply write a repeating string
How to write a unit test for Spring Boot 2
[SpringBoot] How to write a controller test
I want to test Action Cable with RSpec test
[Ruby] I want to do a method jump!
I want to design a structured exception handling
I want to write quickly from java to sqlite
I want to call a method of another class
I want to convert characters ...
I want to use a little icon in Rails
Introduction to Micronaut 2 ~ Unit test ~
I want to monitor a specific file with WatchService
I want to define a function in Rails Console
To write a user-oriented program (1)
I want to add a reference type column later
I want to click a GoogleMap pin in RSpec
I want to connect to Heroku MySQL from a client
I want to create a generic annotation for a type
I want to add a delete function to the comment function
How to dynamically write iterative test cases using test / unit (Test :: Unit)
[Java] I want to convert a byte array to a hexadecimal number
I want to randomly generate information when writing test code
I want to find a relative path in a situation using Path
I want to implement a product information editing function ~ part1 ~
I want to make a specific model of ActiveRecord ReadOnly
I want to make a list with kotlin and java!
I want to call a method and count the number
I want to make a function with kotlin and java!
I want to create a form to select the [Rails] category
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I want to give a class name to the select attribute
I want to create a Parquet file even in Ruby
I want to use FireBase to display a timeline like Twitter
[Java] I want to test standard input & standard output with JUnit
[Personal development] I made a site to recruit test users!
Swift: I want to chain arrays
How to unit test Spring AOP
I want to use FormObject well
I want to convert InputStream to String
How to write a ternary operator
I want to docker-compose up Next.js!
[RSpec] How to write test code
I want to write a loop that references an index with Java 8's Stream API
I want to recursively search for files under a specific directory
I want to create a chat screen for the Swift chat app!
I want to be able to think and write regular expressions myself. ..
I want to add a browsing function with ruby on rails
I want to use swipeback on a screen that uses XLPagerTabStrip
I just want to write Java using Eclipse on my Mac
Only this I want to remember, 4 designs for writing unit tests
I tried to write code like a type declaration in Ruby
Sample code to unit test a Spring Boot controller with MockMvc
I got stuck trying to write a where in clause in ActiveRecord
I tested how to use Ruby's test / unit and rock-paper-scissors code.
I want to extract between character strings with a regular expression
A flowing interface? -I want to give you an opportunity to write good code. 3 [C # refactoring sample]
I want to eliminate duplicate error messages
Introduce RSpec and write unit test code
I want to make an ios.android app