第二章 Exploring JUnit
1.JUnit類
TestCase
TestSuite -- Designed to run one or more test cases
BaseTestRunner -- Launches the TestSuite
2.Composite pattern 在JUnit中的應用
TestSuite and TestCase 都實現了Test介面,在TestSuite增加Test對象,意味著你可以增加TestSuite去運行一組test,或者增加TestCase去運行一個test。
public class TestAll
{
public static Test suite()
{
TestSuite suite = new TestSuite("All tests from part 1");
suite.addTestSuite(TestCalculator.class);
suite.addTestSuite(TestDefaultController.class);
// if TestDefaultController had a suite method
// (or alternate suite methods) you could also use
// suite.addTestSuite(TestDefaultController.suite());
return suite;
}
}
3.Failures and errors
JUnit 區分 failures 和 errors.
Failures是能夠被預計的,比方說代碼改變引起的Failure。
Error 是不能被預期的,如網路連接失敗之類的錯誤.
第四章 Examining Software Tests
軟體測試的幾種類別:
1.Unit test
2.Integration test 整合測試
3.Feature test 功能測試 use case
4.Stress/Load test 壓力測試
5.Acceptance test 驗收測試
Stress/Load test 的工具
JMeter http://jakarta.apache.org/jmeter
JUnitPerf
程式碼涵蓋範圍測試
Clover http://www.thecortex.net/clover/
Jester
Question:
在沒有整合測試前能做功能測試嗎?公司的Software process為Component先做功能測試,再整合測試。
第五章 Automating JUnit
JUnit + Ant
已經有持續測試的架構CruiseControl
http://blog.csdn.net/chelsea/archive/2004/11/22/190545.aspx
第六章 Coarse-grained testing with stubs
Stubs Objects
1. 定義
運行時動態插入的用於替代真實環境的對象。
stub—A stub is a portion of code that is inserted at runtime in place of the real code, in order to isolate calling code from the real implementation.
2. 適用性
粗粒度
3. 缺點
編寫、調試、維護過於複雜
4. 工具
Jetty (Stubbing the web server’s resources)
內嵌的伺服器(embedded server),通過繼承它的ResourceHandler類,可類比web伺服器響應的內容.
http://jetty.mortbay.org/jetty/index.html
啟動Jetty
HttpServer server = new HttpServer();
SocketListener listener = new SocketListener();
listener.setPort(8080);//綁定listener到8080連接埠,接受HTTP的request
server.addListener(listener);
HttpContext context = new HttpContext();
context.setContextPath("/");
context.setResourceBase("./");
context.addHandler(new ResourceHandler());
server.addContext(context);
server.start();
5.執行個體
TestCase中的代碼
public class TestWebClientSkeleton extends TestCase
{
protected static HttpServer server;
protected void setUp()
{
server = new HttpServer();
SocketListener listener = new SocketListener();
listener.setPort(8080);
server.addListener(listener);
HttpContext context1 = new HttpContext();
context1.setContextPath("/testGetContentOk");
context1.addHandler(new TestGetContentOkHandler());
server.addContext(context1);
server.start();
}
protected void tearDown()
{
server.stop();
}
public void testGetContentOk() throws Exception
{
WebClient client = new WebClient();
String result = client.getContent(new URL("http://localhost:8080/testGetContentOk"));
assertEquals ("It works", result);
}
}
實現Jetty的Handle類
private class TestGetContentOkHandler extends AbstractHttpHandler
{
public void handle(String pathInContext, String pathParams,HttpRequest request, HttpResponse response)throws IOException
{
OutputStream out = response.getOutputStream();
ByteArrayISO8859Writer writer =new ByteArrayISO8859Writer();
writer.write("It works");
writer.flush();
response.setIntField(HttpFields.__ContentLength,writer.size());
writer.writeTo(out);
out.flush();
request.setHandled(true);
}
6.TestSetup in TestCase
為所有的TestCase提供全域的setUp和tearDown
Extend TestSetup to provide global setUp and tearDown
比方說所有的TestCase都需要在setUp中啟動Jetty Server,則不該在每個TestCase中做此事。
通過繼承TestSetup,把全域的建立資源的代碼放在setUp裡。
import junit.extensions.TestSetup;
public class TestWebClientSetup1 extends TestSetup
{
protected static HttpServer server;
public TestWebClientSetup1(Test suite)
{
super(suite);
}
protected void setUp() throws Exception{
...//The code should be here
}
}
配置TestSetup
public class TestWebClient1 extends TestCase
{
public static Test suite()
{
TestSuite suite = new TestSuite();
suite.addTestSuite(TestWebClient1.class);
return new TestWebClientSetup1(suite);
}
...
}
第七章 Testing in isolation with mock objects
mock object
1.定義
mock object—A mock object (or mock for short) is an object created to
stand in for an object that your code will be collaborating with. Your
code can call methods on the mock object, which will deliver results
as set up by your tests.
2.最佳實務
2.1 Don't write business logic in mock objects
stub正相反,包含邏輯。
2.2 Only test what can possibly break
3.適應性
Real object has non-deterministic behavior
Real object is difficult to set up
Real object has behavior that is hard to cause (such as a network error)
Real object is slow
Real object has (or is) a UI
mock object作為重構的一種手段
4.優勢
mock object 簡單(因為不包括邏輯)
mock object是一個空殼(empty shell),它自身不需要測試
5.執行個體
5.1 需要被測試的類
public class WebClient
{
public String getContent(URL url)
{
StringBuffer content = new StringBuffer();
try
{
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
InputStream is = connection.getInputStream();
byte[] buffer = new byte[2048];
int count;
while (-1 != (count = is.read(buffer)))
{
content.append(new String(buffer, 0, count));
}
}
catch (IOException e)
{
return null;
}
return content.toString();
}
}
當你希望使用mock對象來測試,如下:
public void testGetContentOk() throws Exception
{
MockHttpURLConnection mockConnection =new MockHttpURLConnection();
mockConnection.setupGetInputStream(new ByteArrayInputStream("It works".getBytes()));
MockURL mockURL = new MockURL();
mockURL.setupOpenConnection(mockConnection);
WebClient client = new WebClient();
String result = client.getContent(mockURL);
assertEquals("It works", result);
}
這樣卻行不通,很難產生mock對象,因為URL是final類,不能通過繼承URL實現mock對象
mock test協助重構,產生靈活的代碼
書中提供了兩種重構方法
一種方法是通過增加介面,類中主要測試的是從URL中取出的Connection。增加一個setConnection和getConnection的方法,允許外部設定Connection(IOC),類裡使用getConnection的方法調用Connection執行個體來獲得Connection的mock類來對Connection測試。
另外一種方法是修改原有的介面,把getContent的參數類型由URL改成引入一個新的工廠類型,定義新的串連工廠,直接返回輸入資料流。代碼如下
public interface ConnectionFactory
{
InputStream getData() throws Exception;
}
public String getContent(ConnectionFactory connectionFactory)
{
...
InputStream is = connectionFactory.getData();
...
}
但如果要對第三方的庫,不能修改,而又沒有良好的設計,則mock不太合適。
第八章 In-container testing with Cactus
測試 Servlet的兩種方式
Stub container:HttpUnit (http://httpunit.sourceforge.net/).
8.1 Outside-the-container testing with mock objects for Servlet
8.1.1 概述
EasyMock 用JDK的dynamic proxy實現
http://www.easymock.org/
8.1.2 執行個體
需要測試的Servlet代碼
public class SampleServlet extends HttpServlet
{
public boolean isAuthenticated(HttpServletRequest request)
{
HttpSession session = request.getSession(false);
if (session == null)
{
return false;
}
String authenticationAttribute =(String) session.getAttribute("authenticated");
return Boolean.valueOf(authenticationAttribute).booleanValue();
}
}
EasyMock的類比代碼
import org.easymock.MockControl;
public class TestSampleServlet extends TestCase
{
private SampleServlet servlet;
private MockControl controlHttpServlet;
private HttpServletRequest mockHttpServletRequest;
private MockControl controlHttpSession;
private HttpSession mockHttpSession;
protected void setUp()
{
servlet = new SampleServlet();
controlHttpServlet = MockControl.createControl(HttpServletRequest.class);
mockHttpServletRequest =(HttpServletRequest) controlHttpServlet.getMock();
controlHttpSession = MockControl.createControl(HttpSession.class);
mockHttpSession =(HttpSession) controlHttpSession.getMock();
}
protected void tearDown()
{
controlHttpServlet.verify();
controlHttpSession.verify();
}
public void testIsAuthenticatedAuthenticated()
{
mockHttpServletRequest.getSession(false);
controlHttpServlet.setReturnValue(mockHttpSession);
mockHttpSession.getAttribute("authenticated");
controlHttpSession.setReturnValue("true");
controlHttpServlet.replay();
controlHttpSession.replay();
assertTrue(servlet.isAuthenticated(mockHttpServletRequest));
}
}
8.1.3 缺點
a. 缺乏與容器的互動測試
b. 缺乏component的部署測試
c. 需要額外的API知識(如Servlet)
d.
8.2 In-container testing using Cactus for Sevelet.
8.2.1 工具
Cactus http://jakarta.apache.org/cactus/
8.2.2 定義
A unit-testing framework specializing in integration unit-testing for server-side Java components.