Java编程与测试基础-软件构造学习总结01


前言

这篇文章是我在学习软件构造课程系列的第一篇文章,围绕Java编程与测试基础章节,对已学内容进行自我总结以供未来使用。
主要内容包括:

    1. Java项目的创建、文件结构。
    1. Java中如何处理基本的输入。
    1. 如何在Eclipse中使用JUnit进行单元测试。
    1. 使用git向github提交代码。

示例代码是习题课的项目代码,由我自己编写。
题目如下:
在这里插入图片描述


一、Java项目的创建和结构

在Eclipse中创建Java项目

在Eclipse主页面的左上角选择new->Java Project创建项目,如图所示:
创建java项目


Java项目的文件结构

下面是我针对习题课的题目自己创建的项目,其文件结构如图:
在这里插入图片描述
由此可见,Java项目的核心文件结构应该是:
Project file -> Source folder -> Package -> Class
对应到图中,就是
Hindex -> src -> main -> Main.java

注意
在Eclipse中,默认的工作路径为项目文件夹,所以如果代码中涉及到文件操作,采用相对路径时要从项目文件夹开始写起。
上图中的文件"Array.txt"就位于默认的路径下,所以代码中只需要写"Array.txt"即可访问该文件。


二、Java中对输入的基本处理方法

从控制台输入

读取用户的控制台输出,可以采用字符串转换的方法。也就是现将用户的输入视为字符串直接读取,然后再对该字符串进行一系列后续操作来提取有效数据。

首先,需要以系统输入为参数创建Scanner类的实例

Scanner scanner = new Scanner(System.in);

然后,使用Scanner类的 .nextLine() 方法等待并读取用户输入。此方法用于读取一行的输入,返回值是一个字符串:

String input = scanner.nextLine();

之后,便可以对输入的字符串input进行处理了。这里选用习题课的例子,需要对以逗号‘,’分割的一串整数进行读取。
对于分隔符",",使用 .split() 方法,接收分隔符为参数,返回一个字符串数组,每一个元素为分割之后得到的字符串。
对于字符串到int型数据的转换,使用 Integer.parseInt() 方法,接收字符串返回该字符串表示的整数。
代码示例如下:

	public static int[] readInt_0(String input) throws NumberFormatException, NumberFormatException,NegativeNumberException {
	    String[] c_numbers = input.split(",");
	    int[] numbers = new int[c_numbers.length];
	    for(int i = 0; i < numbers.length; i++) {
	        numbers[i] = Integer.parseInt(c_numbers[i]);
	        if(numbers[i] < 0) {
	            System.out.printf("输入的第%d个数为负!\n", i + 1);
	            throw new NegativeNumberException();
	        }
	    }
	    return numbers;
	}

注意:我在编写输入控制的方法时,发现如果将scanner定义在方法的内部,在测试时会出现一系列的问题。所以尽量将scanner定义在主方法main中,将读取的字符串作为参数进行传递。同时也不要忘记在输入结束后关闭scanner。


从文件读入

在java中,其实有多种文件读入类可供选择:

  • FileInputStream:字节流,逐字节的读入,适合读取二进制文件。
  • FileReader:字符流,以系统默认编码读取字符,适合读取文本文件。
  • BufferedReader:具有缓冲区,可一次读取多个字符(比如读取一行),性能较高,通常用这个。

下面选用BufferedReader实现对整行

从文件读入的操作,大致分为三步:

  • 1.创建BufferedReader对象。构造方法需要接受FileReader对象作为参数。
  • 2.使用.readLine()方法即可读入一行
  • 3.处理完毕后,要及时检查null并关闭BufferedReader对象。
BufferedReader r = new BufferedReader(new FileReader("Array.txt"));
String line = r.readLine();

这里FileReader接收文件地址作为参数,同时自己本身也作为参数构造BufferedReader的实例。
.readLine()会从文件中读取一行,返回一个字符串,后续对字符串进行与上面相似的处理即可。

但是由于java中文件的读写等操作,很可能会出现IOException异常。
此异常属于检查性异常,无法避免,要求必须使用try和catch进行处理,否则无法编译。
可以先编写核心代码,然后再套入try和catch结构。
对于函数的返回值等,需要在try结构外创建,try结构外返回,以免出现错误。
关闭Reader需要在finally中,同时也要注意null检查以及异常检查。
示例如下:

