在代码层面描述软件的可测试性
软件可测试性是指通过测试(通常是基于运行的测试)揭示软件缺陷的容易程度。在开发设计良好的系统的成本中,至少有40%是用在了测试上。如果我们能够降低此成本,那带回的回报将是巨大的。当然,如果要对系统进行正确的测试,必须能够“控制”每个组件的内部状态及其输入,然后“观察”其输出。这通常通过使用“测试工具”进行,这是一种专门设计的软件,用于执行所测试的软件。这可能会如同在各种接口上回放已记录的数据一样简单,也可能会像测试发动机的燃烧室一样复杂。
质量属性的第五大战术是可测试性战术。什么是可测试性战术,顾名思义,测试就是为了发现错误,那么可测试性战术的目标是允许在完成软件开发的一个增量后,轻松地对软件进行测试。可测试性战术的分类可以由下图所示:
1.记录/回放(Record/playback ):记录/回放是指将捕获跨接口的信息,并将其作为测试专用软件的输入。
使用命令模式把一个请求或者操作封装到一个对象中,把发出命令的责任和执行命令的责任分割开,委派给不同的对象,可降低行为请求者与行为实现者之间耦合度。在这里,我们通过维护undo和redo两个盛放Command的栈(用List实现),首次执行一个Command时,执行execute()并将其放入undo栈内,同时要清空redo栈;当执行撤销操作时把undo栈内最上面一个Command拿出来执行undo(),然后将其放入redo栈内;执行重做操作时把redo栈内最上面一个Command拿出来执行execute(),然后将其放入undo栈内。
当点击按钮执行操作时,可在其ActionListener内执行commandManager.executeCommand(newXXXCommand());把命令加入栈中。
public class CommandManager { private List undoList = new ArrayList(); private List redoList = new ArrayList(); // 可撤销的步数,-1时无限步 private int undoCount = -1; public CommandManager() { // 可通过配置文件配置撤销步数 undoCount = 5; } /** * 执行新操作 */ public void executeCommand(Command cmd) { // 执行操作 cmd.execute(); undoList.add(cmd); // 保留最近undoCount次操作,删除最早操作 if (undoCount != -1 && undoList.size() > undoCount) { undoList.remove(0); } // 执行新操作后清空redoList,因为这些操作不能恢复了 redoList.clear(); } /** * 执行撤销操作 */ public void undo() { if (undoList.size() <= 0) { return; } Command cmd = ((Command)(undoList.get(undoList.size() - 1))); cmd.undo(); undoList.remove(cmd); redoList.add(cmd); } /** * 执行重做 */ public void redo() { if (redoList.size() <= 0) { return; } Command cmd = ((Command)(redoList.get(redoList.size() - 1))); cmd.execute(); redoList.remove(cmd); undoList.add(cmd); }
2.将接口与实现分离(Separate interface from implementation )
将接口与实现分离指的就是将接口与实现分离允许实现的代替。占位实现允许在缺少被占位组件时,对系统的剩余部分进行测试。
在上面的例子中,我们使用的是命令模式,当然不可避免的要使用接口来进行定义。因此就会使用分离的格式进行测试。
public interface Command { public void execute(); // 执行命令和重做 public void undo(); // 执行撤销操作 }
如果缺少接口类的调用,那就会在代码修改时发生大量的变动,使得测试工作也难以进行下去。
3.特化访问路线/接口
特化的接口可以独立于程序正常运行,即设计独立的接口仅供测试使用。
比如在python web开发的Django框架中,我们通过专门的数据库连接来获取到我们的数据库数据,他并不是在程序运行的过程中直接显示的,而是在刚开始进行框架设计的时候就进行了数据的加载。通过更改配置文件,来达到获取数据的目的。
在settings中创建数据库连接,然后执行脚本,就可以得到我们的数据。
""" Django settings for django_demo_00 project. Generated by 'django-admin startproject' using Django 3.0.3. For more information on this file, see https://docs.djangoproject.com/en/3.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.0/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '!fo8_1bnoyif_rzsg*y4w&b8g4a5r&2pv9j0c-so-2ncrhet_2' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'demo_00_app', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'django_demo_00.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'django_demo_00.wsgi.application' # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db_pymysql', 'USER':'root', 'PASSWORD':'20173673', 'HOST':'localhost', 'PORT':'3306' } } # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = '/static/'
在settings中,有许多的事件都是通过修改配置文件进行的,Django也是通过将需要的接口进行特化,来实现相应功能的实现。
4.内部监视器(Built-in monitors )
内部检测器指的是组件可以维持状态、性能负载、容量、安全性或其他可通过接口访问的信息。当监视状态被激活时可以记录事件。说到这里,就不得不说谷歌浏览器的开发者模式了。
Google通过对网页的传递信息进行监听,进而获得到了请求于传输相应等内容,这在我们做spider分析的时候是至关重要的,同时,对于前端的开发,我们也可以通过他进行前端模块的展示,如果是ajax进行加载的话,就是通过NetWork中的XHR进行查看,加载出的数据也就一目了然了。