`
harry
  • 浏览: 180129 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

更好的代码:使用单元测试

阅读更多

什么是单元测试

      单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

      程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和期望的一致。

为什么要使用单元测试

      如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。

但代码通过编译,只是说明了它的语法正确;我们却无法保证它的语义也一定正确。

      编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。

 

单元测试的优点

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>

 

测试报告:

单元测试报告1 单元测试报告2

 

 

  • 大小: 36.2 KB
  • 大小: 40.7 KB
2
0
分享到:
评论

相关推荐

    sparky-test-helpers:.NET单元测试帮助程序库-“ SparkyTestHelper” nuget包的源代码-spark source code

    此仓库包含nuget软件包的源代码和单元测试: SparkyTestHelpers 单元测试助手,用于异常期望和“方案”(行)测试: SparkyTestHelpers.Scenarios.MsTest SparkyTestHelper.Scenarios扩展,用于使用MSTest / ...

    Junit单元测试的实验报告.docx

    简单介绍了Junit的安装过程与实例应用。应用的问题是软件测试中的佣兵问题,整个文档中有代码及测试结果,可以更好地帮助学生了解Junit单元测试中的作用。

    软件测试之单元测试和自动化测试及UTF应用

    单元测试是软件开发中的一项关键技术,它是指对软件系统中的最小可测试单元进行测试,以确保其能够按照预期的方式工作。...单元测试还可以提高代码质量,降低维护成本,并帮助开发人员更好地理解和修改代码

    单元测试之道(C#版-中文完整版)

    单元测试不但会使你的工作完成得更轻松,而且会令你的设计变得更好,甚至大大减少你花在调试上面的时间。 在我们上面的小故事里面,Pat 因为假设底层的代码是正确无误的而卷入麻烦之中,先是高层代码中使用了底层...

    在.NET环境中使用单元测试工具NUnit

    虽然由程序开发人员自己写Unit Tests(单元测试)来测试自己写的程序代码已经行之有年,但是大部分的Unit Tests都是写在主要的程序代码已经设计好、写好之后。大部分的程序开发人员都有相同的的经验,在主要程序代码...

    对复杂的单元测试使用模拟对象

    真的没有那么困难参考资料本文来自于RationalEdge:为创建使用模拟对象的单元测试,使用相对简单的技术产生更多的无缺陷代码。 如今,程序员比以往更多地认识到他们有责任创建编写较好的单元测试。无论一个开发人员...

    jutf:Java单元测试框架(Warp H2Mockitojmockit工具,使Java应用程序更好)

    Java单元测试框架(Warp H2 / Mockito / jmockit工具使Java应用程序更好) 功能 jutf(无弹簧依赖版本) 使用mockito / jmockit来模拟界面 实用程序模拟get / set / construct / tostring H2内存测试数据库工具 ...

    Java单元测试框架源码分钟-AndroidUnitTest:Android单元测试最佳实践及(junit+mockito+powermock

    单元测试: 顾名思义,是针对某个单元进行测试,单元可大可小,可能是一个框架的核心处理流程,一个模块的控制逻辑,一个类的实现或者一个函数。在这里你只需要关心一点,那就是单元测试的单元是有边界的,在任何时候你要...

    unit-testing-guidelines:单元测试最佳实践

    单元测试最佳实践以下是帮助实现有效且可维护的单元测试的一系列... 确保只使用有意义的断言消息,或者根本不使用(有意义的测试名称更好)。 确保断言与操作(不同的行)分开。 确保测试不使用魔法字符串和值作为输

    开发者视角:使用VSTS进行应用程序分析和单元测试

    VSTS开发版不仅注重为开发者带来强大方面的功能,同时也注重于开发者和团队之间的协作,每一个功能几乎都可以于TeamFoundation进行无缝结合,主要体现在以下几个方面:代码度量单元测试代码分析团队协作下面我们会...

    JAVA单元测试接口作业.zip

    这个资源中的接口主要是为了帮助学生或者开发者更好地理解和掌握Java单元测试的概念和技术。通过这些接口,你可以学习如何编写单元测试,如何使用断言方法来检查结果,以及如何组织和运行测试用例等。此外,这个资源...

    有效的单元测试

    第二部分(第4~6章)的目标是帮助我们更好地识别并修复测试代码中的坏味道。第4章展示破坏测试可读性的坏味道。第5章继续对破坏可维护性的测试提供建议。第6章涉及有关脆弱或不可靠的测试坏味道。第三部分(第7~9章...

    如何更好的实施单元测试的策略

    我在《单元测试实施解惑(一)》中指出,使用象Cmockery这样的测试框架,将所需测试的模块通过打桩的方法实施单元测试并不是最有效的方法。在这篇文章中,让我们一同来探索更好的方法。在继续探索之前,让我从传统单元...

    Catch2:用于单元测试,TDD和BDD的现代,C ++原生,仅标头的测试框架-使用C ++ 11,C ++ 14,C ++ 17和更高版本(或Catch1上的C ++ 03) .x分支)

    Catch2 v3正在开发中! 您在devel分支上,正在开发Catch2的下一个主要版本v3。... 测试本身会自动注册,不必使用有效的标识符来命名,断言看起来像普通的C ++代码,而部分则提供了一种很好的方式来

    java程序员如何编写更好的单元测试?

    在做单元测试时,代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到80%或90%。于是乎,测试人员费尽心思设 在做单元测试时,代码覆盖率常常被拿...

    testing-workshop:单元测试车间

    演讲(20分钟) 现场测试写作(20分钟) 问题与讨论(20分钟)日程第一周:使用RSpec()进行基本单元测试第2周:与Shoulda的验证和关联() 第3周:避免与《 Factory Girl》打datbase的热潮() 第4周:使用RSpec...

    iOS单元测试和UI测试

    单元测试是开发者编写的一小段代码,用于检验被测代码中的一个很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件...单元测试使你的设计更好7.大大减少花在调试上的时间1.代码会暗藏很多缺陷,健壮性

    cypress-angularjs-unit-test:使用Cypress.io测试运行器对Angularjs代码进行单元测试

    使用Cypress.io测试运行器对Angularjs代码进行单元测试 动机 您可以使用端到端测试任何应用程序,但是如果要分别测试Angular.js值,服务,控制器和组件怎么办? 该适配器使您可以非常快速地执行此操作。 可以在真实...

Global site tag (gtag.js) - Google Analytics