结构测试是一种白盒测试技术,开发人员以白盒方法根据代码的内部结构设计测试用例。 该方法需要识别代码中所有可能的路径。测试人员选择测试用例输入、执行它们并确定适当的输出。 主要结构测试技术包括:
功能单元测试是一种用于测试应用程序组件功能的”黑盒”测试技术。 主要功能技术包括:
基于错误的单元测试最好由最初设计代码的开发人员构建。技术包括:
https://www.testrail.com/blog/highly-testable-code/
想成为百万富翁吗?好吧,请按照以下两个简单步骤操作:
首先,我们来定义耦合。在软件开发中,这意味着一个给定的软件工件(一个方法、一个类,甚至一个模块)对另一个软件工件的依赖程度。 考虑到这一点,“低耦合”意味着代码的每个部分都应该尽可能少地了解代码的其他部分。
当您的代码是高耦合的时,其维护就会变得昂贵。 例如:您编辑一些被广泛使用的方法签名,这会产生连锁反应。 您很快就会意识到,您几乎已经接触了整个应用程序的代码。 让我们看一个简单的例子。假设您正在编写一个包含 ProductService 类的应用程序:
public class ProductService
{
// fields, properties, etc
public void SaveProduct(ProductToAddDTO productDTO)
{
Product product = MapToEntity(productDTO);
productRepository.Add(product);
productRepository.SaveChanges();
}
// more methods
}
上面的代码只是一个玩具。请允许我在这里发挥你的想象力来填写缺少的相关代码。 是的,代码工作正常,但现在有人决定它需要日志记录。你说,这很公平,然后你将代码更改为如下所示:
productRepository.Add(product);
productRepository.SaveChanges();
var logger = new FileLogger(@"logsapp-demo.log");
logger.Info($"The product with Id {product.Id} was saved.");
上面的代码有错吗?嗯,它可能有效,但有问题。上面的代码与 FileLogger 类紧密耦合。它需要了解得太多了。首先,它知道FileLogger类确实存在。 未来需求完全有可能(甚至很可能)再次发生变化,就像刚刚发生变化一样。两个月后,有人可能会决定代码应该记录到数据库而不是文件。 或者更糟糕的是,除了记录到文件之外,还记录到数据库。上面的代码还知道 FileLogger 的构造函数需要路径。 如果将来它也开始需要一个布尔标志来指示如果文件不存在是否应该创建该文件怎么办? 文件路径。路径、数据库字符串连接、XML 配置文件等都属于我所说的基础设施级别;业务代码逻辑执行中不需要知道。 哦,当然,上面的代码只是一个简单的例子。在一个规模很大的应用程序中,您可能有数百行引用 FileLogger 的代码。 每次开发人员对其进行更改时,都可能需要花费数小时的开发工作。
有一个已知的解决这个问题的方法,它被称为依赖注入(DI)。 DI 包括通过其构造函数以接口的形式传递类所需的依赖项。 让我们重写示例,使用 DI 创建低耦合代码。首先,我们需要一个ILogger接口:
public interface ILogger
{
void Debug(string entry);
void Trace(string entry);
void Info(string entry);
void Warning(string entry);
void Error(string entry);
}
接口声明就位后,我们现在准备根据需要编写接口的 n 个实现。这里就不具体描述代码了。 最后,让我们编辑 ProductService 类:
public class ProductService
{
// fields, properties, etc
public ProductService(IProductRepository repo, ILogger logger)
{
this.repo = repo ?? throw new ArgumentNullException(nameof(repo));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void SaveProduct(ProductToAddDTO productDTO)
{
Product product = MapToEntity(productDTO);
this.repo.Add(product);
this.repo.SaveChanges();
this.logger.Info($"The product with Id {product.Id} was saved.");
}
// more methods
}
您肯定已经注意到,我不仅通过 ProductService 的构造函数注入记录器,而且还注入存储库 IProductRepository。 同样的推理也适用:只要实体被持久化,您的业务规则就不应该关心它们被持久化到哪里。 这样,ProductService 类现在完全可以进行单元测试,无需接触数据库或文件系统。
这个概念是函数式编程范式不可或缺的一部分。面向对象的程序员也应该理解并利用它。 当我们谈论纯代码时,我们实际上是在谈论函数的纯粹性。 简而言之,纯函数是一种既不消耗也不产生副作用的函数。通过一个例子就会变得更清楚。考虑下面的代码:
public int Sum(int a, int b)
{
return a + b;
}
将两个数字相加感觉像是我能想到的最懒的例子,但它是纯函数的完美例证。
一个不纯函数的示例:
public string GetGreeting()
{
var now = DateTime.Now;
var hour = now.Hour;
var greeting = string.Empty;
if (hour >= 6 && hour < 12)
{
greeting = "Good morning!";
}
else if (hour >= 12 && hour < 18)
{
greeting = "Good afternoon!";
}
else
{
greeting = "Good evening!";
}
greeting += " Today is " + now.ToString("D");
}
GetGreeting 方法绝对是不纯粹的。为什么?它从参数以外的源访问数据(参数甚至不存在)。 换句话说,这些差异可能看起来不多,但这些差异的后果可能是巨大的。
纯函数是确定性的。对于给定的输入,它将始终返回相同的输出。这使它安全。你可以放心地调用它一百万次,任何地方的任何事情都不会因此而改变。 纯函数本质上是可测试的。 相反,考虑一下您将如何测试非纯代码的 GetGreeting 函数。 Assert.AreEquals(“这里怎么描述期望输出呢???”, obj.GetGreeting()); 这绝对是可能的,甚至有多种方法可用,但这需要一些思考。
对于任何一个已经从事该行业多年的值得信赖的软件开发人员来说,这一点确实不应该感到惊讶。 业务逻辑和表示之间的明确分离是一个值得追求的目标,即使您没有考虑可测试性。 保持这两个问题之间的严格分离允许您将用户界面层视为插件,根据需要将一种类型的界面替换为另一种类型的界面。 或者您可以拥有多个 UI,所有 UI 都使用相同的底层 API。 但分离逻辑和表示的真正好处在于测试。当您将所有业务逻辑包含在不关心 UI 等脆弱问题的库中时,您将拥有尽可能快速、健壮和可靠的单元测试。
最后,我们来谈谈简单性。我所说的简单并不是一种模糊的、虚假的深刻的方式,这听起来好像在你阅读时有意义,但随后会让你摸不着头脑,想知道到底如何才能以实际的方式应用它。 不,我的意思是非常实用且可衡量的。简单代码是具有低圈复杂度的代码。 简而言之,圈复杂度是给定函数或方法中所有可能的执行分支的数量。具有高圈复杂度的方法将需要大量的测试用例才能正确测试。 如果降低代码的复杂性,代码不仅会变得更易于测试,而且会变得更干净、更可读、更容易维护。
功能测试是针对软件应用的功能进行的测试,以确保它们按照需求规格说明书的要求执行。 功能测试是测试的核心,也是通常意义下所指的测试:
非功能测试关注软件的非功能方面,如性能、可用性、可靠性、响应时间和安全性。 这类测试评估软件系统在非功能需求上的表现。
白盒测试是一种测试软件的方法,用于测试应用程序的内部结构或工作情况。白盒测试又称透明箱、玻璃箱、透明箱测试、结构测试。
黑盒测试是一种软件测试方法,它检查应用程序的功能(例如软件的功能),而无需深入了解其内部结构或工作原理。 黑盒测试完全是基于软件需求和规格说明书来进行的。
单元测试指对系统的一小部分而进行的测试。由于单元测试与代码存在固有耦合性,所以他们是开发人员编写,由单元测试框架执行。 单元测试大小是模糊的,可以是函数、方法、类,甚至是实现某些特定功能的一组相互协助的类。
集成测试这一概念不仅模糊,而且有多重含义。 集成指的是集成多个系统还是多个模块?是系统测试还是单元测试? 这两种含义相差比较大,干系人可能并不相同,所以在使用集成测试这个名称时,需要确保大家理解一致。
验收测试开始是指由最终用户实施的,证明软件符合预期,并为上线做准备的测试活动。 现在这个活动被称为用户验收测试。 验收测试现在通常指由框架执行的自动化黑盒测试,确保所有需求的用户故事都被正确实现。
根据瀑布开发模式和敏捷开发模式,匹配的测试可以分为传统测试和敏捷测试
传统上测试是一个验证的阶段,发生在软件完成构建之后。
测试者更容易跟开发人员形成分裂和对立。
敏捷测试是一种支持敏捷开发的测试方式。在敏捷测试中,测试者参加团队的质量核心小组,并且以任何方式为成功发布做出贡献,而不是仅仅编写测试用例。 敏捷团队的每一个人都承担把客户需要的功能转变到软件里去的责任,而测试者花更多的时间在客户身上,他们的角色就是帮助澄清需求和设计测试用例。 这两个工作都需要非常熟悉业务规则。
工具是辅助测试过程必备的,包括自动化测试框架、环境这些工具,都极大的降低手工测试的难度和繁琐程度。 并且,测试工具可以帮助开发人员检查对于一段自己所写代码的一些假设。 我们通过工具进行测试,会受到工具功能和程序的限制;一般基于工具的测试可能会在第一次发现,后续重复执行时很少会发现新的缺陷以及产生新的洞察了。 通过工具进行测试适合发现回归缺陷以及验证已经存在的假设(设计的用例)。
开发者测试更多的是依赖工具,甚至开发工具以完成测试内容和测试自动化,用来确保预期假设的回归和验证。 开发者测试主要集中在支持测试:通过实现自动化测试,测试驱动开发,稳定开发过程和错误预防活动。