public class FileRead 
{
	/**
	 * 从文件中读取整数数组
	 * @return 从文件中读取的数组
	 */
	public static int[] readFromFile()
	{
		//创建BufferedReader对象用于读取文件,接收FileReader指定文件地址作为构造参数
		//为了要处理异常,构造FileReader时需要在try结构内进行。
		BufferedReader r = null;
		String line;
		int[] numbers = null;
		try
		{
			r = new BufferedReader(new FileReader("Array.txt"));
			//当读到文件末尾时,.readLine会返回null.
			//这里要求只读取一行
			System.out.println("文件存储数组为:");
			line = r.readLine();
			System.out.println(line);
			String[] c_numbers = line.split(",");
			numbers = new int[c_numbers.length];
			for(int i=0;i<numbers.length;i++)
			{
				numbers[i] = Integer.parseInt(c_numbers[i]);
			}
		}
		catch(IOException io_1)
		{
			io_1.printStackTrace();
		}
		finally 
		{
			if(r!=null)
			{
				try
				{
					r.close();
				}
				catch(IOException io_2)
				{
					io_2.printStackTrace();
				}
			}
		}
		return numbers;
	}
}

三、使用JUnit进行单元测试

基本步骤

Eclipse中有自带的JUnit插件可供使用,但是需要进行一些操作才能在项目中运行JUnit测试。如果该项目并非使用Eclipse构建,同样也可以在Eclipse平台上使用JUnit进行测试,但是需要根据项目的配置说明,手动导入JUnit库并设置。

其整体步骤如下:

  • 1.在项目中添加JUnit库:
    选择项目->Build Path->选择Class Path->Add Library->添加JUnit
  • 2.在项目中新建Source Folder:test
  • 3.建立测试类:
    选中需要进行测试的java类文件,选择new->other…->JUnit Test Case->选择需要测试的方法
    建立完成后,便可以在test源文件夹中的同名包下找到类文件的测试类文件。
  • 4.编写测试用例:
    使用assertEquals(<期望值>,<方法返回值>) 来进行返回值测试。
    使用assertArrayEquals(<期望数组>,<方法返回的数组>) 来进行返回值为数组的测试。(完全一致才通过)
    使用assertThrows(<异常类型.class>,<函数式接口>) 来进行异常抛出的测试。

注意
在导入JUnit时,必须在Class Path中添加,且项目中不要保留module.info文件,不然会出错!
正确操作的结果如下图所示:
在这里插入图片描述
我在测试代码时也发现,测试文件不需要导入被测试代码中的被测试类,测试仍然可以正常进行。(猜测)这是因为按照正常操作下来,测试代码的包与主代码的包同名,尽管它们处于不同的文件位置,但java将二者视为同一个包,这可以使测试时不必导入,方便快捷。


异常测试与处理

JUnit提供了了assertThrows()方法来进行异常抛出的测试,经过学习,我总结如下:

  • assertThrows() 方法:
  • JUnit提供的测试异常抛出的方法。
  • 第一个参数是预期的异常类型。这里需要加后缀.class,这是因为Class对象表示了一个类的元信息,而非对象实例。
  • 我们关心的是异常类型是否符合预期,而不关系异常的实例,因为异常的实例会随输出而变化。
  • 第二个参数是一个函数式接口的实现,因为用于测试需要输入测试用例,所以一般可以写为:
    ()->{调用需要测试的方法,比如Class.methodName(x,y)}。即使用空参数的Lambda表达式来实现。

这里涉及到了“函数式接口”的概念,通过与ChatGPT一番友好交流后, 我总结如下:

  • 简而言之,函数式接口的最大特征就是其一般只有一个抽象方法。函数式接口用于快速创建一个输入->输出的函数。
  • 函数式接口的实现可以采用方法引用Lambda表达式,二者一般就用在这里,且是等效的。
  • 函数式接口一旦被实现,就可以在任何地方引用,而不需要像传统的那样先创建类,再在类中写方法,更加方便快速。
  • Lambda表达式:形式为 (<参数>,…)->{代码块}。代码块相当于一个函数的主体部分,可以使用return语句。
  • 方法引用:形式为 <类名>::<方法名>。一般用于直接采用已经写好的方法来实现函数式接口。不能使用return语句。

