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")) } }