他使用的原因之一是有些测试似乎毫无意义,这使我想到了什么是单元测试,什么也不需要打扰。
考虑下面一个简单的不可变的Name Bean,其中包含一个构造函数和一堆getter。
在这个示例中,我将让代码说明一切,因为我希望任何测试都是毫无意义的。
public class Name {private final String firstName;private final String middleName;private final String surname;public Name(String christianName, String middleName, String surname) {this.firstName = christianName;this.middleName = middleName;this.surname = surname;}public String getFirstName() {return firstName;}public String getMiddleName() {return middleName;}public String getSurname() {return surname;}
}
……只是为了强调这一点,这是毫无意义的测试代码:
public class NameTest {private Name instance;@Beforepublic void setUp() {instance = new Name("John", "Stephen", "Smith");}@Testpublic void testGetFirstName() {String result = instance.getFirstName();assertEquals("John", result);}@Testpublic void testGetMiddleName() {String result = instance.getMiddleName();assertEquals("Stephen", result);}@Testpublic void testGetSurname() {String result = instance.getSurname();assertEquals("Smith", result);}
}
测试此类毫无意义的原因是该代码不包含任何逻辑。 但是,当您向Name类添加如下内容时:
public String getFullName() {if (isValidString(firstName) && isValidString(middleName) && isValidString(surname)) {return firstName + " " + middleName + " " + surname;} else {throw new RuntimeException("Invalid Name Values");}}private boolean isValidString(String str) {return isNotNull(str) && str.length() > 0;}private boolean isNotNull(Object obj) {return obj != null;}
…然后整个情况发生了变化。 以if语句的形式添加一些逻辑会生成大量测试:
@Testpublic void testGetFullName_with_valid_input() {instance = new Name("John", "Stephen", "Smith");final String expected = "John Stephen Smith";String result = instance.getFullName();assertEquals(expected, result);}@Test(expected = RuntimeException.class)public void testGetFullName_with_null_firstName() {instance = new Name(null, "Stephen", "Smith");instance.getFullName();}@Test(expected = RuntimeException.class)public void testGetFullName_with_null_middleName() {instance = new Name("John", null, "Smith");instance.getFullName();}@Test(expected = RuntimeException.class)public void testGetFullName_with_null_surname() {instance = new Name("John", "Stephen", null);instance.getFullName();}@Test(expected = RuntimeException.class)public void testGetFullName_with_no_firstName() {instance = new Name("", "Stephen", "Smith");instance.getFullName();}@Test(expected = RuntimeException.class)public void testGetFullName_with_no_middleName() {instance = new Name("John", "", "Smith");instance.getFullName();}@Test(expected = RuntimeException.class)public void testGetFullName_with_no_surname() {instance = new Name("John", "Stephen", "");instance.getFullName();}
因此,鉴于我刚刚说过,您不需要测试不包含任何逻辑语句的对象,并且在逻辑 语句列表中,我将包含if并与所有运算符一起切换 (+-*- ),以及可能发生变化和对象状态的一整套事物。
在此前提下,我建议在我前两篇博客中一直在讨论的Address项目中为地址数据访问对象(DAO)编写单元测试毫无意义。 DAO由AddressDao接口定义,并由JdbcAddress类实现:
public class JdbcAddress extends JdbcDaoSupport implements AddressDao {/*** This is an instance of the query object that'll sort out the results of* the SQL and produce whatever values objects are required*/private MyQueryClass query;/** This is the SQL with which to run this DAO */private static final String sql = "select * from addresses where id = ?";/*** A class that does the mapping of row data into a value object.*/class MyQueryClass extends MappingSqlQuery<address> {public MyQueryClass(DataSource dataSource, String sql) {super(dataSource, sql);this.declareParameter(new SqlParameter(Types.INTEGER));}/*** This the implementation of the MappingSqlQuery abstract method. This* method creates and returns a instance of our value object associated* with the table / select statement.* * @param rs* This is the current ResultSet* @param rowNum* The rowNum* @throws SQLException* This is taken care of by the Spring stuff...*/@Overrideprotected Address mapRow(ResultSet rs, int rowNum) throws SQLException {return new Address(rs.getInt("id"), rs.getString("street"),rs.getString("town"), rs.getString("post_code"),rs.getString("country"));}}/*** Override the JdbcDaoSupport method of this name, calling the super class* so that things get set-up correctly and then create the inner query* class.*/@Overrideprotected void initDao() throws Exception {super.initDao();query = new MyQueryClass(getDataSource(), sql);}/*** Return an address object based upon it's id*/@Overridepublic Address findAddress(int id) {return query.findObject(id);}}
在上面的代码中,接口中的唯一方法是:
@Overridepublic Address findAddress(int id) {return query.findObject(id);}
……这实际上是一种简单的吸气方法。 在我看来,这是可以的,因为DAO中确实不应包含属于AddressService的任何业务逻辑,该业务逻辑应具有大量的单元测试。
您可能要决定是否要为MyQueryClass编写单元测试。 对我来说这是一个临界情况,所以我期待任何评论……
我猜想有人会不同意这种方法,说您应该测试JdbcAddress对象,这是真的,我亲自为其编写了一个集成测试,以确保我使用的数据库可以正常运行,并且可以理解我的数据库SQL,并且两个实体(DAO和数据库)可以互相通信,但是我不会打扰对其进行单元测试。
总而言之,单元测试必须是有意义的,并且对“有意义”的良好定义是被测对象必须包含一些独立的逻辑。
参考: 您应该对什么进行单元测试? – Captain Debug博客上的 JCG合作伙伴
测试技术3相关文章 :
- 测试技巧–不编写测试
- 端到端测试的滥用–测试技术2
- 常规单元测试和存根–测试技术4
- 使用模拟的单元测试–测试技术5
- 为旧版代码创建存根–测试技术6
- 有关为旧版代码创建存根的更多信息–测试技术7
- 为什么要编写单元测试–测试技巧8
- 一些定义–测试技术9
- 使用FindBugs产生更少的错误代码
- 在云中开发和测试
翻译自: https://www.javacodegeeks.com/2011/11/what-should-you-unit-test-testing.html