下面对readInt_0()进行测试。主代码如下:

	public static int[] readInt_0(String input) throws NumberFormatException, NumberFormatException,NegativeNumberException {
	    String[] c_numbers = input.split(",");
	    int[] numbers = new int[c_numbers.length];
	    for(int i = 0; i < numbers.length; i++) {
	        numbers[i] = Integer.parseInt(c_numbers[i]);
	        if(numbers[i] < 0) {
	            System.out.printf("输入的第%d个数为负!\n", i + 1);
	            throw new NegativeNumberException();
	        }
	    }
	    return numbers;
	}

测试代码如下:

	@Test
	void testReadInt0() throws NumberFormatException, NegativeNumberException
	{
		//返回值的测试,使用数组测试
		assertArrayEquals(new int[]{1,2,3,4}, Main.readInt_0("1,2,3,4"));
		assertArrayEquals(new int[]{1,1,1,1}, Main.readInt_0("1,1,1,1"));
		assertArrayEquals(new int[]{0}, Main.readInt_0("0"));
		
		//异常抛出的测试
		assertThrows(NumberFormatException.class, ()->{Main.readInt_0("1,2,p,4");});
		assertThrows(NumberFormatException.class, ()->{Main.readInt_0("@qdsaf");});
		assertThrows(NegativeNumberException.class, ()->{Main.readInt_0("1,1,-1,1");});
	}

在进行异常抛出的测试时,出现了许多bug,经过多次修改后,最终通过了测试。一句话总结,就是:测试方法的异常抛出在方法之外而不是方法之内。 也就是说要使用throws关键字,将异常抛出后交给调用该方法的代码段来处理。这样才能使assertThrows()接收到抛出的异常,否则异常将会在方法内部处理完毕,无法抛出。

调用该方法的代码块如下:

		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入数组");
		int[] numbers = null;
		int flag = 1;
		while(flag==1)
		{
			try 
			{
				numbers = readInt_0(scanner.nextLine());
				flag = 0;
			} 
			catch (NumberFormatException e1) 
			{
				System.out.println("非法输入,请重新输入!");
				flag = 1;
			}
			catch(NegativeNumberException e2)
			{
				System.out.println("输入了负数,请重新输入!");
				flag = 1;
			}
		}

在这里,我实际上自定义了一个异常NegativeNumberException

class NegativeNumberException extends Exception {
    public NegativeNumberException() {
        super("输入不能为负数!");//这里是设置异常消息
    }
}

这里的异常抛出有两种方式,为了便于区分我称之为:

  • 1.显式抛出:throw new Exception()。一般用于自定义的异常。
  • 2.自动抛出:比如在这里,如果有非法字符那么就会自动抛出NumberFormatException。

异常抛出后方法会立刻终止,try中后续代码也不会执行,而是直接跳到catch块中。这样就可以利用标识变量flag控制for循环,使用户反复输入直到输入完全合法。


四、使用git向github提交代码

在老师的要求下,之后的每次实验都要在github上提交,所以我学习了一些git与github的基本知识(主要通过git中文使用指南)。当然,git作为一个强大的版本控制系统是本专业必须要学习使用的工具。

这里只简要的说明一下如何创建远程仓库内并关联到本地仓库,以及如何将自己的代码通过git命令进行提交。

如何创建远程仓库并关联到本地仓库

  1. 先在github上创建一个仓库,获取URL。
  2. 在本地创建仓库,
    git init
    
    并使用:
    git remote add <shortname> <URL>
    
    将本地仓库关联到远程仓库
  3. 使用命令
    git fetch <shortname>
    
    便可以拉取仓库的信息。
    拉取后会看到有:* [new branch]的提示
    这表示本地仓库跟踪了新的远程分支。
    此时使用命令
    git branch -r
    
    找到该远程分支,假如它的名字为。
    那么执行
    git checkout -b <local_branch_name> <RemoteBranch>
    
    即可在本地创建一个新的分支并追踪远程分支。
  4. 上传文件
    使用
    git add <filename>
    
    以及
    git commit
    
    将文件提交到本地仓库。
    然后再使用
    git push <remote> <branch>
    
    就可以将本地项目上传到远程仓库了。

一个比较方便的操作

偶然间发现Eclipse中自动检测到了git bash的终端。实际上现在很多IDE都集成了各种终端。在Eclipse中,单击项目,右键选择Show in Local Terminal->Git Bash,即可在页面下方显示git bash界面。
如图所示:

在这里插入图片描述

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值