Junit | can't write unit tests, just like a bear child without autumn pants running around in winter

In my past Android development career, I have hardly used unit testing, nor have I seen anyone introduce it. It seems that this thing is not very important in the eyes of domestic developers, or most development students do not have special time to use unit testing framework. Perhaps the more important reason should be my personal ignorance.

background

What is unit testing?

Unit testing is to write test code for the smallest unit. In Java, the smallest functional unit is the method. Therefore, unit testing of Java programs is testing for a single Java method.

Why unit testing

In foreign countries, the actual development process is often to write tests first, and then start writing implementation code. In the specific implementation process, write and test at the same time. When all the tests pass, it means that the development task is completed. This is what we often call TDD (Test Driven Development)

brief introduction

Junit is an open source unit testing framework for Java language. It is specially designed for Java and is most widely used. (of course, Kotlin has no problem using it. Just pay attention to some small details)

Dependency mode

Maven

<dependencies>
 	<dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.12</version>
    	<scope>test</scope>
	</dependency>
</dependencies>

Gradle

dependencies {
	testImplementation 'junit:junit:4.12'
}

Main methods

The main methods in Assert class are as follows:

Method name

Method description

assertEquals

Assert that the expected value passed in is equal to the actual value

assertNotEquals

Assert that the expected value passed in is not equal to the actual value

assertArrayEquals

Assert that the expected array passed in is equal to the actual array

assertNull

Asserts that the object passed in is empty

assertNotNull

Asserts that the object passed in is not empty

assertTrue

Assertion condition is true

assertFalse

Assertion condition is false

assertSame

The assertion refers to the same object, which is equivalent to "= ="

assertNotSame

Assert that two objects refer to different objects, which is equivalent to "! ="

assertThat

Asserts whether the actual value meets the specified condition

Note that all the above methods have corresponding overloaded methods. You can add a String type parameter in front to indicate the prompt when the assertion fails.

Common notes

Execution sequence: @ BeforeClass – > @ before – > @ test – > @ after – > @ afterclass

Annotation name

meaning

@Test

Indicates that this method is a test method

@Before

It is executed before each test method and can be initialized

@After

Execute after each test method to release resources

@Ignore

Ignored test method

@BeforeClass

Run before all methods in the class. The method modified by this annotation must be static void

@AfterClass

Run last in class. The method modified by this annotation must be static void

@RunWith

Specifies that the test class uses a runner

@Parameters

Specifies the test data collection for the test class

@Rule

Reformulate the behavior of methods in test classes

@FixMethodOrder

Specifies the execution order of the methods in the test class

Mode of use

Basic use

For example, we have an algorithm equivalent to parentheses.

StackExample.kt

/** Equivalent bracket
 *  For example, the parenthesis sequence represented by a given string contains the following characters: '(', '', '{', '}', '[' and ']' to determine whether it is a valid parenthesis sequence.
 *  Brackets must be expressed in the order of '()', '() [] {}' is a valid bracket, but '([)]' is an invalid bracket.
 *
 *  Solution idea:
 *  Using stack storage, cut the string into char traversal, and first store the symbols in the specified direction, such as' (',' {',' ['.
 *  If it belongs to the right direction, such as'} ', enter the judgment. If the symbol at the top of the stack is equal to the current char and the stack will not be null, it is correct. Otherwise, return false directly
 * */
fun isBrackets(str: String): Boolean {
    if (str.length < 2) return false
    val stack = Stack<Char>()
    str.forEach {
        if ("{([".contains(it)) {
            stack.push(it)
        } else {
            if (stack.isNotEmpty() && isBracketChart(stack.peek(), it)) {
                stack.pop()
            } else return false
        }
    }
    return stack.isEmpty()
}

private fun isBracketChart(str1: Char, str2: Char): Boolean =
    (str1 == '{' && str2 == '}') ||
            (str1 == '[' && str2 == ']') || (str1 == '(' && str2 == ')')

Code testing (JUnit not used)

If Junit is not used, we may write the following test code:

fun main() {
    println(isBrackets("{}"))
  	xxxx...
}

In contrast, if we add other methods, we need to modify the main() method frequently, and we can't be intuitive about the correctness of the test.

Using Junit

Under the corresponding test package, we can create a new class such as stackeexamplekttest, or directly use the following shortcut to use mac(option + Enter) and windows(ctrl + Enter) in front of the corresponding method, as shown in the figure

Examples are as follows:

StackExampleKtTest

class StackExampleKtTest {

    @Test
    fun testIsBrackets() {
        assertArrayEquals(
            booleanArrayOf(
                isBrackets(""),
                isBrackets("{"),
                isBrackets("{{}}"),
                isBrackets("{({})}"),
                isBrackets("{({}"),
            ), booleanArrayOf(
                false, false, true, true,false
            )
        )
    }
}

