意图
我正在寻找以下内容:
- 可靠的单元测试方法
- 我的方法中缺少什么?
- 我在做什么错?
- 我在做什么是不必要的?
- 一种尽可能多地自动完成的方法
当前环境
- Eclipse作为 IDE
- JUnit作为测试框架,集成到 Eclipse 中
- Hamcrest作为“匹配器”库,以提高断言的可读性
- 用于前置条件验证的Google Guava
目前的方法
结构
- 每个班级一个测试班进行测试
- 在静态嵌套类中分组的方法测试
- 测试方法命名以指定测试的行为 + 预期结果
- Java Annotation指定的预期异常,不在方法名称中
方法
- 注意
null
价值观 - 注意空的List<E>
- 注意空字符串
- 注意空数组
- 注意被代码改变的对象状态不变量(后置条件)
- 方法接受记录的参数类型
- 边界检查(例如Integer.MAX_VALUE等...)
- 通过特定类型记录不变性(例如Google Guava ImmutableList<E>)
- ...有这个清单吗?不错的测试列表示例:
- 在数据库项目中检查的东西(例如 CRUD、连接性、日志记录等)
- 在多线程代码中检查的事情
- 检查 EJB 的事项
- ... ?
示例代码
这是一个人为的例子来展示一些技术。
MyPath.java
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Arrays;
import com.google.common.collect.ImmutableList;
public class MyPath {
public static final MyPath ROOT = MyPath.ofComponents("ROOT");
public static final String SEPARATOR = "/";
public static MyPath ofComponents(String... components) {
checkNotNull(components);
checkArgument(components.length > 0);
checkArgument(!Arrays.asList(components).contains(""));
return new MyPath(components);
}
private final ImmutableList<String> components;
private MyPath(String[] components) {
this.components = ImmutableList.copyOf(components);
}
public ImmutableList<String> getComponents() {
return components;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (String pathComponent : components) {
stringBuilder.append("/" + pathComponent);
}
return stringBuilder.toString();
}
}
MyPathTests.java
import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import com.google.common.base.Joiner;
@RunWith(Enclosed.class)
public class MyPathTests {
public static class GetComponents {
@Test
public void componentsCorrespondToFactoryArguments() {
String[] components = { "Test1", "Test2", "Test3" };
MyPath myPath = MyPath.ofComponents(components);
assertThat(myPath.getComponents(), contains(components));
}
}
public static class OfComponents {
@Test
public void acceptsArrayOfComponents() {
MyPath.ofComponents("Test1", "Test2", "Test3");
}
@Test
public void acceptsSingleComponent() {
MyPath.ofComponents("Test1");
}
@Test(expected = IllegalArgumentException.class)
public void emptyStringVarArgsThrows() {
MyPath.ofComponents(new String[] { });
}
@Test(expected = NullPointerException.class)
public void nullStringVarArgsThrows() {
MyPath.ofComponents((String[]) null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInterspersedEmptyComponents() {
MyPath.ofComponents("Test1", "", "Test2");
}
@Test(expected = IllegalArgumentException.class)
public void rejectsSingleEmptyComponent() {
MyPath.ofComponents("");
}
@Test
public void returnsNotNullValue() {
assertThat(MyPath.ofComponents("Test"), is(notNullValue()));
}
}
public static class Root {
@Test
public void hasComponents() {
assertThat(MyPath.ROOT.getComponents(), is(not(empty())));
}
@Test
public void hasExactlyOneComponent() {
assertThat(MyPath.ROOT.getComponents(), hasSize(1));
}
@Test
public void hasExactlyOneInboxComponent() {
assertThat(MyPath.ROOT.getComponents(), contains("ROOT"));
}
@Test
public void isNotNull() {
assertThat(MyPath.ROOT, is(notNullValue()));
}
@Test
public void toStringIsSlashSeparatedAbsolutePathToInbox() {
assertThat(MyPath.ROOT.toString(), is(equalTo("/ROOT")));
}
}
public static class ToString {
@Test
public void toStringIsSlashSeparatedPathOfComponents() {
String[] components = { "Test1", "Test2", "Test3" };
String expectedPath =
MyPath.SEPARATOR + Joiner.on(MyPath.SEPARATOR).join(components);
assertThat(MyPath.ofComponents(components).toString(),
is(equalTo(expectedPath)));
}
}
@Test
public void testPathCreationFromComponents() {
String[] pathComponentArguments = new String[] { "One", "Two", "Three" };
MyPath myPath = MyPath.ofComponents(pathComponentArguments);
assertThat(myPath.getComponents(), contains(pathComponentArguments));
}
}
问题,明确表述
是否有用于构建单元测试的技术列表?比我上面过于简化的列表更高级的东西(例如检查空值、检查边界、检查预期异常等)也许可以在要购买的书或要访问的 URL 中找到?
一旦我有了一个采用某种类型参数的方法,我能否获得任何Eclipse插件来为我的测试生成存根?也许使用 Java注释来指定有关方法的元数据并让工具为我实现相关检查?(例如@MustBeLowerCase,@ShouldBeOfSize(n=3),...)
我发现必须记住所有这些“质量检查技巧”和/或应用它们很乏味且像机器人一样,我发现复制和粘贴很容易出错,而且当我像我一样编写代码时发现它不是自我记录的以上。诚然,Hamcrest库朝着专门测试类型的大方向发展(例如,使用 RegEx对String对象、对File对象等),但显然不会自动生成任何测试存根,也不会反映代码及其属性并准备对我来说是一个安全带。
请帮我把这个做得更好。
附言
不要告诉我,我只是在展示代码,它是围绕从静态工厂方法中提供的路径步骤列表创建路径的概念的愚蠢包装,这是一个完全虚构的示例,但它显示了“几个” 论证验证案例......如果我包含一个更长的例子,谁会真正阅读这篇文章?