什么是单元测试
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和期望的一致。
为什么要使用单元测试
如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。
但代码通过编译,只是说明了它的语法正确;我们却无法保证它的语义也一定正确。
编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。
单元测试的优点
1、它是一种验证行为。
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。
2、它是一种设计行为。
编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。
3、它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
4、它具有回归性。
自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。
单元测试所要做的工作
1、它的行为和我期望的一致吗?
这是单元测试最根本的目的,我们就是用单元测试的代码来证明它所做的就是我们所期望的
2、它的行为一直和我期望的一致吗?
编写单元测试,如果只测试代码的一条正确路径,让它正确走一遍,并不算是真正的完成。软件开发是一个项复杂的工程,在测试某段代码的行为是否和你的期望一致时,你需要确认:在任何情况下,这段代码是否都和你的期望一致;譬如参数很可疑、硬盘没有剩余空间、缓冲区溢出、网络掉线的时候。
3、 我可以依赖单元测试吗?
不能依赖的代码是没有多大用处的。既然单元测试是用来保证代码的正确性,那么单元测试也一定要值得依赖。
4、单元测试说明我的意图了吗?
单元测试能够帮我们充分了解代码的用法,从效果上而言,单元测试就像是能执行的文档,说明了在你用各种条件调用代码时,你所能期望这段代码完成的功能。
单元测试的覆盖种类
语句覆盖
语句覆盖就是设计若干个测试用例,运行被测试程序,使得每一条可执行语句至少执行一次
判定覆盖(也叫分支覆盖)
设计若干个测试用例,运行所测程序,使程序中每个判断的取真分支和取假分支至少执行一次。
条件覆盖
设计足够的测试用例,运行所测程序,使程序中每个判断的每个条件的每个可能取值至少执行一次。
判定-条件覆盖
使程序中每个判断的每个条件的每个可能取值至少执行一次,并且每个可能的判断结果也至少执行一次。
条件组合测试
设计足够的测试用例,运行所测程序,使程序中每个判断的所有条件取值组合至少执行一次
路径测试
设计足够的测试用例,运行所测程序,要覆盖程序中所有可能的路径。
单元测试实战
单元测试工具:
JUnit JMock Ant
实例代码:
package cn.net.inch.unittest;
public interface IBankService {
void setInterestStrategy(IInterestStrategy interestStrategy);
void checkout(BankAccount account);
}
package cn.net.inch.unittest;
/**
* @author yellowcat
* 银行相关业务
*/
public class BankService implements IBankService {
private IInterestStrategy interestStrategy;
public void setInterestStrategy(IInterestStrategy interestStrategy) {
this.interestStrategy = interestStrategy;
}
/**
* 对银行账号进行结算
* 现有的余额等于本金加上利息所得
* @param account 银行账号
* @return void
* @throws EmptyAccountException
*/
public void checkout(BankAccount account) {
if (account == null || account.getId() == 0) {
throw new EmptyAccountException("银行账号不能为空");
}
double amount = account.getAmount();
amount += interestStrategy.calculateInterest(account.getAmount(), account.getRate());
account.setAmount(amount);
}
}
package cn.net.inch.unittest;
/**
* @author yellowcat
* 银行账号
*/
public class BankAccount {
private int id; // 账号ID
private String name; //账号名称
private double amount; //账号余额
private double rate; //存款利率
public BankAccount(int id, String name, double amount, double rate) {
super();
this.id = id;
this.name = name;
this.amount = amount;
this.rate = rate;
}
public BankAccount() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
}
package cn.net.inch.unittest;
/**
* @author yellowcat
*
*/
public class EmptyAccountException extends RuntimeException {
private static final long serialVersionUID = 6403530909283386537L;
public EmptyAccountException(String message) {
super(message);
}
}
package cn.net.inch.unittest;
/**
*
* @author yellowcat
*
* 利息计算策略接口
* 要求有两种利息计算策略:
* 包含利息税的和不包含的
*/
public interface IInterestStrategy {
double calculateInterest(double amount, double rate);
}
package cn.net.inch.unittest;
/**
* @author yellowcat
*
*/
public class InterestStrategyWithTax implements IInterestStrategy {
private static final double INTEREST_TAX = 0.2;
/* (non-Javadoc)
* @see cn.net.inch.unittest.IInterestStrategy#calculateInterest(double, double)
*/
public double calculateInterest(double amount, double rate) {
return amount * rate * (1 - INTEREST_TAX);
}
}
package cn.net.inch.unittest;
/**
* @author yellowcat
*
*/
public class InterestStrategyWithoutTax implements IInterestStrategy {
/* (non-Javadoc)
* @see cn.net.inch.unittest.IInterestStrategy#calculateInterest(double, double)
*/
@Override
public double calculateInterest(double amount, double rate) {
return amount * rate;
}
}
测试代码:
package cn.net.inch.unittest;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author yellowcat
*
*/
public class BankServiceTest {
private BankService bankService;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
bankService = new BankService();
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
// nothing to do
}
/**
* 测试包含利息税的结算
*/
@Test
public void testCheckoutWithInterestTax() {
IInterestStrategy interestStrategy = new InterestStrategyWithTax();
bankService.setInterestStrategy(interestStrategy);
BankAccount accontToTest = new BankAccount(1, "harry", 10000D, 0.0225D);
bankService.checkout(accontToTest);
assertEquals(10180D, accontToTest.getAmount());
}
/**
* 测试不包含利息税的结算
*/
@Test
public void testCheckoutWithoutInterestTax() {
IInterestStrategy interestStrategy = new InterestStrategyWithoutTax();
bankService.setInterestStrategy(interestStrategy);
BankAccount accontToTest = new BankAccount(1, "harry", 10000D, 0.0225D);
bankService.checkout(accontToTest);
assertEquals(10225D, accontToTest.getAmount());
}
/**
* 测试账号为空异常
*/
@Test
public void testCheckoutWithEmptyAccount() {
BankAccount accontToTest = new BankAccount();
try {
bankService.checkout(accontToTest);
fail("没有抛出账号为空异常!");
} catch (EmptyAccountException eae) {
// what I except to
}
}
}
package cn.net.inch.unittest;
import static org.junit.Assert.*;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author yellowcat
*
*/
public class BankServiceJMockTest {
private BankService bankService;
// mock factory
private Mockery context = new Mockery();
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
bankService = new BankService();
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
// nothing to do
}
/**
* Test method for {@link cn.net.inch.unittest.BankService#checkout(cn.net.inch.unittest.BankAccount)}.
*/
@Test
public void testCheckout() {
BankAccount accontToTest = new BankAccount(1, "harry", 10000D, 0.0225D);
// set up
final IInterestStrategy interestStrategy = context.mock(IInterestStrategy.class);
// expectations
context.checking(new Expectations() {{
allowing(interestStrategy).calculateInterest(10000D, 0.0225D); will(returnValue(225D));
}});
// execute
bankService.setInterestStrategy(interestStrategy);
bankService.checkout(accontToTest);
// verify
context.assertIsSatisfied();
assertEquals(10225D, accontToTest.getAmount());
}
}
Ant脚本:
<project name="unittest" default="junit-report" basedir=".">
<property name="bin" value="bin" />
<property name="src" value="src" />
<property name="lib" value="lib" />
<property name="test.src" value="test" />
<property name="test.report" value="report" />
<target name="test-init" description="test report folder init">
<mkdir dir="${test.report}" />
</target>
<path id="lib.classpath">
<fileset dir="${lib}">
<include name="*.jar" />
</fileset>
</path>
<target name="compile">
<javac classpathref="lib.classpath" srcdir="${src}" destdir="${bin}" />
<echo>compilation complete!</echo>
</target>
<target name="test-compile" depends="test-init" description="compile test cases">
<javac classpathref="lib.classpath" srcdir="${test.src}" destdir="${bin}" />
<echo>test compilation complete!</echo>
</target>
<target name="compile-all" depends="compile, test-compile">
</target>
<target name="junit-report" depends="compile-all" description="auto test all test case and output report file">
<junit printsummary="on" fork="true" showoutput="true">
<classpath>
<fileset dir="${lib}" includes="*.jar" />
<pathelement path="${bin}" />
</classpath>
<formatter type="xml" />
<batchtest todir="${test.report}">
<fileset dir="${bin}">
<include name="**/*Test.*" />
</fileset>
</batchtest>
</junit>
<junitreport todir="${test.report}">
<fileset dir="${test.report}">
<include name="TEST-*.xml" />
</fileset>
<report format="frames" todir="${test.report}" />
</junitreport>
</target>
</project>
测试报告:
- 大小: 36.2 KB
- 大小: 40.7 KB
分享到:
相关推荐
在软件开发过程中,单元测试是确保代码质量的重要环节。它允许开发者独立地测试代码的各个模块,确保每个部分都能正常工作。EasyMock 是一个流行的 Java 单元测试框架,它帮助开发者创建模拟对象来测试目标类的行为...
《单元测试的艺术》是Roy Osherove所著的一本经典著作,主要探讨了软件开发中的单元测试技术及其重要性。源代码的提供使得读者...同时,这也有助于团队之间的沟通和合作,因为清晰的测试用例可以更好地表达代码意图。
4. **隔离与依赖注入**:讨论如何使用Mock和Stub技术来隔离被测试代码,避免外部依赖对测试结果的影响,以及依赖注入在单元测试中的应用。 5. **测试驱动开发(TDD)**:介绍TDD的工作流程,即先写测试,再写实现,...
了解JaCoCo的使用方法和它提供的信息,可以帮助开发者更好地理解测试的覆盖面,提升代码质量。在实际项目中,结合持续集成和代码审查,可以有效地利用JaCoCo来推动团队遵循良好的测试实践,确保代码的健壮性和可靠性...
为了更好地管理和监控单元测试的效果,"eclipse插件 - 单元测试代码覆盖统计"提供了强大的工具。 这个插件的主要功能是提供代码覆盖率统计,帮助开发者直观地了解单元测试对源代码的覆盖情况。当安装并激活此插件后...
在Android开发中,单元测试是确保代码质量、可维护性和减少缺陷的重要环节。这个小例子将引导我们了解如何在Android项目中设置和执行单元测试。首先,我们需要知道Android的单元测试框架主要有两个:JUnit和 ...
2. 代码质量保证:单元测试迫使开发者编写可测试的代码,这通常意味着更好的设计和模块化。 3. 提高重构信心:当进行代码重构时,单元测试可以提供一个安全网,确保修改不会破坏现有功能。 4. 更快的反馈:由于只...
单元测试是一种软件开发过程中的重要环节,主要用于验证代码的各个最小可测试单元,如函数、方法或类,是否能够按照预期工作。通过编写自动化测试用例,开发者可以在修改代码后快速检查新变更是否引入了错误,确保...
3. **测试驱动开发(TDD)**:使用NUnit进行单元测试可以推动采用TDD(Test-Driven Development)方法。在TDD中,先编写测试,再编写满足测试的代码,这有助于提高代码质量,减少bug,并促进更好的设计。 4. **测试...
单元测试是软件开发过程中的重要环节,它主要目的是验证代码中的最小可测试单元,如方法或函数,是否按预期工作。这种测试可以在早期发现错误,提高代码质量,并且便于后期维护。本篇将深入探讨单元测试的基本理论...
3. **编写单元测试**:使用JUnit或TestNG编写针对源代码的单元测试。 4. **运行测试**:运行单元测试,EclEmma会自动计算并显示覆盖率。 5. **分析结果**:通过颜色标记的源代码查看覆盖率,绿色表示已覆盖,黄色...
单元测试说明模板 单元测试是一种软件测试方法,它通过在单元层面上对软件的每个组件进行测试,以...本文档的内容将有助于开发经理和开发人员更好地理解单元测试的重要性和必要性,并且能够为他们提供明确的测试指导。
《软件单元测试讲义》是一份深入探讨软件开发中单元测试...总结,《软件单元测试讲义》这份资料为开发者提供了一个全面了解和掌握单元测试的平台,通过学习和实践,我们可以更好地确保代码质量,提升软件项目的成功率。
2. **ANT脚本测试所需ANT版本**:使用ANT 1.7.0或更高版本。 3. **JAVA单元测试的JDK环境**:至少使用JDK 1.5.0_11或更高版本。 4. **第三方服务/接口/交易**:如果单元测试涉及第三方服务或接口,应确保能够提供...
可以使用JUnit框架来编写对应的单元测试: ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CalculatorTest { @Test public void test...
根据提供的标题、描述、标签及部分内容,我们可以详细地解析并扩展出...通过以上详细的内容,我们可以看到单元测试报告模板不仅提供了测试过程的全面记录,还帮助团队成员更好地理解测试结果,从而促进软件质量的提升。