Parametric test

Using the above methods, if we have to set the corresponding value every time we test a method, which is relatively cumbersome, how can we test the same method with continuous different values, so that we can avoid not modifying it many times and save some time. At this time, use @ RunWith and @ Parameters

First, you need to add the runwith (parameterized. Class) annotation on the test class. After creating a static method annotated by @ Paramters, you can return a corresponding test data set. Finally, you need to create a construction method. The parameter order of the method corresponds to the test data set one by one.

Examples are as follows:

@RunWith(Parameterized::class)
class StackExampleKtTest(private val str: Pair<String, Boolean>) {

    // TODO: 2020/11/15 accordingly, this processing method is also easy to make it difficult to find errors
    companion object {
        @Parameterized.Parameters
        @JvmStatic
        fun primeStrings(): Collection<Pair<String, Boolean>> =
            listOf(
                "" to false,
                "{" to false,
                "{}" to false,
                "{[]}" to true,
                "asd{()}" to false
            )
    }

    @Test
    fun testIsBrackets() {
      	//Pay attention to the error prompt here
        assertEquals("Error -current \"${str.first}\" to ${str.second}",
            str.second, isBrackets(str.first))
    }
}

Note: the corresponding @ parameterized When using the parameters method in Kotlin, @ JvmStatic needs to be added. In the process of using this kind of parametric test, if we do not add error prompt, it may not be easy to find the problem in the test case when looking for the problem, so we also need to pay attention to this.

assertThat usage

Used to improve readability of output information after assertion failure. By default, assertion failure will only throw AssertionError. We can't know where the error is, and the function of assertThat is to solve this problem.

Common matcher collations:

Matcher

explain

example

is

The assertion parameter is equal to the matching expression given later

assertThat(5, is (5));

not

The assertion parameter is not equal to the matching expression given later

assertThat(5, not(6));

equalTo

Assert parameter equality

assertThat(30, equalTo(30));

equalToIgnoringCase

Assert string equality, ignoring case

assertThat("Ab", equalToIgnoringCase("ab"));

containsString

The assertion string contains a string

assertThat("abc", containsString("bc"));

startsWith

The assertion string starts with a string

assertThat("abc", startsWith("a"));

endsWith

The assertion string ends with a string

assertThat("abc", endsWith("c"));

nullValue

The value of the assertion parameter is null

assertThat(null, nullValue());

notNullValue

The value of the assertion parameter is not null

assertThat("abc", notNullValue());

greaterThan

Assertion parameter greater than

assertThat(4, greaterThan(3));

lessThan

Assertion parameter is less than

assertThat(4, lessThan(6));

greaterThanOrEqualTo

Assertion parameter is greater than or equal to

assertThat(4, greaterThanOrEqualTo(3));

lessThanOrEqualTo

Assertion parameter is less than or equal to

assertThat(4, lessThanOrEqualTo(6));

closeTo

Asserts that a floating-point number is within a certain range

assertThat(4.0, closeTo(2.6, 4.3));

allOf

The assertion meets all conditions and is equivalent to&&

assertThat(4,allOf(greaterThan(3), lessThan(6)));

anyOf

An assertion that meets a condition is equivalent to or

assertThat(4,anyOf(greaterThan(9), lessThan(6)));

hasKey

Assert that the Map collection contains this key

assertThat(map, hasKey("key"));

hasValue

Assert that the Map collection contains this value

assertThat(map, hasValue(value));

hasItem

Assert that the iteration object contains this element

assertThat(list, hasItem(element));

@Rule

During the test, we can also add @ Before or @ After to achieve a prompt effect Before and After the test, but it may be a little troublesome to write this every time. So @ Rule. Can be used at this time

Examples are as follows:

TestPrompt

 class TestPormpt : TestRule {
    override fun apply(statement: Statement, description: Description): Statement {

        // Gets the name of the test method
        val methodName: String = description.methodName
        //Equivalent to @ Before
        println(methodName + "Before the test starts!")

        // Running test method
        statement.evaluate()

        //End of operation, equivalent to @ After
        println(methodName + "The test is over!")
        return statement
    }
}
class StackExampleKtTest {

    // TODO: 2020/11/15
    //  Note: when used in Kotlin, you need to change @ Rule to @ get:Rule
    //  Or use @ Rule @JvmField
    //@get:Rule
    @Rule @JvmField
    public val prompt = TestPormpt()
  
//    @Before
//    fun testStart(){
//        println("test on")
//    }
//
//    @After
//    fun testStop(){
//        println("test off")
//    }

    @Test
    fun testThat() {
        assertThat("123", equalTo("123"))
    }
}

reference resources

Added by fpbaum on Wed, 09 Feb 2022 17:51:07 +0200