断言
断言的基本原理和意图隐藏在无意义的单词和数字背后,难以理解,难以验证断言的正确性。
Bad eg.
@Test
public void outputHasLineNumbers() {
String content = "1st match on #1\nand\n2nd match on #3";
String out = grep.grep("match", "test.txt", content);
assertTrue(out.indexOf("test.txt:1 1st match") != -1);
assertTrue(out.indexOf("test.txt:3 2nd match") != -1);
}
Good eg.
@Test
public void outputHasLineNumbers() {
String content = "1st match on #1\nand\n2nd match on #3";
String out = grep.grep("match", "test.txt", content);
assertThat(out, containsString("test.txt:1 1st match"));
assertThat(out, containsString("test.txt:3 2nd match"));
}
一个测试应该只有一个失败的原因
按位
Bad eg.
@Test
public void platformBitLength() {
assertTrue(Platform.IS_32_BIT ^ Platform.IS_64_BIT);
}
Good eg.
@Test
public void platformBitLength() {
assertTrue("Not 32 or 64-bit platform?", Platform.IS_32_BIT || Platform.IS_64_BIT);
assertFalse("Cant't be 32 and 64-bit at the same time." Platform.IS_32_BIT && Platform.IS_64_BIT);
}
附加细节
数据初始化+断言细节--混在一起
Bad eg.
public class TestObjectSpace {
private Ruby runtime;
private ObjectSpace objectSpace;
@Before
public void setUp() {
runtime = Ruby.newInstance();
objectSpace = new ObjectSpace();
}
@Test
public void testObjectSpace() {
IRubyObject o1 = runtime.newFixnum(10);
IRubyObject o2 = runtime.newFixnum(20);
IRubyObject o3 = runtime.newFixnum(30);
IRubyObject o4 = runtime.newString("hello");
objectSpace.add(o1);
objectSpace.add(o2);
objectSpace.add(o3);
objectSpace.add(o4);
List storedFixnums = new ArrayList(3);
storedFixnums.add(o1);
storedFixnums.add(o2);
storedFixnums.add(o3);
Iterator strings = objectSpace.iterator(runtime.getString());
assertSame(o4, strings.text());
assertNull(strings.next());
Iterator numerics = objectSpace.iterator(runtime.getNumeric());
for(int i=0; i<3; i++) {
Object item = numerics.next();
assertTrue(storedFixnums.contains(item));
}
assertNull(numerics.next());
}
}
Good eg.
public class TestObjectSpace {
private Ruby runtime;
private ObjectSpace objectSpace;
private IRubyObject string;
private List<IRubyObject> fixnums;
@Before
public void setUp() {
runtime = Ruby.newInstance();
objectSpace = new ObjectSpace();
string = runtime.newString("hello");
fixnums = new ArrayList<IRubyObject>() {{
add(runtime.newFixnum(10));
add(runtime.newFixnum(20));
add(runtime.newFixnum(30));
}};
}
@Test
public void testObjectSpace() {
//填充ObjectSpace
addTo(space, string);
addTo(space, fixnums)
//检查ObjectSpace的内容
Iterator strings = space.iterator(runtime.getString());
assertContainsExactly(strings, string);
Iterator numerics = space.iterator(runtime.getNumeric());
assertContainsExactly(numerics, fixnums);
}
private void addTo(ObjectSpace space, Object... values) {}
private void addTo(ObjectSpace space, List values) {}
private void assertContainsExactly(Iterator i, Object... values) {}
private void assertContainsExactly(Iterator i, List values){}
}
方法粒度
人格分裂:一个测试方法内有多个测试。
Bad eg.
@Test
public void testParsingCommandLineArguments() {
String[] args = {"-f", "hello.txt", "-v", "--version"};
Configuration config = new Configuration();
config.processArguments(args);
assertEquals("hello.txt", config.getFileName());
assertFalse(config.isDebuggingEnabled());
assertFalse(config.isWarningsEnabled());
assertTrue(config.isVerbose());
assertTrue(config.shouldShowVersion());
config = new Configuration();
try {
config.processArguments(new String[] {"-f"});
fail("Should've faild");
} catch (InvalidArgumentException e) {
// this is okay and expected
}
}
Good eg.
public class TestConfiguration {
private Configuration config;
@Before
public void before() {
config = new Configuration();
}
@Test
public void validArgumentsProvided() {
String[] args = {"-f", "hello.txt", "-v", "--version"};
config.processArguments(args);
assertEquals("hello.txt", config.getFileName());
assertFalse(config.isDebuggingEnabled());
assertFalse(config.isWarningsEnabled());
assertTrue(config.isVerbose());
assertTrue(config.shouldShowVersion());
}
@Test(expected = InvalidArgumentException.class)
public void missingArgument() {
config.processArguments(new String[] {"-f"});
}
}
逻辑分割
过度分散,增加认知负担。
Bad eg.
public class TestRuby {
private Ruby runtime;
@Before
public void before() {
runtime = Ruby.newInstance();
}
@Test
public void testVarAndMet() {
runtime.getLoadService().init(new ArrayList());
eval("load 'test/testVariableAndMethod.rb'");
assertEquals("Hello World", eval("puts($a)"));
assertEquals("dlroW olleH", eval("puts($b)"));
assertEquals("Hello World", eval("puts $d.reverse, $c, $e.reverse"));
assertEquals("135 20 3", eval("put $f, \" \", $g, \" \", $h"));
}
}
代码与文件分离testVariableAndMethod.rb
a = String.new("Hello World");
b = a.reverse
c = " "
d = "Hello".reverse
e = a[6, 5].reverse
f = 100 + 35
g = 2 * 10
h = 13 % 5
$a = a
$b = b
$c = c
$d = d
$e = e
$f = f
$g = g
$h = h
Good eg.
public class TestRuby {
private Ruby runtime;
private AppendableFile script;
@Before
public void before() {
runtime = Ruby.newInstance();
script = withTempFile();
}
@Test
public void variableAssignment() {
script.line("a = String.new('Hello')");
script.line("b = 'World'");
script.line("$c = 1 + 2");
afterEvaluating(script);
assertEquals("Hello", eval("puts(a)"));
assertEquals("World", eval("puts b"));
assertEquals("3", eval("puts $c"));
}
@Test
public void methodInvocation() {
script.line("a = 'Hello'.reverse");
script.line("b = 'Hello'.length()");
script.line("c = ' abc '.trim(' ', '_')");
afterEvaluating(script);
assertEquals("olleH", eval("puts a"));
assertEquals("3", eval("puts b"));
assertEquals("_abc_", eval("puts c"));
}
private void afterEvaluating(AppendableFile sourceFile) {
eval("load '" + sourceFile.getAbsolutePath() + "'");
}
}
魔法数字
Bad eg.
public class BowlingGameTest {
@Test
public void perfectGame() {
roll(10, 12);
assertThat(game.score(), is(equalTo(300)));
}
}
Good eg.
public class BowlingGameTest {
@Test
public void perfectGame() {
roll(pins(10), times(12));
assertThat(game.score(), is(equalTo(300)));
}
private int pins(int n) {
return n;
}
private int times(int n) {
return n;
}
}
冗长安装
Bad eg.
public class PackageFetcherTest {
private PackageFetcher fetcher;
private Map downloads;
private File tempDir;
@Before
public void before() {
String systemTempDir = System.getProperty("java.io.tmpdir");
tempDir = new File(systemTempDir, "downloads");
tempDir.mkDirs();
String filename = "/manifest.xml";
InputStream xml = getClass().getResourceAsStream(filename);
Document manifest = XOM.parse(IO.streamAsString(xml));
PresentationList presentations = new PresentationList();
presentations.parse(manifest.getRootElement());
PresentationStorage db = new PresentationStorage();
List list = presentations.getResourcesMissingFrom(null, db);
fetcher = new PackageFetcher();
downloads = fetcher.extractDownloads(list);
}
@After
public void after() {
IO.delete(tempDir);
}
@Test
public void downloadsAllResources() {
fetcher.download(downloads, tempDir, new MockConnector());
assertEquals(4, tempDir.list().length);
}
}
Good eg.
public class PackageFetcherTest {
private PackageFetcher fetcher;
private Map downloads;
private File tempDir;
@Before
public void before() {
fetcher = new PackageFetcher();
tempDir = new File(systemTempDir, "downloads");
downloads = extractMissingDownloadsFrom("/manifest.xml");
}
@After
public void after() {
IO.delete(tempDir);
}
@Test
public void downloadsAllResources() {
fetcher.download(downloads, tempDir, new MockConnector());
assertEquals(4, tempDir.list().length);
}
private File createTempDir(String name) {
String systemTempDir = System.getProperty("java.io.tmpdir");
File dir = new File(systemTempDir, name);
dir.mkDirs();
return dir;
}
private Map extractMissingDownloadsFrom(String path) {
PresentationStorage db = new PresentationStorage();
List list = presentations.createPresentationListFrom(path);
List downloads = list.getResourcesMissingFrom(null, db);
return fetcher.extractDownloads(downloads);
}
private PresentationList createPresentationListFrom(String path) {
PresentationList presentations = new PresentationList();
presentations.parse(readManifestFrom(path).getRootElement());
}
private Document readManifestFrom(String path) {
InputStream xml = getClass().getResourceAsStream(path);
Document manifest = XOM.parse(IO.streamAsString(xml));
}
}
过度保护
Bad eg.
@Test
public void count() {
Data data = project.getData();
assertNotNull(data);//过度保护,没必要
assertEquals(4, data.count());
}
Good eg.
@Test
public void count() {
Data data = project.getData();
assertEquals(4, data.count());
}