软件测试是 执行的软件以验证代码状态(state testing)或事件序列(behavior testing)符合预期 。
软件单元测试帮助开发人员验证程序的部分逻辑是正确的。
被测试的代码通常称被测代码。如果您正在测试的应用程序,则为被测试应用程序。
测试夹具是代码的固定状态,通常为测试输入,也可以为预置条件。
单元测试用代码测试代码,关注状态和行为测试。单元测试所测试的百分比通常被称为测试覆盖率。
单元测试通常针对一小段代码,例如方法或类。通过测试实现或mock排除外部依赖。
单元测试通常不适合测试复杂用户界面或组件集成。
集成测试关注测试组件的行为或一组组件之间的集成。功能测试有时等同于集成测试。通常需要把user story转化为test suite。
性能测试使用基准重复测试软件组件。其目的是为了确保测试的代码即使是在高负载下也运行速度够快。
行为测试(也叫交互作用测试)不验证的方法调用的结果,但检查方法是否为用正确的输入参数调用。状态测试验证的结果。
如果您正在测试的算法和系统的功能,你想测试在大多数情况下的状态,而不是交互。典型的测试装置使用嘲笑或相关类的存根,以抽象与这些其他类路程,测试状态中的对象,被测试的相互作用。
测试代码与实际代码分开。简单的getter和setter之类代码通常不需要测试。JVM相关内容通常可以认为是可靠的。对已有代码的测试,通常从出错最多的地方开始。Java的主流测试框架是JUnit和TestNG。前者用户最多,对mock框架兼容好。后者可读性更好。
JUnit的4.x版使用注解来指定测试。JUnit的主页:http://junit.org/,代码托管:https://github.com/junit-team/junit。JUnit测试是类中用于测试的方法。使用注解@org.junit.Test。方法中使用的断言方法(JUnit或其他断言框架提供),检查代码的执行的实际结果与预期。
下面的代码演示JUnit测试。
创建java工程first,并在src目录下创建test目录。
创建类MyClass:
package first; public class MyClass { public int multiply(int x, int y) { // the following is just an example if (x > 999) { throw new IllegalArgumentException("X should be less than 1000"); } return x / y; } }
创建测试类MyClassTest,创建方法参见下面的安装配置部分。
package first; import static org.junit.Assert.assertEquals; import org.junit.Test; public class MyClassTest { @Test(expected = IllegalArgumentException.class) public void testExceptionIsThrown() { MyClass tester = new MyClass(); tester.multiply(1000, 5); } @Test public void testMultiply() { MyClass tester = new MyClass(); assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5)); } }
选中测试类, 右键选择Run-As → JUnit Test.
在广泛使用的Junit命名方法是类名称下测试和“测试”加Test后缀。should常用于测试方法名,如ordersShouldBeCreated,menuShouldGetActive,以增强可读性。Maven通过surfire插件自动生成Tests后缀的测试类。
多个测试类可以组合成test suite。运行test suite将在该suite按照指定的顺序执行所有测试类。下面演示包含两个测试类 (MyClassTest和MySecondClassTest) 的测试集,通过@Suite.SuiteClasses statement可以增加测试类。
import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ MyClassTest.class, MySecondClassTest.class }) public class AllTests { }
test suite中也可以包含test suite。
通过标准的Java代码可以在Eclipse之外运行JUnit测试。Apache Maven的或Gradle框架之类的构建框架通常与持续集成服务器(如Hudson或Jenkins)组合定期自动执行测试。
org.junit.runner.JUnitCore类的runClasses()方法允许运行测试类,返回为org.junit.runner.Result对象,包含测试结果信息。
下面类演示如何运行MyClassTest。这个类将执行测试类,输出潜在错误到控制台。
import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; public class MyTestRunner { public static void main(String[] args) { Result result = JUnitCore.runClasses(MyClassTest.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } } }
以上类可以像Java类一样从命令行运行,你只需要添加JUnit Jar到classpath即可。
Junit4.*使用注解(annotation)标识测试方法和配置测试,以下是常用相关annotation的简要说明。特别重要的注解如下:
注解 | 描述 |
@Test public void method() |
标识方法为测试方法 |
@Test (expected = Exception.class) | 标识抛出指定的异常 |
@Test(timeout=100) | 超时,单位ms |
@Before public void method() |
每个测试执行之前的准备工作。用来准备测试环境,如读取输入数据,初始化类等。 |
@After public void method() |
每个测试执行之后的清理工作。如删除临时文件,恢复初始设置,释放内存等。 |
@BeforeClass public static void method() |
每个测试集执行之前的准备工作。用来执行费时任务,如连接数据库。必须是Static方法。 |
@AfterClass public static void method() |
每个测试集执行之后的清理工作。用来清理,如断开数据库连接,必须是Static方法。 |
@Ignore or @Ignore("Why disabled") | 忽略指定测试方法,用于测试类尚未准备好等情况,使用时最好标明忽略的原因。 |
@RunWith会替代默认的org.junit.runner.Runner类,比如:
@RunWith(Suite.class) public class MySuite { }
Mock也需要使用注解。
import org.junit.runner.RunWith; import org.junit.runners.Suite;
@RunWith(Suite.class) @Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class, Assumption.class }) public class TestSuite { }
JUnit的Assert
类提供一些静态方法,一般以assert开头,参数分别为错误信息
,期望值
,实际值
。如果期望与实际不符,将抛出AssertionException
异常。
以下给出这类方法的概览,[]中的参数为可选,类型为String。
语句 | 描述 |
fail(message) | 让方法失败。可用于确认某部分代码未被运行,或在测试代码完成前人为返回失败结果,message参数可选。 |
assertTrue([message,] boolean condition) | 确认布尔条件为真。 |
assertFalse([message,] boolean condition) | 确认布尔条件为假 |
assertEquals([message,] expected, actual) | 确认返回等于期望,数组等比较的是地址而非内容。 |
assertEquals([message,] expected, actual, tolerance) | 测试float或double相等。 tolerance为容许的误差。 |
assertNull([message,] object) | 对象为null |
assertNotNull([message,] object) | 对象非null |
assertSame([message,] expected, actual) | 变量指向同一对象. |
assertNotSame([message,] expected, actual) | 变量指向不同对象 |
JUnit会假定所有测试方法按任意顺序执行,也就是说,一个测试不应该依赖其它测试。
Junit 4.11允许你用Annoation以字母顺序对测试方法进行排序,使用方法为用@FixMethodOrder(MethodSorters.NAME_ASCENDING)。默认使用固定但不可预期的顺序,对应参数为`MethodSorters.DEFAULT`,也可以使用`MethodSorters.JVM`,代表JVM的默认方式,每次运行顺序会不同。
assertEquals(double expected,double actual, double delta)可以解决double的精度误差,对于钱,建议使用BigDecimal类型。
assertThat扩展了断言,语法如下:
public static void assertThat(Object actual, Matcher matcher)
Matcher实现了org.hamcrest.Matcher接口,并且可以组合,比如:
• assertThat(calculatedTax, is(not(thirtyPercent)) );
• assertThat(phdStudentList, hasItem(DrJohn) );
• assertThat(manchesterUnitedClub, both( is(EPL_Champion)).and(is(UEFA_Champions_League_Champion)) );
Matcher列表如下:
allOf , anyOf , both , either , describedAs , everyItem , is , isA , anything ,
hasItem , hasItems , equalTo , any , instanceOf , not , nullValue , notNullValue ,
sameInstance , theInstance , startsWith , endsWith , and containsString。
相关实例如下:
package com.example; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; import org.junit.Test; public class AssertThatTest { @Test public void test_matcher_behavior() throws Exception { int myAge = 30; //examine the exact match with equalTo and is assertThat(myAge, equalTo(30)); assertThat(myAge, is(30)); //examine partial match with not() assertThat(myAge, not(equalTo(33))); assertThat(myAge, is(not(33))); } @Test public void verify_multiple_values() throws Exception { double myMarks = 100.00; assertThat(myMarks, either(is(100.00)).or(is(90.9))); assertThat(myMarks, both(not(99.99)).and(not(60.00))); assertThat(myMarks, anyOf(is(100.00),is(1.00),is(55.00),is(88.00),is(67.8))); assertThat(myMarks, not(anyOf(is(0.00),is(200.00)))); assertThat(myMarks, not(allOf(is(1.00),is(100.00),is(30.00)))); } @Test public void verify_collection_values() throws Exception { List<Double> salary =Arrays.asList(50.0, 200.0, 500.0); assertThat(salary, hasItem(50.00)); assertThat(salary, hasItems(50.00, 200.00)); assertThat(salary, not(hasItem(1.00))); } @Test public void verify_Strings() throws Exception { String myName = "John Jr Dale"; assertThat(myName, startsWith("John")); assertThat(myName, endsWith("Dale")); assertThat(myName, containsString("Jr")); } }
自定义的实例:
package org.hamcrest.examples.tutorial;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class IsNotANumber extends TypeSafeMatcher<Double> {
@Override
public boolean matchesSafely(Double number) {
return number.isNaN();
}
public void describeTo(Description description) {
description.appendText("not a number");
}
@Factory
public static <T> Matcher<Double> notANumber() {
return new IsNotANumber();
}
}
使用:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;
import junit.framework.TestCase;
public class NumberTest extends TestCase {
public void testSquareRootOfMinusOneIsNotANumber() {
assertThat(Math.sqrt(-1), is(notANumber()));
}
}
切记,要静态导入notANumber, 参考资料:https://code.google.com/p/hamcrest/wiki/Tutorial。
稍微复杂点的实例:
package com.example; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Factory; import org.hamcrest.Matcher; public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> { private final Comparable<T> expValue; public LessThanOrEqual(T expValue) { this.expValue= expValue; } @Override public void describeTo(Description desc) { desc.appendText(" less than or equal(<=)" +expValue); } @Override public boolean matches(Object t) { int compareTo = expValue.compareTo((T)t); return compareTo > -1; } @Factory public static<T extends Comparable<T>> Matcher<T> lessThanOrEqual(T t) { return new LessThanOrEqual(t); } }
使用:
@Test public void lessthanOrEquals_matcher() throws Exception { int actualGoalScored = 2; int expGoalScored= 4; assertThat(actualGoalScored, lessThanOrEqual(expGoalScored)); expGoalScored =2; assertThat(actualGoalScored, lessThanOrEqual(expGoalScored )); double actualDouble = 3.14; double expDouble = 9.00; assertThat(actualDouble, lessThanOrEqual(expDouble)); String authorName = "Sujoy"; String expAuthName = "Zachary"; assertThat(authorName, lessThanOrEqual(expAuthName)); }
在Gradle编译时使用Junit:
apply plugin: 'java' dependencies { testCompile 'junit:junit:4.12' }
Maven:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
Eclipse 等IDE自带Junit。
Eclipse对Junit的支持
Eclipse有创建JUnit测试的向导。例如,要为现有类创建JUnit测试或测试类,在类单击鼠标右键,New → JUnit Test Case。File → New → Other... → Java→ JUnit也可打开类似窗口。
执行:在类单击鼠标右键Run-as →JUnit Test,执行类中所有测试。Alt+Shift+X, ,T,如果光标在测试里面,只会执行当前测试。
只看失败的测试:
测试失败时才弹出:
拷贝错误信息
JUnit的静态导入
静态导入允许在类中定义的public static字段和方法不指定类就可以使用。
// without static imports you have to write the following statement Assert.assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5)); // alternatively define assertEquals as static import import static org.junit.Assert.assertEquals; // more code // use assertEquals directly because of the static import assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
Eclipse中可以配置自动静态导入:Window → Preferences and select Java → Editor → Content Assist → Favorites.
org.junit.Assert
org.hamcrest.CoreMatchers
org.hamcrest.Matchers
这样就可以使用Content Assist(快捷方式Ctrl+Space)添加方法导入。
注解:@Test (expected = Exception.class)可以测试单个异常。
测试多个异常的方法:
try { mustThrowException(); fail(); } catch (Exception e) { // expected // could also check for message of exception, etc. }
JUnit的插件测试为插件书写单元测试。这些测试运行特殊的测试执行器,在单独的虚拟机中生成Eclipse实例。
使用注解@RunWith(Parameterized.class)即可。
测试类必须包含@Parameters注解的静态方法返回数组的集合,用于作为测试方法的参数。public域使用@parameter注解可以取得测试注入测试值。
package first; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.util.Arrays; import java.util.Collection; import static org.junit.Assert.assertEquals; import static org.junit.runners.Parameterized.*; @RunWith(Parameterized.class) public class ParameterizedTestFields { // fields used together with @Parameter must be public @Parameter public int m1; @Parameter (value = 1) public int m2; // creates the test data @Parameters public static Collection<Object[]> data() { Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } }; return Arrays.asList(data); } @Test public void testMultiplyException() { MyClass tester = new MyClass(); assertEquals("Result", m1 * m2, tester.multiply(m1, m2)); } // class to be tested class MyClass { public int multiply(int i, int j) { return i *j; } } }
使用构造方法也可以实现类似的效果:
package first; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class ParameterizedTestUsingConstructor { private int m1; private int m2; public ParameterizedTestUsingConstructor(int p1, int p2) { m1 = p1; m2 = p2; } // creates the test data @Parameters public static Collection<Object[]> data() { Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } }; return Arrays.asList(data); } @Test public void testMultiplyException() { MyClass tester = new MyClass(); assertEquals("Result", m1 * m2, tester.multiply(m1, m2)); } // class to be tested class MyClass { public int multiply(int i, int j) { return i *j; } } }
Rule可以灵活的增加或者重定义测试类每个测试方法的行为。通过@Rule注解可以创建和配置在测试方法使用的对象。比如灵活地指定异常:
package first; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class RuleExceptionTesterExample { @Rule public ExpectedException exception = ExpectedException.none(); @Test public void throwsIllegalArgumentExceptionIfIconIsNull() { exception.expect(IllegalArgumentException.class); exception.expectMessage("Negative value not allowed"); ClassToBeTested t = new ClassToBeTested(); t.methodToBeTest(-1); } }
注意上述代码只做演示,不能实际执行。
JUnit的已经提供了规则的几个有用的实现。例如, TemporaryFolder类在测试执行完毕后会删除文件。
package first; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; public class RuleTester { @Rule public TemporaryFolder folder = new TemporaryFolder(); @Test public void testUsingTempFolder() throws IOException { File createdFolder = folder.newFolder("newfolder"); File createdFile = folder.newFile("myfilefile.txt"); assertTrue(createdFile.exists()); } }
实现TestRule接口可自定义Rule。这个接口的apply(Statement, Description)方法返回Statement实例。Statement即JUnit运行时中的测试,Statement#evaluate()运行它们。Description 描述了单个测试。它通过反射阅读测试信息。
下例子添加日志语句到Android应用。
import android.util.Log; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class MyCustomRule implements TestRule { private Statement base; private Description description; @Override public Statement apply(Statement base, Description description) { this.base = base; this.description = description; return new MyStatement(base); } public class MyStatement extends Statement { private final Statement base; public MyStatement(Statement base) { this.base = base; } @Override public void evaluate() throws Throwable { Log.w("MyCustomRule",description.getMethodName() + "Started"); try { base.evaluate(); } finally { Log.w("MyCustomRule",description.getMethodName() + "Finished"); } } } }
使用Rule:
@Rule public MyCustomRule myRule = new MyCustomRule();
更多关于Rule的资料:https://github.com/junit-team/junit/wiki/Rules。
public interface FastTests { /* category marker */ } public interface SlowTests { /* category marker */ } public class A { @Test public void a() { fail(); } @Category(SlowTests.class) @Test public void b() { } } @Category({ SlowTests.class, FastTests.class }) public class B { @Test public void c() { } } @RunWith(Categories.class) @IncludeCategory(SlowTests.class) @SuiteClasses({ A.class, B.class }) // Note that Categories is a kind of Suite public class SlowTestSuite { // Will run A.b and B.c, but not A.a } @RunWith(Categories.class) @IncludeCategory(SlowTests.class) @ExcludeCategory(FastTests.class) @SuiteClasses({ A.class, B.class }) // Note that Categories is a kind of Suite public class SlowTestSuite { // Will run A.b, but not A.a or B.c }
本部分参考资料:https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.8.md
python开发自动化测试群291184506 PythonJava单元白盒测试群144081101
Unit Testing with JUnit - Tutorial