有效单元测试之可读性

断言

断言的基本原理和意图隐藏在无意义的单词和数字背后,难以理解,难以验证断言的正确性。

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());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值